> hypequery

Manual Installation

Install packages, configure env vars, generate schema types, and create a typed ClickHouse client.

Manual Installation

Use this page as a setup reference when you do not want to start from hypequery init.

Prerequisites

  • Node.js 18+
  • ClickHouse credentials

1. Install 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

2. Configure env vars

Create .env:

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

3. Generate schema types

npx hypequery generate --output analytics/schema.ts

Re-run this anytime your ClickHouse schema changes.

4. Create the typed client

Create analytics/client.ts:

import 'dotenv/config';
import { createQueryBuilder } from '@hypequery/clickhouse';
import type { IntrospectedSchema } from './schema';

export const db = createQueryBuilder<IntrospectedSchema>({
  host: process.env.CLICKHOUSE_HOST!,
  database: process.env.CLICKHOUSE_DATABASE!,
  username: process.env.CLICKHOUSE_USERNAME!,
  password: process.env.CLICKHOUSE_PASSWORD,
});

5. Build a simple query

Create analytics/example.ts:

import { db } from './client';

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

This is the first useful step in hypequery: a typed ClickHouse query.

6. Need to share it?

Once that builder query needs a name, typed input, or reuse across your app, wrap it with query({ ... }).

Create analytics/queries.ts:

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

const { query } = initServe({
  context: () => ({ db }),
});

export const latestUsers = query({
  description: 'Most recent active users',
  input: z.object({
    limit: z.number().min(1).max(100).default(10),
  }),
  query: ({ ctx, input }) =>
    ctx.db
      .table('users')
      .select(['id', 'email', 'created_at'])
      .where('status', 'eq', 'active')
      .orderBy('created_at', 'DESC')
      .limit(input.limit)
      .execute(),
});

Execute it locally:

const rows = await latestUsers.execute({
  input: { limit: 25 },
});

console.log(rows);

This is the point where a local builder query becomes a reusable contract you can execute in-process.

7. Continue with the main flow

  • Use Query Basics to keep learning the raw builder chain
  • Use Core Concepts to understand when to stay at the query layer and when to add the runtime

On this page