Validacija podataka u NestJS-u korišćenjem “Pipe”

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”.

pipes

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:

Š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

Primer: klasa u plain JS objekat

Š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

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:

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:

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:

ValidatePipe na nivou parametra

ValidationPipe se može primeniti na pojedinačni parametar rute za validaciju ulaznih podataka.

Ako korisnik pošalje zahtev sa nevalidnim id (npr. nije UUID), API vraća grešku:

ValidatePipe na nivou metode

Dekorator @UsePipes se koristi da primeni ValidationPipe na nivou metode kontrolera.

Validan zahtev:

Nevalidan zahtev:

API odgovara sa greškom:

ValidatePipe na nivou klase

Dekorator @UsePipes može se primeniti na klasu kontrolera, čime se ValidationPipe primenjuje na sve metode u okviru te klase.

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.

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:

  1. Kreirate novu klasu koja implementira PipeTransform interfejs.
  2. 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.

Korišćenje u kontroleru:

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:

Primer 2: Pipe za dozvoljene vrednosti

Pipe proverava da li je vrednost iz liste dozvoljenih vrednosti.

Korišćenje u kontroleru:

Validan zahtev: /products?type=electronics

Nevalidan zahtev: /products?type=clothing

Primer 3: Pipe za transformaciju u veliki tekst

Pipe transformiše string u uppercase.

Korišćenje u kontroleru:

Ulaz: {“text”: “hello world”}

Izlaz: Message: HELLO WORLD


Nest.js kontroleri

Šta su kontroleri?

Kontroleri u Nest.js su klasične TypeScript klase koje su dekorisane @Controller() dekoratorom. U ovakvim klasama se definišu metode koji odgovaraju HTTP operacijama kao što su GET, POST, PUT, DELETE, gde je svaki metod u okviru kontrolera dekorisan odgovarajućim HTTP dekoratorom.

Primer jednostavnog kontrolera:

U ovom primeru @Controller('users') dekorator definiše osnovnu rutu (“/users“) za sve metode unutar kontrolera. Ovaj kontroler iz primera ima tri metode za tri tipa zahteva, i sve tri metode su obeležene sa odgovajućim dekoratorima u zavisnosti od tipa HTTP zahteva koji obrađuju:

  • findAll(): Obradjuje GET zahtev na ruti /users i vraća listu svih korisnika.
  • findOne(): Obradjuje GET zahtev na ruti /users/:id i vraća korisnika sa zadatim ID-om.
  • create(): Obradjuje POST zahtev na ruti /users i kreira novog korisnika.

nest.js

Kreiranje kontrolera

Da biste dodali kontroler u modul, najlakše je koristiti sledeće komande u terminalu:

Ova komanda će generisati users.controller.ts fajl u okviru novog foldera “users” i njega će smestiti u root projekta tj. src/users:

NAPOMENA:
Ako ne postoji direktorijum pod nazivom “users” ova komanda će i njega napraviti. Ukoliko ipak ne želimo da napravimo folder pod nazivom kontrolera već samo kontroler fajl, onda koristimo flag --flat.

Prethodna naredba će napraviti u root direktorijumu (“src”) samo fajl vezan za kontroler nekiKontroler.controller.ts, takodje postoji način da kreiramo novi kontroler fajl u već postojeći folder, a za to koristimo sledeću sintaksu:

Sa ovom naredbom će biti kreiran fajl “noviKontroler.controller.ts” u okviru foldera pod nazivom “nekiFolder”.

Naredba će pored toga će istovremeno dodati taj kontroler i u modul u okviru liste svih kontrolera:

Dekoratori za pristup podacima request-a

