typescript
55 lines · 9 steps
How validation groups reuse one DTO in NestJS
A single UserDto validates differently for create and update by tagging each rule with a group.
Explained by
highlit
1import {
2 IsEmail,
3 IsNotEmpty,
4 IsOptional,
5 IsString,
6 IsUUID,
7 MinLength,
8} from 'class-validator';
9
10export const CREATE = 'create';
11export const UPDATE = 'update';
12
13export class UserDto {
14 @IsUUID('4', { groups: [UPDATE] })
15 @IsNotEmpty({ groups: [UPDATE] })
16 id?: string;
17
18 @IsEmail({}, { groups: [CREATE, UPDATE] })
19 @IsNotEmpty({ groups: [CREATE] })
20 @IsOptional({ groups: [UPDATE] })
21 email!: string;
22
23 @IsString({ groups: [CREATE, UPDATE] })
24 @MinLength(2, { groups: [CREATE, UPDATE] })
25 @IsNotEmpty({ groups: [CREATE] })
26 @IsOptional({ groups: [UPDATE] })
27 displayName!: string;
28
29 @IsString({ groups: [CREATE] })
30 @MinLength(8, { groups: [CREATE] })
31 @IsNotEmpty({ groups: [CREATE] })
32 password!: string;
33}
34
35@Controller('users')
36export class UsersController {
37 constructor(private readonly users: UsersService) {}
38
39 @Post()
40 create(
41 @Body(new ValidationPipe({ groups: [CREATE], whitelist: true }))
42 dto: UserDto,
43 ) {
44 return this.users.create(dto);
45 }
46
47 @Patch(':id')
48 update(
49 @Param('id', ParseUUIDPipe) id: string,
50 @Body(new ValidationPipe({ groups: [UPDATE], whitelist: true }))
51 dto: UserDto,
52 ) {
53 return this.users.update(id, dto);
54 }
55}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Validation groups let one DTO enforce different rule sets per operation without duplicating classes.
- 2Naming groups as shared constants keeps the DTO decorators and the controller pipes in sync.
- 3A ValidationPipe scoped to a group only runs the decorators tagged with that group, so the same field can be required in one flow and optional in another.
Related explainers
typescript
type RGB = { r: number; g: number; b: number }; function parseHex(hex: string): RGB { const normalized = hex.replace(/^#/, "").trim();
Lightening and darkening hex colors in TypeScript
bitwise
color-manipulation
parsing
Intermediate
9 steps
php
<?php namespace App\Http\Controllers;
Building a multi-step onboarding wizard in Laravel
session-state
validation
multi-step-forms
Intermediate
9 steps
typescript
interface RunningStats { count: number; total: number; average: number;
Streaming running averages in TypeScript
generators
streaming
incremental-aggregation
Intermediate
6 steps
typescript
import { InjectionToken, inject, Provider, isDevMode } from '@angular/core'; import { WINDOW } from './window.token'; export interface AnalyticsConfig {
Layered config with an Angular InjectionToken
dependency-injection
configuration
factory-provider
Intermediate
8 steps
javascript
const IBAN_LENGTHS = { DE: 22, FR: 27, GB: 22, ES: 24, IT: 27, NL: 18, BE: 16, CH: 21, AT: 20, PT: 25, };
How IBAN validation works in JavaScript
validation
checksum
modular-arithmetic
Intermediate
8 steps
typescript
import { useCallback, useRef, useState } from "react"; type UploadZoneProps = { accept?: string[];
A drag-and-drop file upload zone in React
drag-and-drop
file-validation
controlled-state
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-validation-groups-reuse-one-dto-in-nestjs-explained-typescript-57fd/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.