Compare

hypequery vs the ClickHouse HTTP Interface

The ClickHouse HTTP interface is a great way to run queries from the shell. hypequery uses the same protocol underneath and adds types, bound parameters, and reuse for application code. Here's when to reach for which.

Decision type

Architecture and workflow fit

Audience

TypeScript teams building on ClickHouse

Outcome

Choose a path and move into implementation

Best for

Application code that queries ClickHouse at request time

Parameters

Bound parameters via the query builder

Response types

Generated from your schema, correct runtime mappings

Reuse

One query definition across execution, HTTP, and React

Exposed endpoints

zod validation, auth hooks, OpenAPI via @hypequery/serve

What this page is for

Use this page when the real question is how one tool changes the shape of your ClickHouse application code, not just which syntax looks nicer.

What this page is not

It is not a broad ecosystem roundup. It stays narrow on the tradeoff a ClickHouse-heavy TypeScript team is actually deciding.

Recommended next move

If the tradeoff already looks clear, stop reading comparisons and test the fit against one real query in your own schema.

Dimension
hypequery
Alternative
Best for
Application code that queries ClickHouse at request time
Scripts, health checks, one-off queries from the shell
Parameters
Bound parameters via the query builder
String-concatenated SQL you escape yourself
Response types
Generated from your schema, correct runtime mappings
Untyped JSON you hand-parse and annotate
Reuse
One query definition across execution, HTTP, and React
Same query string pasted wherever it's needed
Exposed endpoints
zod validation, auth hooks, OpenAPI via @hypequery/serve
You build validation and auth from scratch
Back to comparisons

If you run ClickHouse, you already know the HTTP interface. It's the first thing that works: point curl at port 8123, pass a query, ask for FORMAT JSON, and you get data back.

Short answer: don't replace this for what it's good at. The HTTP interface is simple, fast, and dependency-free, and it's the right tool for scripts, health checks, and one-off queries. What breaks down is using it directly from application code — where the same query needs runtime parameters, correct types, and a definition you can reuse. hypequery speaks the same HTTP protocol underneath (it's built on @clickhouse/client), so this isn't a protocol argument. It's about what sits on top of the protocol.

What the HTTP interface does well

The ClickHouse HTTP interface is genuinely excellent, and it's worth being specific about why:

  • Zero dependencies. Any language with an HTTP client can talk to it. No driver, no ORM, no build step.
  • Fast and direct. You're talking to the server with nothing in the middle. Latency is the network plus the query.
  • Great for automation. Health checks (SELECT 1), CI probes, cron jobs, quick data pulls — the HTTP endpoint is the obvious right answer for all of them.
  • Flexible output. FORMAT JSON, JSONEachRow, CSV, TSV, Pretty — one endpoint, many shapes.

If your task is "check the cluster is up" or "pull yesterday's totals into a spreadsheet," stop reading. curl is the tool.

Where curl-over-HTTP stops scaling

The trouble starts when the HTTP interface moves from your terminal into your backend, and the query stops being a fixed string. Four things go wrong, and they compound.

1. String-concatenated SQL

A real endpoint takes input. A region filter, a date range, a tenant ID. With the HTTP interface, that means building the SQL string yourself:

That interpolation is a SQL injection hole, and even when it isn't malicious it's an escaping bug waiting to happen — a region name with an apostrophe breaks the query. ClickHouse does support server-side bound parameters over HTTP (param_region), but wiring them by hand across every endpoint is exactly the boilerplate that gets skipped under deadline.

2. No types on the response

FORMAT JSON gives you { data: [...], meta: [...], rows: N }. Every field is whatever JSON says it is, and you annotate it by hand:

That cast is a promise you're making to the compiler with nothing backing it. And it's frequently wrong in ClickHouse-specific ways: UInt64 comes back as a string, not a number; DateTime is a string; Nullable(T) can be null. Hand-written types drift from the real schema, and the mismatch surfaces at runtime.

3. No reuse

The "revenue by region" query is needed in the dashboard endpoint, the export job, and the internal admin view. Over raw HTTP, that's the same SQL string pasted in three places. When the definition of revenue changes, you change it three times and hope you found them all. This is the exact failure mode covered in stop writing the same query three times.