Pored dekoratora za obeležavanje tipa request-a (@Get(), @Post()…), postoje i dekoratori za lakši pristup podacima zahteva. Ovi dekoratori olakšavaju rad sa različitim delovima HTTP zahteva i omogućavaju jednostavno i čitljivo rukovanje zahtevima unutar kontrolera:

  • @Param('id'): Omogućava pristup parametru id iz URL-a
  • @Body(): Omogućava pristup telu POST zahteva.
  • @Headers() omogućava pristup HTTP zaglavljima.
  • @Ip() omogućava pristup IP adresi klijenta.
  • @Body() omogućava pristup telu POST zahteva.
  • @Session() omogućava pristup sesijskim podacima.
  • @Cookies() omogućava pristup kolačićima.
  • @HostParam('host') omogućava pristup parametrima hosta.
Primer

Evo kako možete koristiti neke od ovih dekoratora u kontroleru:

U prethodnom primeru dekorator @Get označava da će metoda findOne() odgovoriti na zahtev. Dekorator @Param(‘id’) izvlači parametar pod imenom “id” iz URL-a zahteva, dok dekorator @Query() izvlači sve query parametre iz URL-a zahteva i čuva ih u objektu “query” (npr. za URL /users/123?search=test, query će biti { search: ‘test’ }). Dekorator @Ip() obezbedjuje IP adresu klijenta koji je poslao zahtev.

Dostupnost servisa u kontrolerima

Servisi se koriste za enkapsulaciju poslovne logike i omogućavaju ponovnu upotrebu koda. Servisi se mogu lako uvesti u kontrolere korišćenjem mehanizma za injekciju zavisnosti (dependency injection) koji pruža Nest.js. U Nest.js postoji nekoliko načina za injekciju servisa u kontroler.

Injectovanje servisa kroz konstruktor

Najčešći način je putem konstruktora, kao što je prikazano u sledećem primeru:

U ovom primeru, UsersController koristi UsersService za obradu zahteva. Servis se ubacuje u kontroler kroz konstruktor.

Injektovanje servisa kroz svojstvo

Iako nije toliko često korišćena, moguća je i injekcija servisa putem svojstava koristeći dekorator @Inject.

NAPOMENA:
Takodje postoji i “Manuelna injekcija” servisa ali je prilično komplikovana a koristi se samo u specifičnim slučajevima kada ne možete koristiti konstruktorsku ili property-based injekciju. Ovaj pristup koristi ModuleRef, koji omogućava pristup Nest.js Dependency Injection (DI) kontejneru za ručno dobijanje instanci servisa.


Uvod u Nest.js

Šta je Nest.js?

Nest.js je Node.js framework za izradu server-side aplikacija. Koristi modularnu arhitekturu što podrazumeva da se aplikacija deli na module, gde svaki modul grupiše povezane funkcionalnost, tako da se lako mogu dodati nove funkcionalnosti bez narušavanja postojećih delova aplikacije.

Nest.js korisi TypeScript jezik (mada se može koristiti i JavaScript), uz korišćenje dekoratora (dekoratori su specijalne oznake koje se dodaju iznad koda sa kojima definišemo dodatna uputstva). Pored HTTP-a, Nest.js podržava WebSockets, GraphQL, gRPC i druge protokole.

nest.js

Moduli

U Nest.js, moduli su osnovni gradivni blokovi aplikacije i služe za organizaciju i strukturu koda. Oni grupišu povezane komponente kao što su kontroleri, provajderi (servisi), repozitorijumi i drugi moduli u logične jedinice. Moduli se definišu pomoću @Module() dekoratora.

Polja Modula:
  • imports: Lista modula koji su uvezeni u trenutni modul. Ovi moduli su dostupni unutar modula.
  • controllers: Lista kontrolera definisanih u modulu. Kontroleri su zaduženi za rukovanje HTTP zahtevima.
  • providers: Lista provajdera (servisa) definisanih u modulu. Provajderi obavljaju poslovnu logiku i mogu biti injektovani u kontrolere ili druge provajdere.
  • exports: Lista provajdera koji su eksportovani iz modula i mogu biti korišćeni u drugim modulima.

Kreiranje modula

