Quarry

Run the quickstart

Go from a seeded ClickHouse database to a typed analytics endpoint.

This walkthrough uses Quarry 0.10.0. You need Node 22 or 24, pnpm 10, Git, and Docker with Compose. The example uses local ports 8123 (ClickHouse) and 3001 (the API).

If you already have a database, add Quarry to your existing project. To explore without installing anything, open the playground.

1. Get the matching example

Terminal
git clone --branch v0.10.0 --depth 1 https://github.com/oorestisime/quarry.git
cd quarry
corepack enable
pnpm install
pnpm build:cli

The v0.10.0 tag fixes the example and workspace dependencies to the version covered by these docs. The example uses the core and CLI built from that checkout. See release guidance when upgrading an existing installation.

2. Start ClickHouse and generate types

Terminal
docker compose -f examples/analytics-api/compose.yaml up -d --wait
pnpm --filter @quarry/example-analytics db:generate

The container creates an analytics.events table with five events: four for demo tenant 1 and one for tenant 2. The CLI writes examples/analytics-api/src/generated-schema.ts from the live schema.

That file models user_id as ClickHouseUInt64, created_at as ClickHouseDateTime, and the defaulted duration_ms column as Generated<number>. The types preserve differences between values you insert, compare, and read. Learn how schema generation works.

3. Run the endpoint

Terminal
pnpm --filter @quarry/example-analytics start

Leave that terminal running. In another terminal:

Terminal
curl 'http://localhost:3001/analytics'
JSON
[
  { "event_type": "purchase", "events": "2", "users": "1" },
  { "event_type": "signup", "events": "2", "users": "2" }
]

The counts are strings because ClickHouse returns UInt64 values that way in JSON. Quarry infers that representation in your editor.

The endpoint fixes the demo tenant to 1. When adapting it, supply the tenant from your application's authenticated caller.

4. Change a filter and inspect the SQL

Terminal
curl 'http://localhost:3001/analytics?eventType=purchase&from=2026-08-04'
curl 'http://localhost:3001/sql?eventType=purchase&from=2026-08-04'

The first request returns:

JSON
[{ "event_type": "purchase", "events": "1", "users": "1" }]

The second returns { query, params } without executing the query. Look for the tenant condition in PREWHERE, the date and event filters in WHERE, and placeholders such as {p0:Int64} with their values in params. All filters run in ClickHouse.

5. Follow the code

Open these files in your checkout:

FileWhat to look for
examples/analytics-api/src/analytics.tsA reusable query with optional filters; hover over AnalyticsRow for the inferred result
examples/analytics-api/src/server.tsDate validation, request cancellation, and the /analytics and /sql routes
examples/analytics-api/src/generated-schema.tsTypes generated from the running database
examples/analytics-api/test/api.test.tsExpected responses and comparison with handwritten SQL

To verify the example yourself, run these from the repository root:

Terminal
pnpm --filter @quarry/example-analytics typecheck
pnpm --filter @quarry/example-analytics test

Continue with daily activity, optional filters, or the recipe setup to build your own queries on the same data.

Stop the example

Stop the API with Ctrl+C, then stop its database:

Terminal
docker compose -f examples/analytics-api/compose.yaml down

The demo container has no persistent data volume. Removing it discards its local data; the next startup loads the seed again.

If a step fails

  • Port 8123 is already occupied: stop the conflicting local service or change the Compose port mapping and the URL in introspect.config.json; set CLICKHOUSE_URL to that URL when starting the API.
  • Database is not healthy: run docker compose -f examples/analytics-api/compose.yaml logs clickhouse.
  • Port 3001 is occupied: start with PORT=3002 pnpm --filter @quarry/example-analytics start and use that port in the requests.
  • A method is missing: check that you selected the v0.10.0 checkout in step 1.

On this page