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
git clone --branch v0.10.0 --depth 1 https://github.com/oorestisime/quarry.git
cd quarry
corepack enable
pnpm install
pnpm build:cliThe 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
docker compose -f examples/analytics-api/compose.yaml up -d --wait
pnpm --filter @quarry/example-analytics db:generateThe 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
pnpm --filter @quarry/example-analytics startLeave that terminal running. In another terminal:
curl 'http://localhost:3001/analytics'[
{ "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
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:
[{ "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:
| File | What to look for |
|---|---|
examples/analytics-api/src/analytics.ts | A reusable query with optional filters; hover over AnalyticsRow for the inferred result |
examples/analytics-api/src/server.ts | Date validation, request cancellation, and the /analytics and /sql routes |
examples/analytics-api/src/generated-schema.ts | Types generated from the running database |
examples/analytics-api/test/api.test.ts | Expected responses and comparison with handwritten SQL |
To verify the example yourself, run these from the repository root:
pnpm --filter @quarry/example-analytics typecheck
pnpm --filter @quarry/example-analytics testContinue 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:
docker compose -f examples/analytics-api/compose.yaml downThe 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; setCLICKHOUSE_URLto 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 startand use that port in the requests. - A method is missing: check that you selected the
v0.10.0checkout in step 1.