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

# MongoDB Benchmarking Guide for Node.js with tinybench

export const NpmInstall = ({packages}) => {
  return <CodeGroup>
      <code className="language-sh" filename="npm">
      npm install --save-dev {packages}
      </code>

      <code className="language-sh" filename="yarn">
      yarn add --dev {packages}
      </code>

      <code className="language-sh" filename="pnpm">
      pnpm add -D {packages}
      </code>
    </CodeGroup>;
};

<Tip>
  If using [`Vitest`](https://vitest.dev/), is possible in your project, we highly
  recommend using it instead of `tinybench`.

  [Click here to see how to use the MongoDB instrument with Vitest](/instruments/databases/mongodb/nodejs/vitest).
</Tip>

<Info>
  Make sure you are using the minimum required version of the plugin:
  [`@codspeed/tinybench-plugin>=3.0.0`](https://github.com/CodSpeedHQ/codspeed-node/releases/tag/v3.0.0)
</Info>

All the code shown on this page is available in the
[`CodSpeedHQ/codspeed-nestjs-mongodb` repository](https://github.com/CodSpeedHQ/codspeed-nestjs-mongodb).
It uses the following technologies:

* [NestJS](https://nestjs.com/)
* [MongoDB](https://www.mongodb.com/)
* [tinybench](/benchmarks/nodejs/tinybench)
* [Docker](https://www.docker.com/)
* [`mongoose`](https://mongoosejs.com/)
* [`@nestjs/mongoose`](https://docs.nestjs.com/techniques/mongodb)

## Sample application

We are going to use a simple NestJS application exposing a REST API to manage
cats.

The following `Cat` model is defined:

```typescript src/cats/schemas/cat.schema.ts theme={null}
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { HydratedDocument } from "mongoose";

export type CatDocument = HydratedDocument<Cat>;

@Schema()
export class Cat {
  @Prop({ index: 1, required: true })
  name: string;

  @Prop({ required: true })
  age: number;

  @Prop({ required: true })
  breed: string;
}

export const CatSchema = SchemaFactory.createForClass(Cat);
```

The `CatsController` exposes the following endpoints:

```typescript src/cats/cats.controller.ts theme={null}
import { Controller, Get, Param } from "@nestjs/common";
import { CatsService } from "./cats.service";
import { Cat } from "./schemas/cat.schema";

@Controller("cats")
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get("name/:name")
  async findByName(@Param("name") name: string): Promise<Cat[]> {
    return this.catsService.findByName(name);
  }

  @Get("breed/:breed")
  async findByBreed(@Param("breed") breed: string): Promise<Cat[]> {
    return this.catsService.findByBreed(breed);
  }
}
```

## Complete setup with Docker

### Setup dependencies

Install the `@codspeed/tinybench-plugin`:

<NpmInstall packages="@codspeed/tinybench-plugin" />

### Create benchmarks

Let's create a script that defines benchmarks on the `cats` endpoints of the
application.

```typescript src/cats/cats.controller.e2e.bench.ts theme={null}
import { faker } from "@faker-js/faker";
import { INestApplication } from "@nestjs/common";
import { getModelToken } from "@nestjs/mongoose";
import { Test } from "@nestjs/testing";
import { AppModule } from "app.module";
import { Model } from "mongoose";
import request from "supertest";
import { Bench } from "tinybench";
import { CatsFactory } from "./cats.factory";
import { Cat } from "./schemas/cat.schema";

faker.seed(1); // enforce the same seed, to remove randomness from generated data

const cats: Cat[] = Array.from({ length: 100 }, () => ({
  name: ["river", "felix", "toto", "marcel"][faker.number.int(3)],
  age: faker.number.int(20),
  breed: ["chausie", "toyger", "abyssinian", "birman"][faker.number.int(3)],
}));

export function registerCatControllerBenches(bench: Bench) {
  let app: INestApplication;
  let catsModel: Model<Cat>;
  let catsFactory: CatsFactory;

  // initialize the application before the benchmark
  async function beforeAll() {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();
    app = moduleRef.createNestApplication();
    catsModel = moduleRef.get(getModelToken(Cat.name));
    catsFactory = new CatsFactory(catsModel);
    await app.init();
    await catsFactory.createMany(cats);
  }
  // clean up the application after the benchmark
  async function afterAll() {
    await catsModel.deleteMany();
    await app.close();
  }

  bench.add(
    "GET /cats/name/:name",
    async () => {
      await request(app.getHttpServer()).get("/cats/name/river");
    },
    { beforeAll, afterAll }
  );

  bench.add(
    "GET /cats/breed/:breed",
    async () => {
      await request(app.getHttpServer()).get("/cats/breed/chausie");
    },
    { beforeAll, afterAll }
  );
}
```

Here we have defined 4 benchmarks for the `cats` endpoints:

* `GET /cats`: retrieve all the cats
* `GET /cats/name/:name`: retrieve all the cats with the given name
* `GET /cats/breed/:breed`: retrieve all the cats with the given breed
* `GET /cats/age/greater/:age`: retrieve all the cats with an age greater than
  the given age

We finally have to register the benchmarks in the `src/bench.e2e.ts` file:

```typescript src/bench.e2e.ts theme={null}
import { withCodSpeed } from "@codspeed/tinybench-plugin";
import { registerCatControllerBenches } from "cats/cats.controller.e2e.bench";
import { Bench } from "tinybench";

const bench = withCodSpeed(new Bench());

(async () => {
  registerCatControllerBenches(bench);

  await bench.run();
  console.table(bench.table());
})();
```

### Setup Docker locally

Add the following file to the root of the project:

```yaml docker-compose.yml theme={null}
version: "3"

services:
  mongodb:
    image: mongo:latest
    environment:
      - MONGODB_DATABASE="test"
    ports:
      - 27017:27017
    volumes:
      - mongo:/data/db

volumes:
  mongo:
```

Run the following command to start the MongoDB instance:

```bash theme={null}
docker-compose up -d
```

### Run the benchmarks locally

To use `tinybench`, we recommend using `ts-node` with `swc`:

<NpmInstall packages="@swc/core @swc/helpers ts-node" />

To enforce using `swc` when running `ts-node`, add the following to your
`tsconfig.json`:

```js tsconfig.json theme={null}
{
  "ts-node": {
    "swc": true
  }
}
```

Add the following script to your `package.json`:

```js package.json theme={null}
{
  "scripts": {
    "bench:e2e": "NODE_ENV=test ts-node --swc -r tsconfig-paths/register src/bench.e2e.ts"
  }
}
```

Run the following command to run the benchmarks:

```shellsession title=terminal icon="square-terminal" theme={null}
$ pnpm bench:e2e

┌─────────┬──────────────────────────────┬─────────┬────────────────────┬──────────┬─────────┐
│ (index) │          Task Name           │ ops/sec │ Average Time (ns)  │  Margin  │ Samples │
├─────────┼──────────────────────────────┼─────────┼────────────────────┼──────────┼─────────┤
│    0    │    'GET /cats/name/:name'    │  '260'  │ 3840144.435868008  │ '±9.38%' │   131   │
│    1    │         'GET /cats'          │  '348'  │ 2870076.392037528  │ '±4.93%' │   175   │
│    2    │   'GET /cats/breed/:breed'   │  '489'  │ 2043167.1677803504 │ '±3.39%' │   245   │
│    3    │ 'GET /cats/age/greater/:age' │  '431'  │ 2318777.595405225  │ '±3.97%' │   216   │
└─────────┴──────────────────────────────┴─────────┴────────────────────┴──────────┴─────────┘
```

### Run the benchmarks in the CI

Add the following file to the project:

```yaml .github/workflows/codspeed.yml {23-25,30-42} theme={null}
name: CodSpeed

on:
  push:
    branches:
      - "main" # or "master"
  pull_request: # required to have reports on PRs
  # `workflow_dispatch` allows CodSpeed to trigger backtest
  # performance analysis in order to generate initial data.
  workflow_dispatch:

jobs:
  benchmarks:
    name: Run benchmarks
    runs-on: ubuntu-latest
    permissions: # optional for public repositories
      contents: read # required for actions/checkout
      id-token: write # required for OIDC authentication with CodSpeed
    steps:
      - uses: actions/checkout@v5
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v6
        with:
          cache: pnpm
          node-version-file: .nvmrc

      # easily setup a MongoDB cluster
      - uses: art049/mongodb-cluster-action@v0
        id: mongodb-cluster-action

      - name: Install dependencies
        run: pnpm install

      - name: Run benchmarks
        uses: CodSpeedHQ/action@v4
        with:
          mode: simulation
          instruments: mongodb
          mongo-uri-env-name: MONGO_URL
          run: |
            pnpm bench:e2e
        env:
          # we need the MONGO_URL to be set in the environment before actually running
          # the benchmark command so we set it here instead of inside the `run` command
          MONGO_URI: ${{ steps.mongodb-cluster-action.outputs.connection-string }}
```

With this configuration, the CodSpeed MongoDB instrument will be activated and
data from MongoDB queries will be sent to CodSpeed.

## Setup using testcontainers

Instead of relying on an externally provided Docker instance, we can leverage
[`testcontainers`](https://node.testcontainers.org/modules/mongodb/) to start a
MongoDB instance dynamically during the benchmarks.

For this setup, we assume that the state of the application is similar to the
one described in the above section.

### Setup tinybench + testcontainers

Install the `testcontainers` dependencies:

<NpmInstall packages="@testcontainers/mongodb" />

Change the `src/bench.e2e.ts` file to the following:

```typescript src/bench.e2e.ts theme={null}
import { setupInstruments, withCodSpeed } from "@codspeed/tinybench-plugin";
import { MongoDBContainer } from "@testcontainers/mongodb";
import { registerCatControllerBenches } from "cats/cats.controller.tinybench";
import { Bench } from "tinybench";

async function setupDatabase() {
  const mongodbContainer = await new MongoDBContainer("mongo:7.0.5").start();
  const mongoUrl =
    mongodbContainer.getConnectionString() +
    "/test?replicaSet=rs0&directConnection=true";

  const { remoteAddr } = await setupInstruments({ mongoUrl });
  process.env.MONGO_URL = remoteAddr;
}

const bench = withCodSpeed(new Bench());

(async () => {
  await setupDatabase();

  registerCatControllerBenches(bench);

  await bench.run();
  console.table(bench.table());
})();
```

<Info title="`testcontainers` on macOS">
  On macOS, we recommend using [`colima`](https://github.com/abiosoft/colima) to
  run Docker containers. However there are
  [issues using `testcontainers` on macOS](https://node.testcontainers.org/supported-container-runtimes/#colima).

  To bypass those issues, some environment variables need to be set when running
  the tests:

  <Accordion title="Enforce a check that the correct environment variables are set">
    To make `testcontainers` work on macOS with `colima`, the following environment variables need to be set:

    ```bash theme={null}
    TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock NODE_OPTIONS="$NODE_OPTIONS --dns-result-order=ipv4first" <command>
    ```

    We will add a function to enforce that they are set when running `tinybench`. Add the following function to your `src/bench.e2e.ts` file:

    ```typescript src/bench.e2e.ts theme={null}
    function checkColimaTestcontainersDarwin() {
      if (
        process.platform === "darwin" &&
        (process.env.TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE === undefined ||
          !process.env.NODE_OPTIONS.includes("--dns-result-order=ipv4first"))
      ) {
        throw new Error(
          'On macOs, run with the following command to make testcontainers + colima work: `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock NODE_OPTIONS="$NODE_OPTIONS --dns-result-order=ipv4first" <command>`'
        );
      }
    }
    ```

    And use it at the top of the `setupDatabase` function:

    ```typescript src/bench.e2e.ts {2} theme={null}
    async function setupDatabase() {
      checkColimaTestcontainersDarwin();

      await setupMongoDB();
    }
    ```

    Now the execution will stop with an explicit error message if the environment variables are not set when running on macOS.
  </Accordion>
</Info>

### Run the benchmarks locally

You can now run the benchmarks locally without having to start a MongoDB
instance:

```bash theme={null}
pnpm bench:e2e
```

### Run the benchmarks in the CI

You can now simplify the `codspeed.yml` file to the following:

* Remove the `mongodb-cluster-action` step
* Remove the `mongo-uri-env-name` input
* Remove the `MONGO_URI` environment variable

```yaml .github/workflows/codspeed.yml {26-32} theme={null}
name: CodSpeed

on:
  push:
    branches:
      - "main" # or "master"
  pull_request: # required to have reports on PRs
  # `workflow_dispatch` allows CodSpeed to trigger backtest
  # performance analysis in order to generate initial data.
  workflow_dispatch:

jobs:
  benchmarks:
    name: Run benchmarks
    runs-on: ubuntu-latest
    permissions: # optional for public repositories
      contents: read # required for actions/checkout
      id-token: write # required for OIDC authentication with CodSpeed
    steps:
      - uses: actions/checkout@v5
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v6
        with:
          cache: pnpm
          node-version-file: .nvmrc

      - name: Install dependencies
        run: pnpm install

      - name: Run benchmarks
        uses: CodSpeedHQ/action@v4
        with:
          mode: simulation
          instruments: mongodb
          run: pnpm bench:e2e
```
