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@alpha

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 { ProfilerService } from '@eleven-labs/nest-profiler';

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

  async findAll(): Promise<ReviewDocument[]> {
    const stop = this.profiler.startSpan('mongo.reviews.findAll');
    const result = await this.model.find().sort({ createdAt: -1 }).exec();
    stop();
    return result;
  }

  async getStats() {
    const stop = this.profiler.startSpan('mongo.reviews.aggregate');
    const result = await this.model
      .aggregate([
        { $match: { status: 'approved' } },
        { $group: { _id: '$productId', avgRating: { $avg: '$rating' } } },
      ])
      .exec();
    stop();
    return result;
  }
}

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 for find queries

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 Schema · Mongoose panel 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 expand the Schema · Mongoose panel:

Schema panel — Mongoose models with their fields, types, _id and defaults

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