NestJS Profiler
Tutorials

MongoDB query profiling

Capture every Mongoose query and aggregation and inspect collection, duration and result count in the profiler's MongoDB panel.

This tutorial shows how to add the Mongoose collector to profile MongoDB queries in a NestJS application that uses @nestjs/mongoose.

Prerequisites

  • @eleven-labs/nest-profiler installed and configured
  • @nestjs/mongoose and mongoose installed with a working MongooseModule connection

Step 1 - Install the package

pnpm add @eleven-labs/nest-profiler-mongoose

Step 2 - Register the collector

Add MongooseCollectorModule in the feature module that performs MongoDB operations:

reviews/reviews.module.ts
import { ConditionalModule } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { MongooseCollectorModule } from '@eleven-labs/nest-profiler-mongoose';

const isProfilerEnabled = (env: NodeJS.ProcessEnv) => env['PROFILER_ENABLED'] === 'true';

@Module({
  imports: [
    MongooseModule.forFeature([{ name: Review.name, schema: ReviewSchema }]),
    ConditionalModule.registerWhen(
      MongooseCollectorModule.forRoot({ slowThreshold: 50 }), // queries > 50ms highlighted
      isProfilerEnabled,
    ),
  ],
})
export class ReviewsModule {}

The collector injects the Connection automatically via @InjectConnection().

Step 3 - Instrument your services with spans

import { TracerService } from '@eleven-labs/nest-profiler';

@Injectable()
export class ReviewsService {
  constructor(
    @InjectModel(Review.name) private readonly model: Model<ReviewDocument>,
    private readonly tracer: TracerService,
  ) {}

  async findAll(): Promise<ReviewDocument[]> {
    return this.tracer.span('mongo.reviews.findAll', () =>
      this.model.find().sort({ createdAt: -1 }).exec(),
    );
  }

  async getStats() {
    return this.tracer.span('mongo.reviews.aggregate', () =>
      this.model
        .aggregate([
          { $match: { status: 'approved' } },
          { $group: { _id: '$productId', avgRating: { $avg: '$rating' } } },
        ])
        .exec(),
    );
  }
}

Step 4 - Test it

curl -i http://localhost:3000/api/v1/reviews

Copy the X-Debug-Token header, open /_profiler/{token}, and click the MongoDB tab.

You will see:

  • Each query with its operation badge (find, aggregate, …)
  • Collection name
  • Filter object
  • Duration per query
  • Slow queries and N+1 patterns flagged as performance tags
  • Result count — documents returned by a read, matched by a count, or affected by a write
  • The returned documents themselves, in a collapsible Result block, when captureResult is enabled

Driving options from ConfigService

Use forRootAsync() to resolve the options from ConfigService (or any provider), gated per environment with ConditionalModule — the recommended way (for the enabled flag, see Enabling and disabling the profiler):

import { ConditionalModule, ConfigService } from '@nestjs/config';

ConditionalModule.registerWhen(
  MongooseCollectorModule.forRootAsync({
    inject: [ConfigService],
    useFactory: (config: ConfigService) => ({
      slowThreshold: config.get<number>('PROFILER_SLOW_QUERY_MS') ?? 50,
    }),
  }),
  isProfilerEnabled,
);

How it works

The collector patches mongoose.Query.prototype.exec and mongoose.Aggregate.prototype.exec to record every query and aggregation into the active request profile. Streaming reads (Query.cursor() / Aggregate.cursor()) are captured too and flagged as streaming. See How it works on the package page for the full mechanism and the operations it captures.

Anti-double-patch guard

If multiple modules import MongooseCollectorModule.forRoot(), the patch is applied only once - a __profilerPatched flag prevents double-wrapping Query.prototype.exec.

Inspect the schema

Alongside the per-request MongoDB panel, the package ships MongooseSchemaCollectorModule — a global collector that adds a Schemas / Mongoose view to the profiler home page. It introspects the connection once at startup and lists every registered model with its fields, references and indexes, so you can recall a model's shape without leaving the profiler.

Register it next to the query collector, gated the same way:

app.module.ts
import { MongooseSchemaCollectorModule } from '@eleven-labs/nest-profiler-mongoose';

ConditionalModule.registerWhen(MongooseSchemaCollectorModule.forRoot(), isProfilerEnabled),

Open /_profiler and select the Schemas / Mongoose view — it has the same shape as every ORM's, one disclosure per model over a columns table (see the SQL walkthrough for a shot of it).

The panel is introspection-only (it never touches data), honours a connectionName option for named connections, redacts secrets embedded in field defaults, and simply does not appear when no Mongoose connection is wired.

Powered & maintained by

On this page