> hypequery
Query Building

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

Working Inside Serve Queries

The core API is identical everywhere, but these docs focus on the serve framework. Every snippet lives inside a query(({ ctx }) => …) resolver and uses ctx.db:

import { initServe } from '@hypequery/serve';
import { z } from 'zod';

const { define, queries, query } = initServe({
  context: ({ headers }) => ({
    db,
    tenantId: headers.get('x-tenant-id'),
  }),
});

export const api = define({
  queries: queries({
    getActiveUsers: query
      .input(z.object({ status: z.string() }))
      .query(async ({ ctx, input }) => {
        return ctx.db
          .table('users')
          .where('status', 'eq', input.status)
          .select(['id', 'name', 'email'])
          .execute();
      }),
  }),
});

Want to use the query builder without the serve framework? The Standalone Query Builder docs show the same operators with direct db access.

Query Builder Pattern

All queries follow this pattern:

return ctx.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
ctx.db
  .table('users')
  .select(['id', 'name', 'email'])
  .execute();
// Returns: Promise<Array<{ id: number; name: string; email: string }>>

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

// Operators match column types
ctx.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 ctx.db
  .table('users')
  .where('status', 'eq', 'active')
  .select(['id', 'name'])
  .execute();

stream()

Stream results for large datasets:

const stream = await ctx.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 ctx.db
  .table('events')
  .select(['id', 'data'])
  .streamForEach(async (row) => {
    await processEvent(row);
  });

On this page