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
dbaccess.
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
| Concept | Description | Link |
|---|---|---|
| Select | Choose which columns to return, use aliases, and expressions | Select → |
| Where | Filter rows using conditions, operators, and predicates | Where → |
| Joins | Combine data from multiple tables | Joins → |
| Aggregation | Group data and calculate summaries (sum, count, avg) | Aggregation → |
| Ordering | Sort results and paginate with limit/offset | Ordering → |
Advanced Operations
| Concept | Description | Link |
|---|---|---|
| Time Functions | Work with dates, timestamps, and time intervals | Time Functions → |
| Advanced | CTEs, raw SQL, query settings, and utilities | Advanced → |
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);
});