NestJS Profiler

DTO validation profiling

Inspect DTO validations — both successful and failed — in the Validator panel.

This tutorial shows how to add the validator collector to a NestJS application. The collector is validator-agnostic; this walkthrough uses class-validator (the default), and a nestjs-zod variant is shown below.

Prerequisites

  • @eleven-labs/nest-profiler installed and configured
  • A validation library. This tutorial uses class-validator + class-transformer, which are required only for the default class-validator pipe — they are not dependencies of @eleven-labs/nest-profiler-validator itself. For the nestjs-zod variant, install nestjs-zod + zod instead.

No separate pipe needed

ValidatorCollectorModule registers a global APP_PIPE internally. You do not need to call app.useGlobalPipes() or configure ValidationPipe yourself — the module handles it.

Step 1 — Install the package

pnpm add @eleven-labs/nest-profiler-validator
# this tutorial uses the default class-validator pipe:
pnpm add class-validator class-transformer

Step 2 — Register the module

app.module.ts
import { Module } from '@nestjs/common';
import { ProfilerModule } from '@eleven-labs/nest-profiler';
import { ValidatorCollectorModule } from '@eleven-labs/nest-profiler-validator';

@Module({
  imports: [ProfilerModule.forRoot({ isGlobal: true }), ValidatorCollectorModule.forRoot()],
})
export class AppModule {}

Step 3 — Create a DTO with validation constraints

Use value imports, not type imports

Always import decorator functions from class-validator as value imports (not import type). The decorators must be executed at runtime to register metadata. Using import type strips them at compile time.

dto/create-article.dto.ts
import { IsString, IsNotEmpty, MinLength, MaxLength, IsOptional, IsUrl } from 'class-validator';

export class CreateArticleDto {
  @IsString()
  @IsNotEmpty()
  @MinLength(5)
  @MaxLength(120)
  title: string;

  @IsString()
  @IsNotEmpty()
  @MinLength(20)
  body: string;

  @IsOptional()
  @IsUrl()
  coverImageUrl?: string;
}

Step 4 — Use the DTO in a controller

articles.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { CreateArticleDto } from './dto/create-article.dto';

@Controller('articles')
export class ArticlesController {
  @Post()
  create(@Body() dto: CreateArticleDto) {
    return { message: 'Article created', title: dto.title };
  }
}

Step 5 — Test it

Valid request:

curl -i -X POST http://localhost:3000/articles \
  -H "Content-Type: application/json" \
  -d '{"title": "My first article", "body": "This is the body of the article with enough content."}'

Invalid request:

curl -i -X POST http://localhost:3000/articles \
  -H "Content-Type: application/json" \
  -d '{"title": "Hi", "body": "Too short"}'

Open /_profiler/{token} and click the Validator tab.

For the valid request, you will see:

  • SourceArticlesController.create
  • DTO classCreateArticleDto
  • Statusvalid
  • Violations — none

For the invalid request, you will see:

  • SourceArticlesController.create
  • DTO classCreateArticleDto
  • Statusinvalid
  • Violations — one entry per failing property, listing the constraint names and messages

The toolbar badge shows the total count of validated DTOs, or N violations when at least one validation failed (e.g., 2 violations).

Capturing both valid and invalid validations

The collector records every validation that passes through the pipe — both successful and failed. This lets you:

  • Confirm that valid payloads pass all constraints
  • See exactly which constraints failed and on which properties
  • Debug validation logic without adding temporary logging

Even when validation fails and NestJS returns a 400 Bad Request, the profile is still created and the failed validation is recorded. Navigate to /_profiler to find the profile by timestamp.

Using a different validator (nestjs-zod)

The collector is validator-agnostic. To profile a nestjs-zod app instead, pass its pipe via the pipe option — class-validator is never loaded:

app.module.ts
import { ZodValidationPipe } from 'nestjs-zod';

@Module({
  imports: [
    ProfilerModule.forRoot({ isGlobal: true }),
    ValidatorCollectorModule.forRoot({ pipe: new ZodValidationPipe() }),
  ],
})
export class AppModule {}
dto/create-article.dto.ts
import { createZodDto } from 'nestjs-zod';
import { z } from 'zod';

const ArticleSchema = z.object({
  title: z.string().min(5).max(120),
  body: z.string().min(20),
  coverImageUrl: z.string().url().optional(),
});

export class CreateArticleDto extends createZodDto(ArticleSchema) {}

The Validator panel renders zod violations exactly the same way — one entry per failing property with its messages. A NestJS app uses a single global validation strategy, so use one validator at a time.

How it works

ValidatorCollectorModule registers ProfilerValidationPipe as a global APP_PIPE. Rather than validating itself, this pipe wraps the validation pipe you configure (a class-validator ValidationPipe by default, or any pipe you pass via pipe). After delegating, it writes an entry into the CLS profile for the current request — the DTO class name, handler source, and status. On failure it runs a chain of duck-typed extractors over the thrown error (class-validator, then zod, then a generic HttpException fallback) to normalize the violations, then re-throws the original exception. To support another validator, implement ValidationViolationExtractor and pass it via the extractors option.

Powered & maintained by

On this page