Š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.
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:
1 |
nest generate module cats |
Ova komanda će generisati novu datoteku cats.module.ts u src/cats direktorijumu, koja izgleda ovako:
1 2 3 4 |
import { Module } from '@nestjs/common'; @Module({}) export class CatsModule {} |
Primer Modula
Evo kako se definiše jednostavan modul u Nest.js:
1 2 3 4 5 6 7 8 9 10 11 |
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ imports: [], // Ovaj modul ne uvozi nijedan drugi modul controllers: [CatsController], // Definiše CatsController kao kontroler u ovom modulu providers: [CatsService], // Definiše CatsService kao provajder u ovom modulu exports: [CatsService], // Eksplicitno eksportuje CatsService za upotrebu u drugim modulima }) export class CatsModule {} |
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.
1 2 3 4 5 6 7 |
import { Module } from '@nestjs/common'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule], // Uvozi CatsModule u glavni modul }) export class AppModule {} |
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.
1 2 3 4 5 6 7 8 9 |
import { Controller, Get } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get() findAll(): string { return 'This action returns all cats'; } } |
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
1 2 3 4 5 6 7 8 |
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; @Module({ controllers: [CatsController], providers: [], }) export class CatsModule {} |
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.
1 |
nest generate service cats |
Ova komanda će generisati cats.service.ts datoteku u src/cats direktorijumu sa osnovnom implementacijom servisa:
1 2 3 4 5 6 7 8 9 10 |
import { Injectable } from '@nestjs/common'; @Injectable() export class CatsService { private readonly cats = ['Cat1', 'Cat2']; findAll(): string[] { return this.cats; } } |
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.
1 2 3 4 5 6 7 8 9 |
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], }) export class CatsModule {} |
Primer:
1 2 3 4 5 6 7 8 9 10 |
import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private readonly users = ['User1', 'User2']; findAll(): string[] { return this.users; } } |
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.
1 |
nest generate middleware logger |
Ova komanda će generisati logger.middleware.ts datoteku u src direktorijumu sa osnovnom implementacijom middleware-a:
1 2 3 4 5 6 7 8 9 10 |
import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log(`Request...`); next(); } } |
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.
12345678910
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; @Module({})export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('users'); }}
U ovom primeru,
LoggerMiddleware će se primenjivati na sve zahteve koji idu na
/users rutu.
1 2 3 4 5 6 7 8 9 10 |
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; @Module({}) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('users'); } } |
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
1 2 3 4 5 6 7 8 9 10 |
import { Injectable } from '@nestjs/common'; @Injectable() export class CatsService { private readonly cats = ['Cat1', 'Cat2', 'Cat3']; findAll(): string[] { return this.cats; } } |
U ovom primeru, CatsService je provajder definisan sa @Injectable() dekoratorom.
Registrovanje provajdera
1 2 3 4 5 6 7 8 9 |
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], }) export class CatsModule {} |
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
1 2 3 4 5 6 7 8 9 10 11 12 |
import { Controller, Get } from '@nestjs/common'; import { CatsService } from './cats.service'; @Controller('cats') export class CatsController { constructor(private readonly catsService: CatsService) {} @Get() findAll(): string[] { return this.catsService.findAll(); } } |
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
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 26 27 28 |
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity'; @Injectable() export class UserRepository { constructor( @InjectRepository(User) private readonly userRepository: Repository<User>, ) {} findAll(): Promise<User[]> { return this.userRepository.find(); } findOne(id: number): Promise<User> { return this.userRepository.findOneBy({ id }); } create(user: User): Promise<User> { return this.userRepository.save(user); } async remove(id: number): Promise<void> { await this.userRepository.delete(id); } } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception instanceof HttpException ? exception.getStatus() : 500; response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, }); } } |
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):
1 2 3 4 5 6 7 8 9 10 |
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { AllExceptionsFilter } from './all-exceptions.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new AllExceptionsFilter()); await app.listen(3000); } bootstrap(); |
Primena na Nivou Kontrolera
Da bi filter radio samo za određeni kontroler, dodamo @UseFilters dekorator iznad kontrolera:
1 2 3 4 5 6 7 8 9 10 11 |
import { Controller, Get, UseFilters } from '@nestjs/common'; import { AllExceptionsFilter } from './all-exceptions.filter'; @Controller('cats') @UseFilters(AllExceptionsFilter) export class CatsController { @Get() findAll() { throw new Error('This is an error'); } } |
Primena na Nivou Pojedinačnih Ruta
Da bi filter radio samo za određenu rutu, dodamo @UseFilters dekorator iznad te rute:
1 2 3 4 5 6 7 8 9 10 11 |
import { Controller, Get, UseFilters } from '@nestjs.common'; import { AllExceptionsFilter } from './all-exceptions.filter'; @Controller('cats') export class CatsController { @Get() @UseFilters(AllExceptionsFilter) findAll() { throw new Error('This is an error'); } } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 |
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'; @Injectable() export class ParseIntPipe implements PipeTransform<string, number> { transform(value: string, metadata: ArgumentMetadata): number { const val = parseInt(value, 10); if (isNaN(val)) { throw new BadRequestException('Validation failed'); } return val; } } |
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):
1 2 3 4 5 6 7 8 9 10 |
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); |
Primena na Kontroleru
Možete primeniti pipes na specifičan kontroler koristeći @UsePipes() dekorator:
1 2 3 4 5 6 7 8 9 10 11 |
import { Controller, Get, UsePipes, Param } from '@nestjs.common'; import { ParseIntPipe } from './parse-int.pipe'; @Controller('cats') export class CatsController { @Get(':id') @UsePipes(ParseIntPipe) findOne(@Param('id') id: number) { return `This action returns a cat with id ${id}`; } } |
Primena na Ruti
Možete primeniti pipes na specifičnu rutu:
1 2 3 4 5 6 7 8 9 10 |
import { Controller, Get, Param } from '@nestjs.common'; import { ParseIntPipe } from './parse-int.pipe'; @Controller('cats') export class CatsController { @Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { return `This action returns a cat with id ${id}`; } } |