hypequery vs @clickhouse/client: What You Actually Gain
The official ClickHouse JavaScript client is solid, but it stops at connections and raw queries. Here's what hypequery adds for TypeScript analytics teams.
The official ClickHouse JavaScript client is fast, well-maintained, and works. If all you need is a connection and the ability to run raw queries, it's the right tool.
If you are comparing approaches at a higher level first, the ClickHouse TypeScript and ClickHouse analytics pillars summarize the two core decisions behind this comparison.
But if you're building an analytics dashboard or a reporting layer in TypeScript, you'll hit a ceiling quickly. This post is about what that ceiling looks like and what hypequery does on the other side of it.
What @clickhouse/client gives you
The official client handles the connection, the protocol, and the query execution. It supports both Node.js and the browser, it's actively maintained by the ClickHouse team, and it's the foundation everything else is built on.
A typical query looks like this:
That unknown[] is the first problem. You get data back, but TypeScript has no idea what's in it. Every field access requires a cast or type assertion. Errors in the query string, a typo, a column that doesn't exist, a renamed field, surface at runtime, not at compile time.
The second problem is reuse. If the same query is needed in an API route, a dashboard component, and a scheduled job, you either write it three times or build your own abstraction on top of the client.
The third problem is serving it. There's no built-in path from raw queries to a typed HTTP endpoint with request validation or generated docs.
None of this is a criticism of the client. It's doing exactly what it promises. These are just the gaps that appear when you start building something real on top of it.
What hypequery adds
hypequery is built on top of @clickhouse/client. It adds three things the client doesn't have:
- type safety derived from your live schema
- a query builder that enforces that schema at compile time
- a server layer that exposes those queries as typed HTTP endpoints
Type safety from your schema
The CLI introspects your ClickHouse schema and generates TypeScript types:
This produces a schema.ts file with a typed representation of every table, column, and runtime mapping in your database. DateTime becomes string, because that's what ClickHouse actually returns over the wire. UInt64 becomes string too. Nullable(String) becomes string | null.
A query builder that knows your schema
With the schema in place, queries are built with a typed builder instead of raw strings:
If event_name doesn't exist on the events table, TypeScript catches it before the query runs. If you pass the wrong value type, TypeScript catches that too.
The same query in multiple contexts
Define a query once and use it across your application without duplication:
That same definition can also be served over HTTP:
One definition. Multiple consumers. One place to update when the schema changes.
That shift from “database client” to “analytics layer” is the main subject of the ClickHouse analytics pillar page.
An HTTP layer with docs
@hypequery/serve takes your query definitions and exposes them as typed HTTP endpoints. If you add Zod schemas, the same definitions also drive docs and OpenAPI output.
No hand-rolling API handlers for every analytics query. No manually duplicating request and response types.
What you give up
hypequery adds an abstraction layer, and abstraction layers have costs.
The query builder doesn't cover everything ClickHouse can do. If you need complex window functions, non-standard ClickHouse SQL extensions, or highly optimized custom query patterns, you'll hit the edges of the builder and may need raw SQL escape hatches.
Schema generation is explicit. If your ClickHouse schema changes, you need to rerun the generator. The types do not update themselves.
It's another dependency. The official client is maintained by ClickHouse. hypequery is an additional layer. For very small scripts or prototypes, the official client plus a few assertions can be enough.
When to use which
Use @clickhouse/client directly when:
- you're running ad-hoc queries or scripts
- your query logic is genuinely one-off
- you need advanced ClickHouse SQL that the builder doesn't yet support
- you're prototyping and the overhead of schema generation isn't worth it yet
Use hypequery when:
- you're building a dashboard, reporting feature, or analytics layer in TypeScript
- the same query logic needs to run in multiple contexts
- you want type safety that reflects your actual ClickHouse schema
- you want typed HTTP endpoints without hand-building the whole server layer
The two tools aren't in competition. hypequery wraps @clickhouse/client. The question is whether the abstraction layer earns its place in your project.
Getting started
If you want the current happy path, start with the Quick Start, then continue with ClickHouse TypeScript or ClickHouse analytics depending on whether your main concern is type mapping or reusable architecture.