> hypequery

Core Concepts

Understand the builder, reusable query definitions, and runtime layers in hypequery.

hypequery has three layers:

  • a ClickHouse query builder
  • reusable query definitions
  • an optional runtime for routes, docs, auth, and framework integration

The three layers

1. Query builder

The builder is the lowest-level and most direct part of hypequery.

This is where you:

  • connect to ClickHouse
  • generate a typed schema
  • build typed queries with db.table(...)
  • execute them directly in your application

If your query only lives in one place, this may be all you need.

If you want to go deeper, see our Inside the Query Builder and ClickHouse Behavior pages in the reference section.

2. Reusable query definitions

The next layer turns local query logic into a reusable contract.

This is where you add things like:

  • a stable name
  • input and output schemas
  • validation
  • metadata
  • per-query auth or tenant rules

The important idea is that the SQL-building logic does not move somewhere else. You still write normal builder code. This layer just gives that logic a durable interface.

3. Runtime

The runtime is optional.

It is the layer you add when reusable query definitions should also become part of a larger application surface, such as:

  • HTTP routes
  • generated docs
  • OpenAPI
  • middleware
  • authentication
  • framework handlers

This is not a separate query language. It is a delivery layer around the same query definitions.

The mental model

Start with a local typed query.

Move to a reusable query definition when the same query needs a stable interface.

Add the runtime when that interface should also be exposed through an application boundary such as HTTP or framework routing.

That progression looks like this:

LayerUse it whenAdds
Query builderQuery logic is local to one placeTyped ClickHouse query construction and execution
Reusable query definitionThe query needs a stable contractValidation, schemas, metadata, named execution, per-query rules
RuntimeThe contract needs an application surfaceRoutes, docs, OpenAPI, auth, middleware, framework integration

If you are unsure where to start, start with the builder.

One example, three stages

Stage 1: local builder query

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

This is the simplest useful hypequery program: a typed ClickHouse query executing directly in your code.

Stage 2: reusable query definition

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(),
});

The important change is not the SQL. The important change is that the query now has a defined interface with validation and metadata around the same builder logic.

Stage 3: runtime exposure

At this point the same reusable query can be mounted behind routes, docs, auth, middleware, or framework-specific handlers.

The runtime layer builds on the reusable query definition rather than replacing it.

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

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(),
});

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

Common transitions

Move from the builder to reusable query definitions when:

  • the same query appears in more than one place
  • the query needs validation
  • the query needs metadata
  • the query should have a stable name or contract

Move from reusable query definitions to the runtime when:

  • the query should be callable over HTTP
  • the query should appear in docs or OpenAPI
  • the query should participate in shared auth or middleware
  • the query should integrate with a framework surface

What this means in practice

hypequery is not one thing pretending to be many things.

It is one query model with three levels of use:

  • local typed query building
  • reusable query contracts
  • optional delivery and runtime integration

That is why the same builder code remains central even as you move up the stack.

On this page