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-profilerinstalled and configured@nestjs/mongooseandmongooseinstalled with a workingMongooseModuleconnection
Step 1 - Install the package
pnpm add @eleven-labs/nest-profiler-mongoose@alphaStep 2 - Register the collector
Add MongooseCollectorModule in the feature module that performs MongoDB operations:
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/reviewsCopy 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:
import { MongooseSchemaCollectorModule } from '@eleven-labs/nest-profiler-mongoose';
ConditionalModule.registerWhen(MongooseSchemaCollectorModule.forRoot(), isProfilerEnabled),Open /_profiler and expand the Schema · Mongoose panel:

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.