Running the Indexer
Use this page when the stream definitions are ready and you need to wire the runtime, database, migrations, and process shape.
Architecture
Section titled “Architecture”The typical Thru indexing stack looks like this:
Chain RPC -> @thru/replay ChainClient -> @thru/indexer runtime -> Postgres tables | v app-owned queries/routesTech Stack Assumptions
Section titled “Tech Stack Assumptions”The current @thru/indexer runtime assumes:
- PostgreSQL-backed tables generated through Drizzle
- a Drizzle database client passed as
db - a
clientFactorythat returns aChainClientfrom@thru/replay - Drizzle Kit or equivalent migration management
- a standalone Node service to run the indexer continuously
If you are not using Postgres plus Drizzle, start with the package reference for @thru/indexer and validate whether the current runtime fits your stack.
Runtime Setup
Section titled “Runtime Setup”import { Indexer } from "@thru/indexer";import { ChainClient } from "@thru/replay";import { db } from "./db";import tokenAccounts from "./account-streams/token-accounts";import tokenTransfers from "./streams/token-transfers";
export function createIndexer() { return new Indexer({ db, clientFactory: () => new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }), eventStreams: [tokenTransfers], accountStreams: [tokenAccounts], defaultStartSlot: 0n, safetyMargin: 64, pageSize: 512, logLevel: "info", });}What Each Runtime Option Does
Section titled “What Each Runtime Option Does”| Option | What it controls |
|---|---|
db | The Drizzle client used for inserts, updates, and checkpoints. |
clientFactory | Fresh replay client creation for backfill and live streaming. |
eventStreams | Append-only streams for event rows. |
accountStreams | Current-state streams for account rows. |
defaultStartSlot | Starting slot when no checkpoint exists yet. |
safetyMargin | How far behind the live tip replay should stay during backfill-to-live switchover. |
pageSize | How many records to request per backfill page. |
logLevel | Runtime verbosity. |
Checkpoints And Schema
Section titled “Checkpoints And Schema”Your Drizzle schema needs the checkpoint table plus every stream table.
export { checkpointTable } from "@thru/indexer";export { tokenAccountsTable } from "./account-streams/token-accounts";export { tokenTransferEvents } from "./streams/token-transfers";Without checkpointTable, the runtime cannot resume safely after restarts.
Process Shape
Section titled “Process Shape”In practice, most apps run the indexer as its own long-lived service:
- load environment and connect to Postgres
- run or verify migrations
- build the
Indexer - call
await indexer.start() - stop gracefully on
SIGTERMorSIGINT
const indexer = createIndexer();
process.on("SIGINT", () => indexer.stop());process.on("SIGTERM", () => indexer.stop());
await indexer.start();Reading The Results
Section titled “Reading The Results”The indexer writes standard Drizzle tables. Query those tables directly from your backend or expose routes owned by your application.
import { desc } from "drizzle-orm";import { db } from "./db";import { tokenTransferEvents } from "./schema";
export async function listRecentTransfers(limit = 50) { return await db .select() .from(tokenTransferEvents) .orderBy(desc(tokenTransferEvents.slot)) .limit(limit);}Next Steps
Section titled “Next Steps”- Open Querying Indexed Data once rows are landing in Postgres.
- See Build an Indexer for production guidance on worker/API separation, resumability, and live validation.
- See
@thru/indexerand@thru/replayfor full package references.