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-mongooseStep 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 { 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/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 — documents returned by a read, matched by a count, or affected by a write
- The returned documents themselves, in a collapsible Result block, when
captureResultis 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:
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.
SQL query profiling (TypeORM & MikroORM)
Capture every SQL query issued by TypeORM or MikroORM and inspect duration, slow queries and execution plans in the profiler's Database panel.
HTTP client profiling
Capture outgoing HTTP requests and inspect them in the HTTP Client panel - with the axios and fetch adapters or any client of your own.