nest-profiler-validator
Inspect DTO validation violations in the Validator panel.
@eleven-labs/nest-profiler-validator
@eleven-labs/nest-profiler-validator captures every DTO validation result (valid or invalid) and displays it in a dedicated Validator panel, inspired by Symfony's Web Profiler validator tab.
It is validator-agnostic: instead of being tied to class-validator, it wraps any validation PipeTransform and normalizes failures through pluggable, duck-typed extractors. Built-in extractors cover class-validator, nestjs-zod, and a generic HttpException fallback.

Installation
pnpm add @eleven-labs/nest-profiler-validator@alphaThere is no stable release yet — install every
@eleven-labs/nest-profiler*package with the@alphadist-tag (@latestresolves to nothing).
Then install the validator you use:
# class-validator (default)
pnpm add class-validator class-transformer
# …or nestjs-zod
pnpm add nestjs-zod zodclass-validator/class-transformer are not peer dependencies — they are only required when you rely on the default class-validator pipe.
Setup
Own the validation pipe in your bootstrap with createProfilerValidationPipe(), and register the panel with ValidatorCollectorModule.forRoot(). Validation runs independently of the profiler, so the panel can be gated like every other collector while validation always runs.
With class-validator (default)
import {
createProfilerValidationPipe,
createClassValidatorPipe,
} from '@eleven-labs/nest-profiler-validator';
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
createProfilerValidationPipe(createClassValidatorPipe({ whitelist: true, transform: true })),
);Wrap createClassValidatorPipe (rather than a bare new ValidationPipe()) so the raw ValidationError[] reaches the panel and violations show per property.
import { ConditionalModule } from '@nestjs/config';
import { ValidatorCollectorModule } from '@eleven-labs/nest-profiler-validator';
const isProfilerEnabled = (env: NodeJS.ProcessEnv) => env['PROFILER_ENABLED'] === 'true';
@Module({
imports: [ConditionalModule.registerWhen(ValidatorCollectorModule.forRoot(), isProfilerEnabled)],
})
export class AppModule {}With nestjs-zod
Pass your own pipe; class-validator is never loaded:
import { ZodValidationPipe } from 'nestjs-zod';
app.useGlobalPipes(createProfilerValidationPipe(new ZodValidationPipe()));A NestJS app uses a single global validation strategy, so use one validator at a time.
createProfilerValidationPipe(inner, extractors?)also accepts a custom extractor chain as its second argument.
The pipe writes outcomes to CLS; the gated panel reads them only when the profiler is on. When the profiler is off the pipe validates and records nothing (transparent pass-through).
e2e / manual bootstrap — when you boot the app yourself in tests (
Test.createTestingModule(...).createNestApplication()), mirror thisuseGlobalPipes(...)call there too, since it lives inmain.tsrather than a module.
The extractor chain ([classValidator, zod, generic]) rarely needs changing; pass a custom one as the second argument of createProfilerValidationPipe(inner, extractors).
Enabling / disabling — gate the panel with
ConditionalModule.registerWhen(..., isProfilerEnabled)as shown, so it loads only whenPROFILER_ENABLEDis on (a top-levelenabledoption is also supported). Wire the coreProfilerModuleonce at the root — the recommended setup bundles the root-level profiler modules into a singleProfilingModulebehind aConditionalModulegate (see Enabling and disabling the profiler and the example app).
Prerequisite: value import for DTO types
For reflect-metadata to emit the DTO class constructor as parameter metadata, use a value import (not import type) on the DTO in your controllers:
// ✓ value import — emits reflect-metadata
import { CreateProductDto } from './dto/create-product.dto';
// ✗ type-only import — metadata is erased, metatype shows as 'Function'
import type { CreateProductDto } from './dto/create-product.dto';What it captures
For each @Body(), @Query(), or @Param() parameter using a DTO class:
| Field | Description |
|---|---|
source | body, query, param, or custom |
dtoClass | DTO class name (e.g., CreateProductDto) |
status | valid or invalid |
violationCount | Total number of constraint violations |
violations | Per-property breakdown with constraint names and messages |
Each violation entry includes:
property— the property path that failed (nested properties use dot notation)value— the rejected value (when available)constraints— map of constraint name → message (e.g.,{ isNotEmpty: "name should not be empty" })
How it works
ProfilerValidationPipe implements PipeTransform and wraps an inner pipe:
- On
transform(), it delegates to the inner pipe. On success it records avalidentry. - On failure it runs the configured extractors over the thrown error, records an
invalidentry with the normalized violations, then re-throws the original exception.
Extractors are tried in order; the first to recognize the error wins:
- class-validator —
createClassValidatorPipe()attaches the rawValidationError[]to the thrown exception (under a private symbol) so the full property/constraint tree is recovered. - nestjs-zod / zod — reads
ZodError.issues(viagetZodError()or a bareZodError). - generic — any
HttpExceptionexposing amessagestring/array (the universal fallback).
Reading the active profile uses CLS, so capture is concurrent-safe across requests.
Custom extractors
To support another validator, implement ValidationViolationExtractor and pass it via extractors:
import type { ValidationViolationExtractor } from '@eleven-labs/nest-profiler-validator';
const myExtractor: ValidationViolationExtractor = {
extract({ error }) {
// return ViolationEntry[] if recognized, otherwise null to defer to the next extractor
return null;
},
};
app.useGlobalPipes(createProfilerValidationPipe(myPipe, [myExtractor]));Toolbar badge
- All valid: number of validated DTOs (e.g.,
1) - With violations: total violation count (e.g.,
3 violations)