Getting started
Install and configure @eleven-labs/nest-profiler in a NestJS application.
Requirements
- Node.js 22 or newer
- A NestJS 11 application
- Any package manager (the examples below use
pnpm;npmandyarnwork too)
Installation
pnpm add @eleven-labs/nest-profiler nestjs-clsThe core package declares @nestjs/common, @nestjs/core, nestjs-cls, reflect-metadata, and rxjs as peer dependencies — a NestJS application already provides all of them except nestjs-cls, which is why it is installed explicitly above.
Configure the core module
import { Module } from '@nestjs/common';
import { ProfilerModule } from '@eleven-labs/nest-profiler';
@Module({
imports: [
ProfilerModule.forRoot({
isGlobal: true,
enabled: process.env.NODE_ENV !== 'production',
}),
],
})
export class AppModule {}Async configuration
Use forRootAsync when your options depend on injected providers such as ConfigService. One field is different: enabled stays at the top level, outside useFactory. It decides whether the profiler's providers are registered at all, so it must be known synchronously at bootstrap — it cannot be resolved from the async factory.
ProfilerModule.forRootAsync({
// Static bootstrap flag — evaluated before the factory runs.
enabled: process.env.NODE_ENV !== 'production',
useFactory: (config: ConfigService) => ({
storageType: config.get('PROFILER_STORAGE_TYPE', 'memory'),
}),
inject: [ConfigService],
});Enable log capture
Wrap the NestJS logger in main.ts so that every log entry is captured in the active request profile:
import { ConsoleLogger } from '@nestjs/common';
import { ProfilerService } from '@eleven-labs/nest-profiler';
const app = await NestFactory.create(AppModule, { bufferLogs: true });
const profilerService = app.get(ProfilerService);
app.useLogger(profilerService.createLogger(new ConsoleLogger('MyApp')));
await app.listen(3000);Optional collector packages
Each collector is a separate package installed alongside the core. Import each one in the feature module it instruments — not in the root module — so the dependency is co-located with the code it observes.
| Package | Panel | Prerequisite |
|---|---|---|
@eleven-labs/nest-profiler-typeorm | Database | TypeOrmModule.forRoot() |
@eleven-labs/nest-profiler-mikro-orm | Database | MikroOrmModule.forRoot() |
@eleven-labs/nest-profiler-mongoose | MongoDB | MongooseModule.forRoot() connection |
@eleven-labs/nest-profiler-axios | HTTP Client | HttpModule in the same module |
@eleven-labs/nest-profiler-cache | Cache | CacheModule.register({ isGlobal: true }) |
@eleven-labs/nest-profiler-auth | Security | Guard or middleware that sets request.user |
@eleven-labs/nest-profiler-config | Config | ConfigModule.forRoot({ load: [...] }) with registerAs factories |
@eleven-labs/nest-profiler-validator | Validator | A validator: class-validator (default) or another (e.g. nestjs-zod) |
@eleven-labs/nest-profiler-graphql | GraphQL | @nestjs/graphql + a driver + context factory configured |
@eleven-labs/nest-profiler-commander | Command | nest-commander + a CLI bootstrap (CommandFactory) |
Module-per-collector pattern
Each optional collector is registered in the feature module it instruments:
import { TypeOrmCollectorModule } from '@eleven-labs/nest-profiler-typeorm';
@Module({
imports: [
TypeOrmModule.forFeature([Product]),
TypeOrmCollectorModule.forRoot({ slowQueryThreshold: 50 }),
],
})
export class ProductsModule {}import { MikroOrmCollectorModule } from '@eleven-labs/nest-profiler-mikro-orm';
@Module({
imports: [
MikroOrmModule.forFeature([Product]),
MikroOrmCollectorModule.forRoot({ slowQueryThreshold: 50 }),
],
})
export class ProductsModule {}import { HttpModule } from '@nestjs/axios';
import { AxiosCollectorModule } from '@eleven-labs/nest-profiler-axios';
import { CacheCollectorModule } from '@eleven-labs/nest-profiler-cache';
@Module({
imports: [
HttpModule, // prerequisite for AxiosCollectorModule
AxiosCollectorModule.forRoot(),
CacheCollectorModule.forRoot(),
],
})
export class PostsModule {}import { AuthCollectorModule } from '@eleven-labs/nest-profiler-auth';
@Module({
imports: [AuthCollectorModule.forRoot({ maskUserFields: ['password'] })],
})
export class AuthModule {}Collectors that install a global APP_PIPE or read global providers should remain in the root module:
import { ConfigCollectorModule } from '@eleven-labs/nest-profiler-config';
import { ValidatorCollectorModule } from '@eleven-labs/nest-profiler-validator';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, load: [appConfig, dbConfig] }),
CacheModule.register({ isGlobal: true }),
ProfilerModule.forRoot({ isGlobal: true, enabled: process.env.NODE_ENV !== 'production' }),
ConfigCollectorModule.forRoot({
enabled: process.env.NODE_ENV !== 'production',
maskKeys: ['database.password'],
}),
ValidatorCollectorModule.forRoot({
enabled: process.env.NODE_ENV !== 'production',
validationPipeOptions: { whitelist: true, transform: true },
}),
// feature modules:
ProductsModule,
PostsModule,
AuthModule,
],
})
export class AppModule {}GraphQL support
GraphQL profiling is opt-in via the dedicated @eleven-labs/nest-profiler-graphql package. The core ProfilerModule is HTTP-only — without this package, GraphQL requests pass through without profiling and without errors.
pnpm add @eleven-labs/nest-profiler-graphqlImport ProfilerGraphQLModule alongside ProfilerModule, then configure the context factory for your driver so the profiler can access the underlying HTTP request:
import { ProfilerGraphQLModule } from '@eleven-labs/nest-profiler-graphql';
@Module({
imports: [
ProfilerModule.forRoot({ isGlobal: true }),
ProfilerGraphQLModule.forRoot(),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
context: ({ req }) => ({ req }), // required — exposes the request to the profiler
}),
],
})
export class AppModule {}GraphQLModule.forRoot<MercuriusDriverConfig>({
driver: MercuriusDriver,
autoSchemaFile: true,
context: ({ request }) => ({ request }), // Mercurius uses `request` instead of `req`
});Without the context factory, the profiler silently falls back to passthrough for GraphQL resolvers. Subscriptions (WebSocket transport) are not profiled at this time.
Each captured GraphQL request appears in /_profiler with a GQL badge and the operation type. The Request tab shows the operation type, operation name, field name, syntax-highlighted query, and variables.
Custom protocol adapters
ProfilerModule exports an IContextAdapter interface and a PROFILER_CONTEXT_ADAPTERS multi-token that let you profile any NestJS execution context beyond HTTP — gRPC, Kafka, WebSockets, and more. @eleven-labs/nest-profiler-graphql is the reference implementation. See the nest-profiler package guide for a full adapter example.
Storage backends
// Default — in-memory LRU, cleared on restart
ProfilerModule.forRoot({ storageType: 'memory', maxProfiles: 100 });
// File — persists to .profiler/ as JSON files, survives restarts
ProfilerModule.forRoot({ storageType: 'file', storagePath: '.profiler' });Add .profiler/ to .gitignore when using file storage.
Test with the example application
pnpm example:devMake requests to exercise each collector:
curl http://localhost:3000/products # TypeORM SELECT (auto-seeded at startup)
curl http://localhost:3000/posts # Axios GET + cache SET
curl http://localhost:3000/posts # cache HIT
curl http://localhost:3000/auth/token # get demo JWT
curl -H "Authorization: Bearer <token>" http://localhost:3000/auth/me # Auth
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-d '{"title":"Hi","body":"too short"}' # Validator violations
curl http://localhost:3000/slow # Timeline spans
# GraphQL — requires FEATURE_GRAPHQL=true (default)
# Query with operation name
curl -X POST http://localhost:3000/graphql \
-H "Content-Type: application/json" \
-d '{
"operationName": "GetBooks",
"query": "query GetBooks { books { id title author publishedYear } }"
}'
# Query with variable
curl -X POST http://localhost:3000/graphql \
-H "Content-Type: application/json" \
-d '{
"operationName": "GetBook",
"query": "query GetBook($id: ID!) { book(id: $id) { id title author } }",
"variables": { "id": "1" }
}'
# Mutation with operation name and variables
curl -X POST http://localhost:3000/graphql \
-H "Content-Type: application/json" \
-d '{
"operationName": "CreateBook",
"query": "mutation CreateBook($title: String!, $author: String!, $publishedYear: Int) { createBook(input: { title: $title, author: $author, publishedYear: $publishedYear }) { id title author } }",
"variables": { "title": "NestJS in Action", "author": "John Doe", "publishedYear": 2024 }
}'Open http://localhost:3000/_profiler to browse all collected profiles.