The gate guard doesn't just check who enters — sometimes it must also dress the visitor in the appropriate toga or take away unnecessary items. This is exactly what class-transformer does — a library for controlling how data is transformed between different formats.
class-transformer offers two main transformation directions:
1// plain object → class instance
2const legionary = plainToInstance(CreateLegionaryDto, requestBody);
3
4// class instance → plain object
5const plainObject = instanceToPlain(legionary);The most commonly used decorator. It hides a field when converting an object to a JSON response. It's like a guard withholding confidential documents:
1import { Exclude } from 'class-transformer';
2
3export class LegionaryResponseDto {
4 name: string;
5 rank: string;
6
7 @Exclude()
8 secretMissionCode: string; // This field will NOT appear in the response
9
10 @Exclude()
11 salary: number; // Pay is also classified
12}For this to work, the controller must use serialization:
1import { UseInterceptors, ClassSerializerInterceptor } from '@nestjs/common';
2
3@UseInterceptors(ClassSerializerInterceptor)
4@Controller('legionaries')
5export class LegionaryController {
6 @Get(':id')
7 findOne(@Param('id') id: string) {
8 // secretMissionCode and salary will be hidden
9 return this.service.findOne(id);
10 }
11}The opposite of
@Exclude(). When you use the excludeAll strategy, only fields with @Expose() will be visible:1import { Exclude, Expose } from 'class-transformer';
2
3@Exclude()
4export class LegionaryResponseDto {
5 @Expose()
6 name: string; // Visible
7
8 @Expose()
9 rank: string; // Visible
10
11 salary: number; // HIDDEN (because the entire class has @Exclude)
12 secretCode: string; // HIDDEN
13}Allows you to define custom transformation logic for a field:
1import { Transform } from 'class-transformer';
2
3export class CreateLegionaryDto {
4 @Transform(({ value }) => value.trim().toLowerCase())
5 name: string;
6 // " MARCUS " → "marcus"
7
8 @Transform(({ value }) => new Date(value))
9 enlistmentDate: Date;
10
11 @Transform(({ value }) => Math.round(value))
12 experienceYears: number;
13}When you have nested objects,
@Type() tells class-transformer which class to convert to:1import { Type } from 'class-transformer';
2
3class WeaponDto {
4 name: string;
5 damage: number;
6}
7
8export class LegionaryDto {
9 name: string;
10
11 @Type(() => WeaponDto)
12 weapon: WeaponDto;
13 // { name: "Gladius", damage: 50 } → WeaponDto instance
14}These functions allow you to manually transform data:
1import { plainToInstance, instanceToPlain } from 'class-transformer';
2
3// From a plain object to a class instance
4const rawData = { name: 'Marcus', rank: 'centurio' };
5const legionary = plainToInstance(CreateLegionaryDto, rawData);
6// Now legionary is a class instance with validation
7
8// From a class instance to a plain object
9const plain = instanceToPlain(legionary);
10// Fields with @Exclude() will be removed1import { Exclude, Expose, Transform, Type } from 'class-transformer';
2import { IsString, IsNumber } from 'class-validator';
3
4export class CreateLegionaryDto {
5 @IsString()
6 @Transform(({ value }) => value.trim())
7 name: string;
8
9 @IsString()
10 rank: string;
11
12 @IsNumber()
13 age: number;
14}
15
16export class LegionaryResponseDto {
17 @Expose()
18 name: string;
19
20 @Expose()
21 rank: string;
22
23 @Expose()
24 @Transform(({ value }) =>
25 value > 10 ? 'Veteranus' : 'Tiro'
26 )
27 experienceLevel: string;
28
29 @Exclude()
30 internalCode: string;
31}class-transformer is a powerful tool that gives you full control over how data enters and leaves your API. Combined with class-validator, you create an impenetrable gate defense!