Declarative database indexer

From source to
queryable — incrementally.

Describe what to index in one YAML file. Indexa handles the how — incremental sync, checkpointing, idempotency, crash-safe resume, blockchain reorg handling, and an auto-generated REST query API. No engine code to write.

Get started Open the App
$ npm i indexa  ·  indexa init my-app && indexa deploy
the pipeline
{{ pipelineSvg }}
declare (yaml + handlers) indexa deploy REST query API
INDEXES CSV & flat files Postgres CDC EVM & blockchain Custom connectors → SQLite / Postgres
Solve it once

One file. Three guarantees you'd otherwise rebuild every time.

Writing an indexer by hand means re-solving where you left off, resuming after a crash, backfilling while tailing, and exposing a query API. Indexa solves these once.

Declarative, not imperative

Describe what to index in YAML; Indexa figures out the how. No engine code, no boilerplate.

schema:
Order:
total: BigDecimal

Incremental & crash-safe

Backfill history, tail new data, resume exactly where it stopped. The checkpoint commits in the same transaction as your writes — never double-write, never skip.

cursor checkpointed → resume here

Query API for free

Every entity becomes a filterable REST resource. Filtering, pagination, ordering — generated from your schema, not written.

GET /orders?status=paid
GET /orders/4
declare → deploy → query

Your schema is the API.

indexa.config.yaml
{{ scYaml }}
$ indexa deploy auto-generated
{{ ep }}
live response
{{ scResp }}
Sources

Connect any source. Tail it incrementally.

A connector exposes ordered streams with a monotonic cursor. The engine persists that cursor inside the same transaction as your writes — that is what makes resumption idempotent.

CSV

cursor · row offset

Zero-dependency files & exports. The fastest way to a query API.

Postgres

cursor · updated_at

Tail a table by a monotonic cursor column. CDC without the moving parts.

EVM

cursor · block number

Index on-chain events with automatic reorg handling. Just give it an RPC URL.

Custom

cursor · your choice

Implement init / streams / close, register it, deploy. Kafka, Firehose, anything.

Every entity becomes a filterable REST resource.

Blockchain · the hard part

Reorgs, handled. Even your running aggregates.

Every write made while indexing an unfinalized block is recorded in an undo journal. When a block hash changes, the engine rolls affected entities back to the last common ancestor — including derived balances — then re-indexes the new canonical chain. All in one transaction.

canonical chain
{{ b.label }}
{{ b.hash }}
{{ b.badge }}
↳ rollback to #107 · reindex new chain ↓
{{ b.label }}
{{ b.hash }}
{{ b.badge }}
holder balances
{{ row.addr }} {{ row.val }}
reorg.test.mjs
{{ reorgLog }}
Architecture

How it works

01

Backfill

Drain each stream from its last checkpoint until caught up with history.

02

Live tail

Poll for records after the cursor on an interval. New rows appear in the API automatically.

03

Idempotency

Entity writes and the checkpoint advance commit together. A crash never double-writes or skips.

 source connector ──stream(cursor)──▶ engine ──transaction──▶ target store ──▶ REST API
                                                               
                                       └─ checkpoint persisted ─┘  (same txn = idempotent)

Declare. Deploy. Query.

From a YAML file to a live, crash-safe, incrementally-synced REST API. Zero engine code.

$ npm i indexa · Node ≥ 22.5 · zero-install SQLite · MIT