Uvodu u Nest.js “Pipe”
Pipes u NestJS-u su specijalne komponente koje se koriste za transformaciju i validaciju podataka pre nego što stignu do kontrolera ili metoda kontrolera. Pipes su korisni kada želimo da osiguramo da podaci koje primamo u API zahtevima ispunjavaju određene uslove ili da se na specifičan način transformišu.
Nest.js dolazi sa ugrađenim pipes koji se mogu koristiti za transformaciju i validaciju podataka. Ugrađeni pipe-ovi, pružaju brza i efikasna rešenja za osnovne zadatke kao što su validacija i transformacija. Evo nekih ugrađeni pipes:
- ValidationPipe: Automatska validacija podataka koristeći dekoratore.
- ParseIntPipe: Automatska konverzija stringa u broj.
- ParseBoolPipe: Automatska konverzija stringa u boolean.
- ParseArrayPipe: Automatska konverzija stringa u niz.
- ParseUUIDPipe: Automatska validacija UUID formata.
- DefaultValuePipe: Postavljanje podrazumevane vrednosti ako vrednost nije definisana.
Međutim, ako su vaši zahtevi specifični, možete kreirati “custom pipe-ove” koristeći interfejs “PipeTransform”.
ValidationPipe
Ako se “ValidationPipe” koriste za DTO validaciju onda je potrebno da instaliramo dodatne biblioteke (kod drugih ugrađenih pipe-ove to nije potrebno). Ovo su dve neophodne biblioteke:
- class-validator: Omogućava definisanje i proveru pravila validacije.
- class-transformer: Transformiše ulazne podatke u instance klasa DTO-a.
Koristite sledeću komandu za instalaciju:
1 |
npm install class-validator class-transformer |
Šta je “class-transformer”?
“class-transformer” je TypeScript biblioteka koja omogućava pretvaranje (transformaciju) plain JavaScript objekata u instance klasa i obrnuto. Obično su to jednostavni JavaScript objekti (dobijeni, na primer, iz JSON-a) u instance definisanih klasa. Takođe omogućava dodavanje logike za prilagođene transformacije i rad sa ugneždenim strukturama objekata.
Primer: JS objekt u instancu klase
1 2 3 4 5 6 7 8 9 10 11 |
import { plainToClass } from 'class-transformer'; class User { firstName: string; lastName: string; } const plainObject = { firstName: 'John', lastName: 'Doe' }; const user = plainToClass(User, plainObject); console.log(user instanceof User); // true |
Primer: klasa u plain JS objekat
1 2 3 4 5 6 7 8 |
import { classToPlain } from 'class-transformer'; const user = new User(); user.firstName = 'John'; user.lastName = 'Doe'; const plainObject = classToPlain(user); console.log(plainObject); // { firstName: 'John', lastName: 'Doe' } |
Šta je “class-validator”?
“class-validator” je TypeScript biblioteka koja omogućava definisanje i proveru pravila validacije za JavaScript objekte. Ova biblioteka koristi dekoratore za definisanje pravila validacije, kao što su provera tipova, dužine, minimalne i maksimalne vrednosti, obavezna polja, regularni izrazi i mnoga druga pravila.
Ovo su najčešće korišćeni:
- @IsString(): Proverava da li je vrednost string.
- @IsInt(): Proverava da li je vrednost integer.
- @IsBoolean(): Proverava da li je vrednost boolean.
- @IsEmail(): Proverava da li je vrednost validna email adresa.
- @IsNotEmpty(): Proverava da li je vrednost prazna.
- @MinLength(): Proverava minimalnu dužinu stringa.
- @MaxLength(): Proverava maksimalnu dužinu stringa.
- @Min(): Proverava minimalnu vrednost broja.
- @Max(): Proverava maksimalnu vrednost broja.
- @Matches(): Proverava da li vrednost odgovara regularnom izrazu.
Listu svih dekoratora možete pronaći na ovome linku.
Primer
1 2 3 4 5 6 |
import { IsString } from 'class-validator'; export class CreateUserDto { @IsString() name: string; } |
ValidationPipe opcije
Validation Pipe u NestJS-u ima nekoliko korisnih opcija za prilagođavanje ponašanja prilikom validacije. Ove opcije omogućavaju detaljniju kontrolu nad načinom na koji će validacija biti izvršena i pomažu pri rukovanju sa nepotrebnim ili nevalidnim podacima. U nastavku su objašnjene glavne opcije koje možete koristiti sa Validation Pipe-om.
a) Opcija whitelist
Opcija whitelist služi za automatsko uklanjanje svih svojstava koja nisu definisana u DTO (Data Transfer Object) klasi. Kada je ova opcija omogućena, svi dodatni podaci koji nisu eksplicitno definisani u DTO klasi biće ignorisani, čime se osigurava da samo željena svojstva budu obrađena.
Primer:
1 2 3 4 5 |
@UsePipes(new ValidationPipe({ whitelist: true })) @Post() createUser(@Body() createUserDto: CreateUserDto) { return createUserDto; } |
b) Opcija forbidNonWhitelisted
Opcija forbidNonWhitelisted radi u kombinaciji sa whitelist. Kada je omogućena, ova opcija baca grešku svaki put kada se pojavi neko polje koje nije definisano u DTO klasi, umesto da ga samo ignoriše. Ovo je korisno ako želite striktno kontrolisati podatke i sprečiti prolazak bilo kakvih dodatnih informacija koje ne pripadaju zahtevanoj strukturi.
Primer:
1 2 3 4 5 |
@UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true })) @Post() createUser(@Body() createUserDto: CreateUserDto) { return createUserDto; } |
c) Opcija transform
Opcija transform omogućava automatsko prebacivanje ulaznih podataka u instance DTO klasa. Kada je omogućena, ulazni podaci će se automatski transformisati u instancu DTO klase, što omogućava lakši pristup metodama i tipovima u okviru objekta.
Primer:
1 2 3 4 5 |
@UsePipes(new ValidationPipe({ transform: true })) @Post() createUser(@Body() createUserDto: CreateUserDto) { return createUserDto; } |
ValidatePipe na nivou parametra
ValidationPipe
se može primeniti na pojedinačni parametar rute za validaciju ulaznih podataka.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import { Controller, Get, Param, ValidationPipe } from '@nestjs/common'; import { IsUUID } from 'class-validator'; class FindUserDto { @IsUUID() id: string; } @Controller('users') export class UsersController { @Get(':id') findOne(@Param('id', new ValidationPipe()) id: string) { return `User with ID: ${id}`; } } |
Ako korisnik pošalje zahtev sa nevalidnim id (npr. nije UUID), API vraća grešku:
1 2 3 4 5 |
{ "statusCode": 400, "message": ["id must be a UUID"], "error": "Bad Request" } |
ValidatePipe na nivou metode
Dekorator @UsePipes se koristi da primeni ValidationPipe na nivou metode kontrolera.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common'; import { IsString, IsInt, Min } from 'class-validator'; class CreateUserDto { @IsString() name: string; @IsInt() @Min(1) age: number; } @Controller('users') export class UsersController { @Post() @UsePipes(ValidationPipe) createUser(@Body() createUserDto: CreateUserDto) { return `User created with name: ${createUserDto.name}, age: ${createUserDto.age}`; } } |
Validan zahtev:
1 2 3 4 |
{ "name": "John", "age": 25 } |
Nevalidan zahtev:
1 2 3 4 |
{ "name": "", "age": -1 } |
API odgovara sa greškom:
1 2 3 4 5 6 7 8 |
{ "statusCode": 400, "message": [ "name should not be empty", "age must not be less than 1" ], "error": "Bad Request" } |
ValidatePipe na nivou klase
Dekorator @UsePipes može se primeniti na klasu kontrolera, čime se ValidationPipe primenjuje na sve metode u okviru te klase.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common'; import { IsString, IsInt, Min } from 'class-validator'; class CreateUserDto { @IsString() name: string; @IsInt() @Min(1) age: number; } @UsePipes(ValidationPipe) @Controller('users') export class UsersController { @Post() createUser(@Body() createUserDto: CreateUserDto) { return `User created with name: ${createUserDto.name}, age: ${createUserDto.age}`; } @Post('update') updateUser(@Body() updateUserDto: CreateUserDto) { return `User updated with name: ${updateUserDto.name}, age: ${updateUserDto.age}`; } } |
Prednost: Svi zahtevi u okviru kontrolera automatski prolaze validaciju, što smanjuje potrebu za pojedinačnim deklarisanjem.
ValidatePipe na globalnom nivou
ValidationPipe se može registrovati kao globalni Pipe, čime se primenjuje na sve rute u aplikaciji.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); // Globalna registracija ValidationPipe-a app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, })); await app.listen(3000); } bootstrap(); |
Opcije za ValidationPipe:
- whitelist: true – Automatski uklanja svojstva koja nisu definisana u DTO klasi.
- forbidNonWhitelisted: true – Baca grešku ako se pojave nepoznata svojstva.
- transform: true – Transformiše ulazne podatke u instance DTO klasa.
Prednost: Validacija se primenjuje automatski na sve zahteve, bez potrebe za deklarisanjem u svakom kontroleru.
Custom pipes
Ako su vaši zahtevi specifični, onda je nepohodno kreirati “custom pipe-ove”. Za kreiranje pipes, je potrebno:
- Kreirate novu klasu koja implementira PipeTransform interfejs.
- Implementirate metodu transform(), koja prima dva parametra:
- value: Ulazna vrednost koja dolazi iz zahteva.
- metadata: Informacije o vrednosti (opciono, sadrži podatke o tipu i dekoratorima).
Primer 1: Pipe za validaciju broja
Kreiramo Pipe koji proverava da li je ulazni string validan broj. Ako nije, baca grešku.
1 2 3 4 5 6 7 8 9 10 11 12 |
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common'; @Injectable() export class ParseIntPipe implements PipeTransform { transform(value: any): number { const val = parseInt(value, 10); if (isNaN(val)) { throw new BadRequestException('Validation failed: Not a number'); } return val; } } |
Korišćenje u kontroleru:
1 2 3 4 5 6 7 8 9 10 |
import { Controller, Get, Param } from '@nestjs/common'; import { ParseIntPipe } from './parse-int.pipe'; @Controller('users') export class UsersController { @Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { return `User with ID ${id}`; } } |
Ako klijent pošalje zahtev na /users/123, vrednost 123 se uspešno konvertuje u broj.
Ako pošalje /users/abc, API baca grešku:
1 2 3 4 5 |
{ "statusCode": 400, "message": "Validation failed: Not a number", "error": "Bad Request" } |
Primer 2: Pipe za dozvoljene vrednosti
Pipe proverava da li je vrednost iz liste dozvoljenih vrednosti.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common'; @Injectable() export class AllowedValuesPipe implements PipeTransform { constructor(private readonly allowedValues: any[]) {} transform(value: any): any { if (!this.allowedValues.includes(value)) { throw new BadRequestException(`Value '${value}' is not allowed.`); } return value; } } |
Korišćenje u kontroleru:
1 2 3 4 5 6 7 8 9 10 |
import { Controller, Get, Query } from '@nestjs/common'; import { AllowedValuesPipe } from './allowed-values.pipe'; @Controller('products') export class ProductsController { @Get() findByType(@Query('type', new AllowedValuesPipe(['electronics', 'furniture'])) type: string) { return `Products of type: ${type}`; } } |
Validan zahtev: /products?type=electronics
Nevalidan zahtev: /products?type=clothing
1 2 3 4 5 |
{ "statusCode": 400, "message": "Value 'clothing' is not allowed.", "error": "Bad Request" } |
Primer 3: Pipe za transformaciju u veliki tekst
Pipe transformiše string u uppercase.
1 2 3 4 5 6 7 8 |
import { PipeTransform, Injectable } from '@nestjs/common'; @Injectable() export class UppercasePipe implements PipeTransform { transform(value: string): string { return value.toUpperCase(); } } |
Korišćenje u kontroleru:
1 2 3 4 5 6 7 8 9 10 |
import { Controller, Post, Body } from '@nestjs/common'; import { UppercasePipe } from './uppercase.pipe'; @Controller('messages') export class MessagesController { @Post() createMessage(@Body('text', UppercasePipe) text: string) { return `Message: ${text}`; } } |
Ulaz: {“text”: “hello world”}
Izlaz: Message: HELLO WORLD