---
title: Running the Indexer
description: Wire the Thru indexer runtime to Postgres, Drizzle, replay clients,
  and application-owned read paths.
source_url:
  html: https://thru.org/docs/indexing/running-the-indexer/
  md: https://thru.org/docs/indexing/running-the-indexer.md
---

# 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

The typical Thru indexing stack looks like this:

```text
Chain RPC -> @thru/replay ChainClient -> @thru/indexer runtime -> Postgres tables
                                                                     |
                                                                     v
                                                        app-owned queries/routes
```

## Tech Stack Assumptions

The current `@thru/indexer` runtime assumes:

- PostgreSQL-backed tables generated through Drizzle
- a Drizzle database client passed as `db`
- a `clientFactory` that returns a `ChainClient` from `@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`](https://thru.org/docs/sdks/web-packages/indexer.md) and validate whether the current runtime fits your stack.

## Runtime Setup

```ts
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

| 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

Your Drizzle schema needs the checkpoint table plus every stream table.

```ts
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

In practice, most apps run the indexer as its own long-lived service:

1. load environment and connect to Postgres
2. run or verify migrations
3. build the `Indexer`
4. call `await indexer.start()`
5. stop gracefully on `SIGTERM` or `SIGINT`

```ts
const indexer = createIndexer();

process.on("SIGINT", () => indexer.stop());
process.on("SIGTERM", () => indexer.stop());

await indexer.start();
```

## Reading The Results

The indexer writes standard Drizzle tables. Query those tables directly from your backend or expose routes owned by your application.

```ts
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

- Open [Querying Indexed Data](https://thru.org/docs/indexing/querying-indexed-data.md) once rows are landing in Postgres.
- See [Build an Indexer](https://thru.org/docs/indexing/build-an-indexer.md) for production guidance on worker/API separation, resumability, and live validation.
- See [`@thru/indexer`](https://thru.org/docs/sdks/web-packages/indexer.md) and [`@thru/replay`](https://thru.org/docs/sdks/web-packages/replay.md) for full package references.