4. DIY governance on anything exposed

The moment an HTTP-backed query sits behind a route users can hit, you own validation, auth, and rate limiting yourself. There's no schema on the input, no place to enforce a tenant boundary, no generated API contract for consumers. All of it is hand-rolled per endpoint.

Same protocol, typed contract on top

hypequery doesn't bypass the HTTP interface — it uses it, through @clickhouse/client, which is the official Node client that talks the same HTTP protocol to ClickHouse. What hypequery adds is the layer between that transport and your application.

You generate types from your live schema once:

Then the same "revenue by region, filtered by input" query looks like this:

The where value is a bound parameter, not string interpolation, so injection and escaping aren't your problem. rows is typed from the generated schema, including the runtime realities — revenue is typed to match how ClickHouse actually returns the aggregate, and a UInt64 column would come back as a string, correctly typed. The query is one definition you import wherever it's needed.

And when that query needs to be an endpoint, @hypequery/serve gives you validation, auth hooks, and OpenAPI docs on the same definition — see /clickhouse-rest-api and /clickhouse-openapi:

The input is validated by zod before any SQL runs, the response type flows through to callers, and the same contract is consumed by React hooks on the dashboard.

The honest comparison

ClickHouse HTTP interfacehypequery
TransportHTTP to port 8123Same HTTP, via @clickhouse/client
Best forScripts, health checks, one-offsApplication code at request time
ParametersYou build the SQL stringBound parameters via the builder
Response typesUntyped JSON, hand-annotatedGenerated from your schema
Runtime mappingsYou remember UInt64 is a stringEncoded in the generated types
ReuseCopy the query stringOne imported definition
Exposed endpointsDIY validation and authzod, auth hooks, OpenAPI via serve
DependenciesNoneAn npm dependency

When the HTTP interface is still the right call

Be clear-eyed about this: hypequery is a dependency in your application, and there are jobs where that's overhead you don't want.

  • Health checks and probes. curl 'http://host:8123/?query=SELECT%201' is perfect. Don't wrap it.
  • Ad-hoc exploration. Poking at data from a shell or a notebook — the HTTP endpoint is faster to reach for than any library.
  • Non-TypeScript scripts. A Python cron job or a Go health checker has no reason to adopt a TypeScript query layer.
  • Bulk exports and inserts. Streaming a large result or loading data is transport-level work; the client and the HTTP interface handle it directly.

hypequery earns its place when queries live in your TypeScript application, take runtime input, need correct types, and get reused. It's not either/or — the same protocol runs underneath either way. For the deeper look at the client hypequery builds on, see hypequery vs @clickhouse/client and the clickhouse-js guide.

Getting started

The quick start takes you from your existing ClickHouse — the same server you've been curl-ing — to generated types and a served, typed endpoint in a few minutes. If you want more background first, /clickhouse-api covers the interface options, and /clickhouse-rest-api covers putting a governed API in front of your queries.

Decision checkpoint

If the tradeoff is already clear, move into implementation

Most teams do not need another round of comparison content after this point. The better test is whether the workflow holds up on your own schema and your own query complexity.

Related content

Continue into implementation

FAQ

Does hypequery replace the ClickHouse HTTP interface?

No. hypequery builds on @clickhouse/client, which speaks the same HTTP protocol to ClickHouse. It adds generated types, bound parameters, and reusable query definitions on top of the same transport.

When should I stick with raw curl over HTTP?

For health checks, ad-hoc exploration, shell scripts, and CI probes, curl against port 8123 with FORMAT JSON is the right tool. There is nothing to install and nothing to maintain.

Is string-concatenated SQL over HTTP a real risk?

Yes, once user input reaches the query. Interpolating values into a SQL string exposes you to injection and escaping bugs. hypequery's query builder binds parameters and validates inputs before any SQL runs.

Next step

Move from evaluation into a typed ClickHouse workflow

Generate schema types, define your first reusable query, and decide whether it should run locally or over HTTP.