> hypequery

Query Basics

Learn the hypequery ClickHouse query builder for TypeScript. Build type-safe queries with autocomplete, reusable filters, joins, and strongly typed results.

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 chain continues to work inside query({ ... })

Start with db

The core builder API starts with a typed db client. These docs show the raw builder chain first because it is the clearest way to learn:

import { createClient } from '@clickhouse/client';
import { createQueryBuilder } from '@hypequery/clickhouse';
import type { Schema } from './generated-schema';

const client = createClient({
  host: process.env.CLICKHOUSE_HOST!,
  username: process.env.CLICKHOUSE_USERNAME!,
  password: process.env.CLICKHOUSE_PASSWORD!,
  database: process.env.CLICKHOUSE_DATABASE!,
});

const db = createQueryBuilder<Schema>({ client });

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

Here, client is the underlying ClickHouse JavaScript client. If you have not set that up yet, see Connecting to ClickHouse.

When you move into query definitions, the builder chain stays the same. The Query Building section keeps examples in terms of a standalone typed db client so the builder API is easier to learn in isolation.

Query Builder Pattern

All queries follow this pattern:

return 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 →
Subqueries & CTEsCompose subqueries, reusable CTEs, and more complex builder flowsSubqueries & CTEs →
SQL ExpressionsDrop to raw SQL fragments and expression helpers when the fluent API is not enoughSQL Expressions →
Time FunctionsWork with dates, timestamps, and time intervalsTime Functions →

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