Straż bramy nie tylko sprawdza, kto wchodzi — czasem musi też przebrać przybysza w odpowiednią togę lub zabrać mu zbędne przedmioty. Dokładnie tym zajmuje się class-transformer — biblioteką do kontrolowania, jak dane są transformowane między różnymi formatami.
class-transformer oferuje dwa główne kierunki transformacji:
1// plain object → class instance
2const legionary = plainToInstance(CreateLegionaryDto, requestBody);
3
4// class instance → plain object
5const plainObject = instanceToPlain(legionary);Najczęściej używany dekorator. Ukrywa pole przy konwersji obiektu na odpowiedź JSON. To jak strażnik, który zatrzymuje poufne dokumenty:
1import { Exclude } from 'class-transformer';
2
3export class LegionaryResponseDto {
4 name: string;
5 rank: string;
6
7 @Exclude()
8 secretMissionCode: string; // To pole NIE pojawi się w odpowiedzi
9
10 @Exclude()
11 salary: number; // Żołd też jest tajny
12}Żeby to działało, kontroler musi używać serializacji:
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 i salary zostaną ukryte
9 return this.service.findOne(id);
10 }
11}Odwrotność
@Exclude(). Gdy użyjesz strategii excludeAll, tylko pola z @Expose() będą widoczne:1import { Exclude, Expose } from 'class-transformer';
2
3@Exclude()
4export class LegionaryResponseDto {
5 @Expose()
6 name: string; // Widoczne
7
8 @Expose()
9 rank: string; // Widoczne
10
11 salary: number; // UKRYTE (bo cała klasa ma @Exclude)
12 secretCode: string; // UKRYTE
13}Pozwala zdefiniować własną logikę transformacji pola:
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}Gdy masz zagnieżdżone obiekty,
@Type() mówi class-transformer, na jaką klasę skonwertować: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 } → instancja WeaponDto
14}Te funkcje pozwalają ręcznie transformować dane:
1import { plainToInstance, instanceToPlain } from 'class-transformer';
2
3// Z zwykłego obiektu na instancję klasy
4const rawData = { name: 'Marcus', rank: 'centurio' };
5const legionary = plainToInstance(CreateLegionaryDto, rawData);
6// Teraz legionary jest instancją klasy z walidacją
7
8// Z instancji klasy na zwykły obiekt
9const plain = instanceToPlain(legionary);
10// Pola z @Exclude() zostaną usunięte1import { 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 ? 'Weteran' : 'Rekrut'
26 )
27 experienceLevel: string;
28
29 @Exclude()
30 internalCode: string;
31}class-transformer to potężne narzędzie, które daje ci pełną kontrolę nad tym, jak dane wchodzą i wychodzą z twojego API. W połączeniu z class-validator tworzysz nieprzeniknioną ochronę bramy!