# Real-time API gateway for ClickHouse

> **Subpages:** [Access Control](https://wavehouse.dev/access-control.md) · [API Reference](https://wavehouse.dev/api.md) · [Architecture](https://wavehouse.dev/architecture.md) · [Claude Code & AI agents](https://wavehouse.dev/claude-code.md) · [Configuration](https://wavehouse.dev/configuration.md) · [Deployment](https://wavehouse.dev/deployment.md) · [Development](https://wavehouse.dev/development.md) · [Getting Started](https://wavehouse.dev/getting-started.md) · [Ingest Pipeline](https://wavehouse.dev/ingest-pipeline.md) · [Named Pipes](https://wavehouse.dev/pipes.md) · [Query playground](https://wavehouse.dev/playground.md) · [Behind a reverse proxy](https://wavehouse.dev/reverse-proxy.md) · [TypeScript SDK](https://wavehouse.dev/sdk.md) · [Why WaveHouse?](https://wavehouse.dev/why-wavehouse.md)
> **Also:** [HTML version](https://wavehouse.dev/) · [Docs index](https://wavehouse.dev/llms.txt)

---

<div class="wh-stats not-content">
  <div class="wh-stat">
    <span class="wh-stat__value">1</span>
    <span class="wh-stat__unit">binary</span>
    <span class="wh-stat__label">API · worker · NATS · dedup</span>
  </div>
  <div class="wh-stat">
    <span class="wh-stat__value">&lt; 100</span>
    <span class="wh-stat__unit">ms</span>
    <span class="wh-stat__label">warm-cache query path, local bench</span>
  </div>
  <div class="wh-stat">
    <span class="wh-stat__value">SSE</span>
    <span class="wh-stat__unit">streaming</span>
    <span class="wh-stat__label">real-time push, gap-filled from history</span>
  </div>
  <div class="wh-stat">
    <span class="wh-stat__value">Apache 2.0</span>
    <span class="wh-stat__unit">open</span>
    <span class="wh-stat__label">no vendor lock, no SaaS tier</span>
  </div>
</div>

## Why WaveHouse exists

ClickHouse is a phenomenal OLAP database, but pointing a frontend straight at it comes with sharp edges: custom APIs, Kafka queues to avoid "too many parts" errors, replacing-merge logic for deduplication. WaveHouse abstracts all of that into a single, deployable binary so you stop interacting with ClickHouse directly.

<div class="wh-versus not-content">
  <div class="wh-versus__col wh-versus__col--pain">
    <span class="wh-versus__head">Frontend → ClickHouse, directly</span>
    <ul>
      <li>One insert per event → <strong>Too many parts</strong>, HTTP 500 under load</li>
      <li>No backpressure, no edge validation — bad rows fail late</li>
      <li>No real-time push — poll every 2s or bolt on Kafka + WebSockets</li>
      <li>No row/column security — hand-write tenant filters on every query</li>
    </ul>
  </div>
  <div class="wh-versus__arrow">→</div>
  <div class="wh-versus__col wh-versus__col--win">
    <span class="wh-versus__head">Frontend → WaveHouse → ClickHouse</span>
    <ul>
      <li>Async WAL + batched flush — <strong>never</strong> "too many parts"</li>
      <li>Schema-validated at the edge, <strong>503 + Retry-After</strong> under load</li>
      <li>Native SSE push, gap-filled from history — no extra stack</li>
      <li>Hasura-style JWT row/column policies, built in</li>
    </ul>
  </div>
</div>

If you're building user-facing analytics, **WaveHouse is like Supabase for ClickHouse** — or an open-source Tinybird that pushes data to the frontend in real time over SSE, not just via pull-based REST.

<CardGrid>
 <Card title="Schema-aware validation" icon="approve-check">
  WaveHouse discovers your ClickHouse schemas via `system.columns` and validates every ingest against the real schema — unknown fields, type mismatches, and null violations are rejected at the edge.
 </Card>
 <Card title="Async buffered ingest" icon="rocket">
  Writes land in a durable NATS JetStream WAL and return `200 OK` instantly. A background worker batch-flushes to ClickHouse — never drop a packet.
 </Card>
 <Card title="Real-time push" icon="bars">
  Every event is broadcast to SSE subscribers **before** it's flushed to ClickHouse. Gap-fill from JetStream history for late-connecting clients.
 </Card>
 <Card title="In-process query cache" icon="laptop">
  Ristretto cache plus Go `singleflight` coalesces identical concurrent queries — dashboards survive thundering herds without an extra cache tier to operate.
 </Card>
 <Card title="Hasura-style access control" icon="seti:lock">
  Per-table, per-role column and row-level policies with JWT claim templating. Stored in NATS KV with file-based bootstrap and cluster sync.
 </Card>
 <Card title="TypeScript SDK" icon="seti:typescript">
  `@wavehouse/sdk` — zero-dependency client with type-safe query builder, live queries, real-time streaming, and codegen from your schemas.
 </Card>
</CardGrid>

**Plus** — optional [deduplication](/configuration#deduplication) (idempotent ingest by ID), a dead-letter queue for failed batch inserts, and Tinybird-style [named pipes](/pipes) with parameter binding and per-role restrictions.

## Query it like a database. Subscribe to it like a socket.

The zero-dependency [TypeScript SDK](/sdk) wraps the whole surface — typed inserts, a chainable query builder, and live queries that backfill history before streaming. The **Query** and **Live updates** tabs below are live: tweak the query, hit **Run**, and watch it execute in your browser against a public read-only demo — or open the full [query playground](/playground).

<HomeQueryDemo />

## Up in five minutes

One compose file — ClickHouse + WaveHouse — and you have an ingest endpoint, a query endpoint, and a live event stream:

```bash
# Boot ClickHouse + WaveHouse with one compose file
git clone https://github.com/Wave-RF/WaveHouse.git
cd WaveHouse
docker compose -f deployments/compose/standalone.yaml up -d

# Ingest an event (run Getting Started first to create the `clicks` table; the dev stack ships a trial policy)
curl -X POST "http://localhost:8080/v1/ingest?table=clicks" \
  -H 'content-type: application/json' \
  -d '{"page":"/home","button":"signup","score":42.5}'

# Subscribe to the live stream
curl -N "http://localhost:8080/v1/stream?table=clicks"
```

WaveHouse is fail-closed, so the standalone stack ships a permissive trial policy (a non-admin `public` role that can read/write the demo tables, seeded on first boot) — then it's five minutes from `git clone` to a live event stream. See [Getting Started](/getting-started).

**How it ships:** one `wavehouse` process — API, batch worker, embedded NATS JetStream, and optional embedded Pebble dedup, all in-process. **The only external dependency is ClickHouse.** Same binary on a laptop, in Docker, or on a production host.

## Where to next

<CardGrid>
 <LinkCard
  title="Getting Started"
  description="Five-minute quickstart — Docker Compose, first ingest, first query, first stream."
  href="/getting-started"
 />
 <LinkCard
  title="Architecture"
  description="System design, ingest/query/streaming data flows, and the internal package map."
  href="/architecture"
 />
 <LinkCard
  title="API Reference"
  description="Every endpoint, authentication, and request/response format."
  href="/api"
 />
 <LinkCard
  title="TypeScript SDK"
  description="Query builder, live queries, streaming, and schema codegen."
  href="/sdk"
 />
</CardGrid>

<div class="wh-closer not-content">
  <p class="wh-closer__title">WaveHouse is <span class="wh-closer__alpha">alpha</span> — and built entirely in the open.</p>
  <p class="wh-closer__sub">Apache-2.0-licensed, single binary, no SaaS tier, no vendor lock-in. We ship with honest expectations — see the <a href="https://github.com/Wave-RF/WaveHouse/blob/main/SUPPORT.md" target="_blank" rel="noopener noreferrer">support cadence</a> and <a href="https://github.com/Wave-RF/WaveHouse/blob/main/SECURITY.md" target="_blank" rel="noopener noreferrer">security policy</a>. Kick the tires and tell us where it breaks.</p>
  <div class="wh-closer__actions">
    <LinkButton href="/getting-started" icon="right-arrow">Get started in five minutes</LinkButton>
    <LinkButton href="https://github.com/Wave-RF/WaveHouse" variant="secondary" icon="external">Star on GitHub</LinkButton>
    <span class="wh-closer__count" data-wh-live-wrap hidden>
      <span class="wh-closer__count-star" aria-hidden="true">★</span>
      <span data-wh-live="stars">—</span> and counting — counted live by WaveHouse
    </span>
  </div>
</div>

<HomepageCtaTracking />