Drizzle with PostgreSQL

The Apibara Indexer SDK supports Drizzle ORM for storing data to PostgreSQL.

Installation

Using the CLI

You can add an indexer that uses Drizzle for storage by selecting "PostgreSQL" in the "Storage" section when creating an indexer.

The CLI automatically updates your package.json to add all necessary dependencies.

Manually

To use Drizzle with PostgreSQL, you need to install the following dependencies:

Terminal
npm install drizzle-orm pg @apibara/plugin-drizzle@next

We recommend using Drizzle Kit to manage the database schema.

Terminal
npm install --save-dev drizzle-kit

Schema configuration

You can use the pgTable function from drizzle-orm/pg-core to define the schema, no changes required.

The only important thing to notice is that your table must have an id column (name configurable) that uniquely identifies each row. This requirement is necessary to handle chain reorganizations. Read more how the plugin handles chain reorganizationson the internals page.

lib/schema.ts
import { bigint, pgTable, text, uuid } from "drizzle-orm/pg-core";

export const transfers = pgTable("transfers", {
  id: uuid("id").primaryKey().defaultRandom(),
  amount: bigint("amount", { mode: "number" }),
  transactionHash: text("transaction_hash"),
});

Adding the plugin to your indexer

Add the drizzleStorage plugin to your indexer's plugins. Notice the following:

  • Use the drizzle helper exported by @apibara/plugin-drizzle to create a drizzle instance. This method supports creating an in-memory database (powered by PgLite) by specifying the memory: connection string.
  • Always specify the database schema. This schema is used by the indexer to know which tables it needs to protect against chain reorganizations.
my-indexer.indexer.ts
import {
  drizzle,
  drizzleStorage,
  useDrizzleStorage,
} from "@apibara/plugin-drizzle";

import { transfers } from "@/lib/schema";

const db = drizzle({
  schema: {
    transfers,
  },
  connectionString: process.env.DATABASE_URL,
});

export default defineIndexer(EvmStream)({
  // ...
  plugins: [drizzleStorage({ db })],
  // ...
});

Writing and reading data from within the indexer

Use the useDrizzleStorage hook to access the current database transaction. This transaction behaves exactly like a regular Drizzle ORM transaction because it is. Thanks to the way the plugin works and handles chain reorganizations, it can expose the full Drizzle ORM API without any limitations.

my-indexer.indexer.ts
export default defineIndexer(EvmStream)({
  // ...
  async transform({ endCursor, block, context, finality }) {
    const { db } = useDrizzleStorage();

    for (const event of block.events) {
      await db.insert(transfers).values(decodeEvent(event));
    }
  },
});

You are not limited to inserting data, you can also update and delete rows.

Drizzle query

Using the Drizzle Query interface is easy. Pass the database instance to useDrizzleStorage: in this case the database type is used to automatically deduce the database schema.

Note: the database instance is not used to query data but only for type inference.

my-indexer.indexer.ts
const database = drizzle({ schema, connectionString });

export default defineIndexer(EvmStream)({
  // ...
  async transform({ endCursor, block, context, finality }) {
    const { db } = useDrizzleStorage(database);

    const existingToken = await db.query.tokens.findFirst({ address });
  },
});

Querying data from outside the indexer

You can query data from your application like you always do, using the standard Drizzle ORM library.

Improving performance

You should always add an index on the id column. This column is used every time the indexer handles a chain reorganization or a new pending block.

Database migrations

There are two strategies you can adopt for database migrations:

  • run migrations separately, for example using the drizzle-kit CLI.
  • run migrations automatically upon starting the indexer.

If you decide to adopt the latter strategy, use the migrations option:

my-indexer.indexer.ts
import { drizzle } from "@apibara/plugin-drizzle";

const database = drizzle({ schema, connectionString });

export default defineIndexer(EvmStream)({
  // ...
  plugins: [
    drizzleStorage({
      db,
      migrate: {
        // Path relative to the project's root.
        migrationsFolder: "./migrations",
      },
    }),
  ],
  // ...
});
Last modified
Apibara

Apibara is the fastest platform to build production-grade indexers that connect onchain data to web2 services.

© 2025 GNC Labs Limited. All rights reserved.