ClickHouse Analytics

Turn ClickHouse into a reusable analytics layer instead of a pile of raw queries

This page is about what happens after the first few queries work. The database is fast, but the same metrics start showing up in route handlers, exports, dashboards, and internal tools. hypequery gives those queries one place to live so the codebase stops forking the same logic.

Core pattern

Schema-driven analytics layer

Execution targets

Local, HTTP, React

Best fit

Product and internal analytics

Metrics get reimplemented everywhere

A team writes one version of “active users” for a dashboard, another for an export, and another for an API. Each one is close enough to feel safe until the numbers stop matching.

The useful queries never stay local

The query that starts in one file usually ends up feeding several consumers. Without a named definition, every new consumer becomes another copy and another place for the logic to drift.

ClickHouse speed does not solve application sprawl

Fast scans are not the bottleneck once several teams or surfaces depend on the same numbers. The real problem is keeping access, filters, and result shapes consistent as usage expands.

What changes

Give important queries names and reuse them on purpose

The main shift is simple: stop treating analytics queries as loose implementation detail. Generate the schema, define the query once, and make that definition the thing other parts of the product call.

  • Generate schema types from ClickHouse so query code matches runtime reality
  • Define named queries in TypeScript instead of scattering SQL strings
  • Call those definitions locally or expose them over HTTP when needed
  • Keep input validation and response typing next to the query itself
  • Add new consumers without rewriting the underlying metric logic

Named query

One shared definition for a metric the product keeps using

what-changes.ts

The page gets much less abstract once you see the unit being shared. It is just a named query with typed input and one obvious place to change it later.

What reuse looks like

The query stays the same even when the consumer changes

This is the part that makes the architecture useful in practice. A server call, an HTTP endpoint, and a browser hook can all depend on the same query instead of growing their own local version.

That is the only claim this page really needs to make. It is not that SQL is hard. It is that reused analytics logic should stop being copied from file to file.

If your current pain is runtime types, go to the TypeScript page. If your current pain is tenant-scoped customer analytics, go to the SaaS analytics page.

Consumers

Three places the same metric can show up

what-reuse-looks-like.ts

Nothing fancy is happening here. The point is that the consumer changes and the definition does not.

Where teams usually get stuck

The questions this page should answer

When this architecture matters

It matters once a few queries stop being one-off reports and start feeding product features, internal tools, or several teams at the same time.

What this is not

This is not a huge BI platform and it is not trying to be. It is a code-first way to stop duplicating analytics logic inside a TypeScript application.

What teams actually gain

Fewer mismatched numbers, fewer route-specific rewrites, and one obvious place to change a metric when the definition moves.

Where to go next

Use the TypeScript page for runtime type problems, the REST page for delivery over HTTP, and the dashboard page for the browser-side consumption story.

Next step

Take one repeated metric and give it a single definition

Pick a metric that already exists in more than one place, move it into a named query, and wire one consumer to it. That is the fastest way to tell whether this model earns its place in your codebase.