Korišćenjem Nest CLI, možete brzo i jednostavno kreirati module u vašoj Nest.js aplikaciji. Da biste kreirali novi modul, možete koristiti nest generate (ili skraćeno nest g) komandu. Na primer, da biste kreirali modul pod nazivom cats, koristite sledeću komandu:

Ova komanda će generisati novu datoteku cats.module.ts u src/cats direktorijumu, koja izgleda ovako:

Primer Modula

Evo kako se definiše jednostavan modul u Nest.js:

Glavni Modul (App Module)

Svaka Nest.js aplikacija ima glavni modul, obično nazvan AppModule, koji je korenski modul aplikacije. On može uvoziti druge module i služi kao ulazna tačka za aplikaciju.

Controllers (Kontroleri)

Kontroleri su odgovorni za rukovanje HTTP zahtevima i vraćanje odgovarajućih odgovora klijentima. U Nest.js, kontroleri služe kao posrednici između klijenta i servisa. Oni definišu rute i metode koje se aktiviraju kada se određene rute pogode. Kontroleri se obeležavaju pomoću @Controller() dekoratora. Dekorartori se takodje koriste i u okviru samog kontrolera da obeleže specifične metode ili rute kao što su: @Get(), @Post(), @Put(), @Delete(), itd.

U ovom primeru, CatsController ima jednu rutu koja odgovara na GET zahteve na /cats i vraća string ‘This action returns all cats’.

Dodavanje kontrolera u modul

Nakon kreiranja kontrolera ili servisa, potrebno je ažurirati modul da ih uključi:

Primer

Services (Servisi)

Servisi predstavljaju sloj aplikacije koji obavlja poslovnu logiku i obezbeđuje podatke za kontrolere. Servisi obično sadrže metode koje se koriste za obavljanje različitih zadataka kao što su pristup bazi podataka, rad sa API-jevima trećih strana, obrada podataka i sl.

  • Definisanje servisa: Servisi se definišu kao klase i obično koriste @Injectable() dekorator kako bi ih Nest.js mogao injektovati u kontrolere ili druge servise.
  • Injekcija zavisnosti: Servisi se mogu ubaciti (injektovati) u kontrolere ili druge servise koristeći konstruktor.

Kreiranje servisa

Da biste dodali novi servis, koristite nest generate service (ili skraćeno nest g service) komandu.

Ova komanda će generisati cats.service.ts datoteku u src/cats direktorijumu sa osnovnom implementacijom servisa:

Dodavanje servisa u modul

Kada kreirate novi servis pomoću komande nest generate service cats, Nest CLI će automatski ažurirati odgovarajući modul da uključi novokreirani servis u providers polje tog modula. Ovo omogućava da servis bude dostupan u okviru modula bez dodatne manuelne intervencije.

Primer:

U ovom primeru, UsersService ima metodu findAll koja vraća listu korisnika.

Middleware

Middleware su funkcije koje se izvršavaju tokom obrade HTTP zahteva. Middleware funkcioniše u prostoru između primanja zahteva i povratka odgovora, omogućavajući modifikaciju zahteva i odgovora, preusmeravanje toka ili završavanje zahteva. U Nest.js, middleware se definiše kao klase koje implementiraju NestMiddleware interfejs ili kao obične funkcije. Middleware se može primeniti na određene rute ili globalno na sve rute.

Kreiranje middleware-a

Da biste dodali novi middleware, koristite nest generate middleware (ili skraćeno nest g middleware) komandu.

Ova komanda će generisati logger.middleware.ts datoteku u src direktorijumu sa osnovnom implementacijom middleware-a:

U ovom primeru, LoggerMiddleware loguje svaki zahtev koji prolazi kroz aplikaciju.

Registrovanje middleware-a u modul:

Da biste registrovali middleware, potrebno je da ga dodate u odgovarajući modul.

U ovom primeru, LoggerMiddleware će se primenjivati na sve zahteve koji idu na /users rutu.

Providers (Provajderi)

