> hypequery

Query Basics

hypequery's query builder provides a fluent, type-safe API for building ClickHouse queries. Whether you're using the serve framework or the standalone client, the query builder API remains consistent.

Core Concepts

The query builder is designed to be:

  • Type-safe - TypeScript ensures columns and types are correct
  • Fluent - Chain methods naturally to build complex queries
  • Database-agnostic API - Works with ClickHouse using familiar patterns
  • Consistent - The same API whether you're in serve or standalone mode

Serve vs Standalone

The query builder API is identical in both contexts. The only difference is how you access the database client and execute queries.

import { initServe } from '@hypequery/serve'; import { db } from './client'; const { define, queries, query } = initServe({ context: () => ({ db }), }); export const api = define({ queries: queries({ getUsers: query .input(z.object({ status: z.string() })) .query(({ ctx, input }) => ctx.db.table('users') .where('status', 'eq', input.status) .select(['id', 'name', 'email']) .execute() ), }), });
import { createClient } from '@hypequery/clickhouse'; const db = createClient({ host: 'localhost', port: 8123, database: 'analytics', }); async function getActiveUsers() { const users = await db.table('users') .where('status', 'eq', 'active') .select(['id', 'name', 'email']) .execute(); return users; }

Key Difference

  • Serve: Use ctx.db inside query definitions and return the promise
  • Standalone: Use db directly and await the execution

Query Builder Pattern

All queries follow this pattern:

db.table('table_name')
  .where('column', 'operator', 'value')
  .select(['col1', 'col2'])
  .orderBy('created_at', 'DESC')
  .limit(10)
  .execute();

Remember

Always finish your query chains with .execute() to run the query.

Query Building Blocks

The query builder is organized into logical concepts. Each concept has detailed documentation:

Core Operations

ConceptDescriptionLink
SelectChoose which columns to return, use aliases, and expressionsSelect →
WhereFilter rows using conditions, operators, and predicatesWhere →
JoinsCombine data from multiple tablesJoins →
AggregationGroup data and calculate summaries (sum, count, avg)Aggregation →
OrderingSort results and paginate with limit/offsetOrdering →

Advanced Operations

ConceptDescriptionLink
Time FunctionsWork with dates, timestamps, and time intervalsTime Functions →
AdvancedCTEs, raw SQL, query settings, and utilitiesAdvanced →

Type Safety

hypequery ensures type safety throughout the query building process:

// TypeScript knows the exact columns in your schema
db.table('users')
  .select(['id', 'name', 'email'])
  .execute();
// Returns: Promise<Array<{ id: number; name: string; email: string }>>

// Invalid columns are caught at compile time
db.table('users')
  .select(['id', 'invalid_column']) // ❌ TypeScript error
  .execute();

// Operators match column types
db.table('users')
  .where('created_at', 'eq', '2024-01-01') // ✅ Valid
  .where('age', 'gte', 18) // ✅ Valid
  .execute();

Execution Methods

execute()

Run the query and get all results:

const users = await db
  .table('users')
  .where('status', 'eq', 'active')
  .select(['id', 'name'])
  .execute();

stream()

Stream results for large datasets:

const stream = await db
  .table('events')
  .select(['id', 'data'])
  .stream();

const reader = stream.getReader();
while (true) {
  const { done, value: rows } = await reader.read();
  if (done) break;
  // Process rows in batches
}

streamForEach()

Process rows with a callback:

await db
  .table('events')
  .select(['id', 'data'])
  .streamForEach(async (row) => {
    await processEvent(row);
  });

On this page