> hypequery

Quick Start

Get started with hypequery.

Choose your route

This page has two paths:

  • Route 1: Query Builder first if you want typed ClickHouse queries running in-process as quickly as possible
  • Route 2: Reusable queries and serve runtime if you want named query contracts, HTTP routes, docs, or OpenAPI

Route 1: Query Builder first

If you only need typed ClickHouse queries in your application code, start here:

Install the packages

npm install @hypequery/clickhouse
npm install -D @hypequery/cli
pnpm add @hypequery/clickhouse
pnpm add -D @hypequery/cli
yarn add @hypequery/clickhouse
yarn add -D @hypequery/cli
bun add @hypequery/clickhouse
bun add -D @hypequery/cli

Configure your ClickHouse env vars

Create .env:

CLICKHOUSE_URL=https://example.clickhouse.cloud:8443
CLICKHOUSE_DATABASE=default
CLICKHOUSE_USER=cli_user
CLICKHOUSE_PASSWORD=super-secret

hypequery generate uses these values to connect to ClickHouse during schema introspection.

Generate your schema types

Use the CLI to introspect ClickHouse and write your TypeScript schema:

npx hypequery generate

This creates the generated schema type you import into the builder. Re-run it anytime your ClickHouse schema changes.

Connect and run a typed query

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

const db = createQueryBuilder<IntrospectedSchema>({
  url: process.env.CLICKHOUSE_URL!,
  username: process.env.CLICKHOUSE_USER,
  password: process.env.CLICKHOUSE_PASSWORD,
  database: process.env.CLICKHOUSE_DATABASE,
});

const users = await db
  .table('users')
  .select(['id', 'email', 'created_at'])
  .where('status', 'eq', 'active')
  .orderBy('created_at', 'DESC')
  .limit(10)
  .execute();

If that is all you need, continue with Query Basics. Or read more about connecting to ClickHouse.

Route 2: Reusable queries and serve runtime

In this route you will:

  • build a typed ClickHouse query
  • wrap that query with object-style query({ ... }) when it becomes reusable
  • run it locally with query.execute()
  • expose the same query over HTTP with serve({ queries })

This is the right route when the same query should have a stable name, validation, docs, OpenAPI, or HTTP handlers.

Install the packages

npm install @hypequery/clickhouse @hypequery/serve zod
npm install -D @hypequery/cli
pnpm add @hypequery/clickhouse @hypequery/serve zod
pnpm add -D @hypequery/cli
yarn add @hypequery/clickhouse @hypequery/serve zod
yarn add -D @hypequery/cli
bun add @hypequery/clickhouse @hypequery/serve zod
bun add -D @hypequery/cli

Scaffold the analytics folder

Run:

npx hypequery init

The CLI will create a structure like:

schema.ts
client.ts
queries.ts
.env

Build the query, then wrap it

Open analytics/queries.ts. First build the query with the typed builder, then wrap it with query({ ... }) so it becomes a reusable definition:

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

const { query, serve } = initServe({
  context: () => ({ db }),
  basePath: '/api/analytics',
});

const activeUsersQuery = ({ limit }: { limit: number }) =>
  db
    .table('users')
    .select(['id', 'email', 'created_at'])
    .where('status', 'eq', 'active')
    .orderBy('created_at', 'DESC')
    .limit(limit)
    .execute();

const activeUsers = query({
  description: 'Most recent active users',
  input: z.object({
    limit: z.number().min(1).max(500).default(50),
  }),
  output: z.array(z.object({
    id: z.string(),
    email: z.string(),
    created_at: z.string(),
  })),
  query: async ({ ctx, input }) => {
    return activeUsersQuery({ limit: input.limit });
  },
});

export const api = serve({
  queries: { activeUsers },
});

api.route('/active-users', api.queries.activeUsers, { method: 'POST' });

The important distinction:

  • activeUsersQuery(...) is plain typed query-builder logic
  • activeUsers = query({ ... }) turns that logic into a named reusable contract
  • serve({ queries: { activeUsers } }) exposes that contract through a runtime

Run the query locally

Because activeUsers is executable, you can call it without HTTP:

const latest = await activeUsers.execute({
  input: { limit: 25 },
});

console.log(latest);

You can also execute through the exported API:

const latest = await api.run('activeUsers', {
  input: { limit: 25 },
});

Preview docs and routes

Start the dev server:

npx hypequery dev analytics/queries.ts

With basePath: '/api/analytics', the runtime exposes:

  • docs at http://localhost:4000/api/analytics/docs
  • OpenAPI at http://localhost:4000/api/analytics/openapi.json
  • your route at http://localhost:4000/api/analytics/active-users

Try it:

curl -X POST http://localhost:4000/api/analytics/active-users \
  -H "Content-Type: application/json" \
  -d '{"limit": 10}'

What to learn next

  • Use Query Basics if you want to stay at the raw builder layer
  • Read Re-using Queries for the distinction between builder queries and query({ ... })
  • Read Core Concepts for the full builder, query, and serve model
  • See Next.js, Vite, or Node.js once you are ready to mount the runtime inside a framework

On this page