> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sqd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Build an Indexer from Scratch

> Learn to compose SDK packages into a complete indexer

# Build an Indexer from Scratch

This guide demonstrates how SDK packages can be combined into a working indexer (called a *squid*).

This page goes through all the technical details to make the squid architecture easier to understand. If you want to get to a working indexer ASAP, [bootstrap from a template](../guides/make-an-indexer#templates).

## Prerequisites

Before you begin, ensure you have:

* **Node.js 20.x or newer** - [Download here](https://nodejs.org/)
* **Docker** - [Download here](https://www.docker.com/)

## Project: USDT Transfers API

In this tutorial, you'll build an indexer that tracks USDT transfers on Ethereum, saves the data to PostgreSQL, and serves it as a GraphQL API.

### Required packages

From the requirements above, you'll need these [packages](../reference/packages-overview):

* [`@subsquid/evm-stream`](../reference/evm-stream) - for retrieving Ethereum data from a SQD Network Portal
* [`@subsquid/batch-processor`](../reference/batch-processor) - for running the data source against a batch handler
* [`@subsquid/evm-objects`](../reference/evm-stream/context-interfaces) - for enriching the retrieved blocks with item IDs and navigation helpers
* `@subsquid/typeorm-store`, `@subsquid/typeorm-codegen`, and `@subsquid/typeorm-migration` - for saving data to PostgreSQL

### Optional packages

* `@subsquid/evm-typegen` - for decoding Ethereum data and useful constants such as event topic0 values
* `@subsquid/evm-abi` and `@subsquid/evm-codec` - runtime dependencies imported by the code generated by `@subsquid/evm-typegen`
* `@subsquid/graphql-server` / [OpenReader](../reference/openreader/overview) - for serving GraphQL API

## Build the Indexer

<Steps>
  <Step title="Initialize the project">
    Create a new folder and initialize a Node.js project:

    ```bash theme={"system"}
    npm init
    ```

    Create a `.gitignore` file:

    ```.gitignore .gitignore theme={"system"}
    node_modules
    lib
    ```
  </Step>

  <Step title="Install dependencies">
    Install the required packages:

    ```bash theme={"system"}
    npm i dotenv typeorm @subsquid/evm-stream @subsquid/batch-processor @subsquid/evm-objects @subsquid/typeorm-store @subsquid/typeorm-migration @subsquid/graphql-server @subsquid/evm-abi @subsquid/evm-codec
    ```

    Install development dependencies:

    ```bash theme={"system"}
    npm i typescript @subsquid/typeorm-codegen @subsquid/evm-typegen --save-dev
    ```
  </Step>

  <Step title="Configure TypeScript">
    Create a `tsconfig.json` file with minimal configuration:

    ```json tsconfig.json theme={"system"}
    {
      "compilerOptions": {
        "rootDir": "src",
        "outDir": "lib",
        "module": "commonjs",
        "target": "es2020",
        "esModuleInterop": true,
        "skipLibCheck": true,
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
      }
    }
    ```
  </Step>

  <Step title="Define the data schema">
    Create a [`schema.graphql`](../reference/schema-files/schema-files-codegen) file to define your data model:

    ```graphql schema.graphql theme={"system"}
    type Transfer @entity {
      id: ID!
      from: String! @index
      to: String! @index
      value: BigInt!
    }
    ```

    Generate TypeORM classes from the schema:

    ```bash theme={"system"}
    npx squid-typeorm-codegen
    ```

    <Check>The TypeORM classes are now available at `src/model/index.ts`.</Check>
  </Step>

  <Step title="Set up the database">
    Create environment variables in a `.env` file:

    ```.env .env theme={"system"}
    DB_NAME=squid
    DB_PORT=23798
    ```

    Create a `docker-compose.yaml` file for PostgreSQL:

    ```yaml docker-compose.yaml theme={"system"}
    services:
      db:
        image: postgres:15
        environment:
          POSTGRES_DB: "${DB_NAME}"
          POSTGRES_PASSWORD: postgres
        ports:
          - "${DB_PORT}:5432"
    ```

    Start the database:

    ```bash theme={"system"}
    docker compose up -d
    ```

    Compile the TypeORM classes:

    ```bash theme={"system"}
    npx tsc
    ```

    Generate and apply the database migration:

    ```bash theme={"system"}
    npx squid-typeorm-migration generate
    npx squid-typeorm-migration apply
    ```
  </Step>

  <Step title="Generate ABI utilities">
    Create an `abi` folder for contract ABIs:

    ```bash theme={"system"}
    mkdir abi
    ```

    Find the [USDT contract ABI on Etherscan](https://etherscan.io/address/0xdAC17F958D2ee523a2206206994597C13D831ec7#code) and save it to `./abi/usdt.json`.

    Generate TypeScript utility classes from the ABI:

    ```bash theme={"system"}
    npx squid-evm-typegen src/abi ./abi/*
    ```

    <Check>The utility classes are now available in `src/abi/usdt/`, with an `index.ts` entry point. Typegen v5 removes a stale `src/abi/usdt.ts` file from the older single-file layout.</Check>
  </Step>

  <Step title="Create the data source and the batch handler">
    Create `src/main.ts` with the following code blocks:

    ### Imports

    ```typescript main.ts theme={"system"}
    import { run } from "@subsquid/batch-processor";
    import { DataSourceBuilder } from "@subsquid/evm-stream";
    import { augmentBlock } from "@subsquid/evm-objects";
    import { TypeormDatabase } from "@subsquid/typeorm-store";
    import * as usdtAbi from "./abi/usdt";
    import { Transfer } from "./model";
    ```

    ### Data source configuration

    ```typescript main.ts theme={"system"}
    const dataSource = new DataSourceBuilder()
      .setPortal("https://portal.sqd.dev/datasets/ethereum-mainnet")
      .setFields({
        log: {
          topics: true,
          data: true,
        },
      })
      .addLog({
        where: {
          address: ["0xdac17f958d2ee523a2206206994597c13d831ec7"],
          topic0: [usdtAbi.events.Transfer.topic],
        },
      })
      .build();
    ```

    The Portal is public — no API key or RPC endpoint is required, and the stream covers everything from historical blocks to the real-time chain head. Note that [field selection](../reference/evm-stream/field-selection) is explicit: there are no default optional fields, so we list every log field the handler reads (the decoder needs `topics` and `data`).

    ### Database configuration

    ```typescript main.ts theme={"system"}
    const db = new TypeormDatabase({ supportHotBlocks: true });
    ```

    With `supportHotBlocks: true` the store can consume unfinalized blocks and roll the data back automatically on chain reorganizations.

    ### Batch handler

    ```typescript main.ts theme={"system"}
    run(dataSource, db, async (ctx) => {
      const blocks = ctx.blocks.map(augmentBlock);
      const transfers: Transfer[] = [];
      for (let block of blocks) {
        for (let log of block.logs) {
          let { from, to, value } = usdtAbi.events.Transfer.decode(log);
          transfers.push(
            new Transfer({
              id: log.id,
              from,
              to,
              value,
            })
          );
        }
      }
      await ctx.store.insert(transfers);
    });
    ```

    `augmentBlock()` from `@subsquid/evm-objects` enriches the raw block items with unique IDs (used here as entity IDs) and navigation helpers — see [Context interfaces](../reference/evm-stream/context-interfaces).

    <Tip>
      Supplying a `TypeormDatabase` to `run()` causes `ctx.store` to be a
      [PostgreSQL-compatible `Store`
      object](../reference/data-stores/typeorm-store#store-interface).
    </Tip>
  </Step>

  <Step title="Run the indexer">
    Compile the project:

    ```bash theme={"system"}
    npx tsc
    ```

    Start the indexer:

    ```bash theme={"system"}
    node -r dotenv/config lib/main.js
    ```

    <Check>
      The indexer is now processing USDT transfers from the Ethereum blockchain.
    </Check>
  </Step>

  <Step title="Start the GraphQL server">
    Update your `.env` file to include the GraphQL server port:

    ```.env .env theme={"system"}
    DB_NAME=squid
    DB_PORT=23798
    GRAPHQL_SERVER_PORT=4350
    ```

    In a separate terminal, start the GraphQL server:

    ```bash theme={"system"}
    npx squid-graphql-server
    ```

    <Check>
      The GraphQL API with GraphiQL is available at
      [localhost:4350/graphql](http://localhost:4350/graphql).
    </Check>
  </Step>
</Steps>

## Next Steps

Congratulations! You've built a complete blockchain indexer from scratch.

<CardGroup cols={2}>
  <Card title="View Complete Code" icon="github" href="https://github.com/subsquid-labs/squid-from-scratch">
    See the final code for this tutorial
  </Card>

  <Card title="Development Flow" icon="diagram-project" href="../guides/make-an-indexer">
    Learn about the general development workflow
  </Card>
</CardGroup>

<Tip>
  The commands in this tutorial are often abbreviated as [custom `sqd`
  commands](/en/cloud/reference/cli/commands-json) in production squids. Use the
  [`commands.json` file from the EVM
  template](https://github.com/subsquid-labs/squid-evm-template/blob/main/commands.json)
  as a starter.
</Tip>