Nest.js ima ugrađen mehanizam za injektiranje zavisnosti, koji olakšava upravljanje zavisnostima i testiranje koda. Ovo znači da komponente kao što su usluge (services) mogu biti automatski ubačene (injected) tamo gde su potrebne, umesto da ih eksplicitno instancirate. Više o dependency injection-u pročitajte u članku “Šta je Dependency Injection”.

Provajderi su osnovni koncept u Nest.js koji omogućavaju kreiranje i deljenje instanci objekata. Obično se koriste za implementaciju servisa, ali mogu takođe obuhvatati bilo koju klasu koju želite da bude dostupna putem Dependency Injection (DI) mehanizma tj. provajderi mogu biti servisi, repozitorijumi, fabričke funkcije, itd.

Obležavanje provajdera

Provajderi se obeležavaju kao klase koje koriste @Injectable() dekorator.

Primer

U ovom primeru, CatsService je provajder definisan sa @Injectable() dekoratorom.

Registrovanje provajdera

U ovom primeru, CatsService je registrovan kao provajder u CatsModule.

Injectovanje provajdera

Provajderi se mogu ubaciti (injektovati) u druge klase ili provajdere koristeći konstruktor.

Primer

Repozitorijum

Repozitorijumi su sloj koji omogućava pristup podacima i upravljanje njima. Oni apstrahuju interakciju sa bazom podataka ili bilo kojim drugim izvorom podataka, čime omogućavaju čistiji i konzistentniji kod. Koriste se za obavljanje CRUD operacija (kreiranje, čitanje, ažuriranje, brisanje).

Primer

U ovom primeru, UserRepository koristi TypeORM repozitorijum za interakciju sa User entitetom.

Exception Filters

Exception filters su kao sigurnosne mreže u programu koje hvataju greške i odlučuju šta da urade s njima. Kada se nešto neočekivano desi u aplikaciji, exception filteri omogućavaju da se uhvate greške i vrati odgovarajuća poruka korisniku. Exception filters omogućavaju da centralizovano rukujemo greškama i ukidaju potrebu da se svuda po kodu pišu akcije koje bi hendlovale greške.

Korak 1: Definisanje Exception Filtera

Definišemo filter koristeći @Catch() dekorator. Ovo je jednostavan primer filtera koji hvata sve greške:

Korak 2: Korišćenje Exception Filtera

Možemo primeniti filter na različitim nivoima: globalno (za celu aplikaciju), na nivou kontrolera ili na nivou pojedinačnih ruta.

Primena na Globalnom Nivou

Da bi filter radio za celu aplikaciju, dodaćemo ga u glavni fajl aplikacije (npr. main.ts):

Primena na Nivou Kontrolera

Da bi filter radio samo za određeni kontroler, dodamo @UseFilters dekorator iznad kontrolera:

Primena na Nivou Pojedinačnih Ruta

Da bi filter radio samo za određenu rutu, dodamo @UseFilters dekorator iznad te rute:

Pipes

Pipes u Nest.js su moćan alat koji omogućava transformaciju i validaciju podataka. Pipes mogu da transformišu dolazne podatke, verifikuju ih i čak odbace nevažeće podatke. To omogućava da se logika za transformaciju i validaciju centralizuje i lako ponovo koristi.

Kreiranje Pipes-a

Pipe se kreira implementirajući PipeTransform interfejs i definišući metodu transform koja će obraditi podatke.

Primer

U ovome primeru pipe koji konvertuje dolazni string u broj:

Korišćenje Pipes-a

Pipes možete primeniti na globalnom nivou, nivou kontrolera ili nivou rute.

Globalna Primena

Globalno primenjivanje pipes-a koristi se u glavnom fajlu aplikacije (npr. main.ts):

Primena na Kontroleru

Možete primeniti pipes na specifičan kontroler koristeći @UsePipes() dekorator:

Primena na Ruti

Možete primeniti pipes na specifičnu rutu: