Als backend-ontwikkelaar van Node.js zult u het ermee eens zijn dat Node.js standaard zeer eenvoudig is en geen aannames doet over wat u nodig heeft tijdens het bouwen van een app. Als gevolg hiervan bent u verantwoordelijk voor het instellen van alles wat u in een app wilt gebruiken, inclusief het afhandelen van routering, het uitvoeren van API-aanroepen, het instellen van TypeScript of Web Sockets, of zelfs fundamentele zaken zoals code-organisatie, bestandsstructuur en naamgevingsconventies. .
Het beheren van een grootschalige applicatie kan een moeilijke taak zijn, vooral als deze niet is ontworpen met een duidelijke structuur en strikte richtlijnen voor de organisatie van code.
Nest.js probeert een aantal van deze problemen aan te pakken door een abstractie rond Node.js te creëren, zodat jij als ontwikkelaar zich kunt concentreren op het applicatieprobleem in plaats van op andere kleine implementatiedetails.
In deze handleiding leert u de kernbeginselen van Nest.js van boven tot onder, zodat u snel aan de slag kunt met het bouwen van Node.js-applicaties op bedrijfsniveau met behulp van Nest.js.
Alles wat we door deze gids zullen leren, zal stapsgewijs zijn; waarbij veel aandacht wordt besteed aan inleidende concepten. Om het meeste uit deze handleiding te halen, helpt het om mee te coderen.
Laten we er meteen in duiken, mensen!
Broncode: Zoals gewoonlijk kunt u sleutelen aan de broncode die wordt gehost GitHub.
Opmerking: We zullen Postman gebruiken om de API in onze demo te testen. Je kunt het downloaden op de Postbode Download pagina. U kunt ook gewoon de browser gebruiken, de opdrachtregel curl
tool of een andere tool die u misschien kent.
Wat is Nest.js
Beschouw Nest.js als een superset van Node.js die moeilijke taken, tools en standaardcode abstraheert, en tegelijkertijd een volwaardige toolkit toevoegt voor de ontwikkeling van uw applicaties met behulp van modern JavaScript en TypeScript.
Nest.js biedt een kant-en-klare applicatiearchitectuur waarmee ontwikkelaars en teams zeer schaalbare, testbare, losjes gekoppelde en gemakkelijk te onderhouden applicaties kunnen creëren, door gebruik te maken van direct beschikbare en prominente opties en modules in de community, zoals die beschikbaar zijn in Express.js-toepassingen. U kunt zelfs Express (dat standaard onder de motorkap wordt gebruikt) omwisselen voor Fastify, maar als u dit doet, moet u mogelijk verschillende Fastify-compatibele bibliotheken in uw toepassing gebruiken.
Het combineert de kenmerken van functioneel programmeren, objectgeoriënteerd programmeren en functioneel reactief programmeren, en met meer dan 52.4k sterren en 6.2k vorken op GitHub en een wekelijkse downloadtelling van maximaal 1,784,004, is het vooruitstrevende Node.js-framework een populaire keuze voor het maken van efficiënte, schaalbare en ondernemingswaardige serverapplicaties.
Kenmerken van Nest.js
Dit zijn redenen waarom Nest.js is uitgegroeid tot zo’n populair Node.js-framework:
- Nest.js is gemaakt om ontwikkelaars te helpen zowel monolithische applicaties als microservices te bouwen.
- Hoewel het krachtig is, is het ook ontwikkelaarsvriendelijk om mee te werken; gemakkelijk te gebruiken, snel te leren en gemakkelijk toe te passen.
- Het maakt gebruik van TypeScript (een superset van JavaScript) en maakt ruimte voor ontwikkelaars om onderhoudbare code te schrijven zonder runtimefouten.
- Het beschikt over een opdrachtregelinterface die de productiviteit van ontwikkelaars en het ontwikkelingsgemak helpt verhogen.
- Wanneer u met Nest.js bouwt, worden de ontwikkelingsprocessen verbeterd en wordt er tijd bespaard, of u nu een Minimum Viable Product aan het opstarten bent of aan een applicatie werkt, omdat Nest standaard wordt geleverd met een geweldige projectmappenstructuur.
- Het ondersteunt een verscheidenheid aan Nest-specifieke modules die helpen bij de integratie van algemene concepten en technologieën, waaronder TypeORM, GraphQL, logboekregistratie, validatie, Mongoose, WebSockets, caching, enz.
- Nest.js kan bogen op de beste documentatie voor elk raamwerk dat er is. De documentatie is grondig, gemakkelijk te begrijpen en nuttig bij het besparen van tijd voor het opsporen van fouten, omdat deze moeiteloos doorkomt wanneer er behoefte is aan een oplossing voor een probleem.
- Nest.js kan worden geïntegreerd met Jest, waardoor het eenvoudig is om unit-tests voor uw applicaties te schrijven.
- Het is gebouwd voor zowel kleine als grootschalige bedrijfstoepassingen.
Een Nest.js-project maken
Om aan de slag te gaan met Nest.js op je lokale computer, moet je eerst de Nest Command Line Interface (CLI) installeren. Hiermee kun je een nieuwe Nest.js-projectmap opzetten en de map vullen met kernbestanden en modules die nodig zijn voor een Nest.js-app.
Voer de volgende opdracht uit om de Nest.js-opdrachtregelinterface te installeren:
$ npm i -g @nestjs/cli
// Or
$ yarn global add @nestjs/cli
// Or
$ pnpm add -g @nestjs/cli
Zodra je de Nest.js CLI wereldwijd met succes op je lokale computer hebt geïnstalleerd, kun je deze uitvoeren nest
op de opdrachtregel om verschillende opdrachten te zien die we kunnen gebruiken:
$ nest
Resulteert in:
Usage: nest [options]
Options:
-v, --version Output the current version.
-h, --help Output usage information.
Commands:
new|n [options] [name] Generate Nest application.
build [options] [app] Build Nest application.
start [options] [app] Run Nest application.
info|i Display Nest project details.
add [options] Adds support for an external library to your project.
generate|g [options] [name] [path] Generate a Nest element.
Schematics available on @nestjs/schematics collection:
┌───────────────┬─────────────┬──────────────────────────────────────────────┐
│ name │ alias │ description │
│ application │ application │ Generate a new application workspace │
│ class │ cl │ Generate a new class │
│ configuration │ config │ Generate a CLI configuration file │
│ controller │ co │ Generate a controller declaration │
│ decorator │ d │ Generate a custom decorator │
│ filter │ f │ Generate a filter declaration │
│ gateway │ ga │ Generate a gateway declaration │
│ guard │ gu │ Generate a guard declaration │
│ interceptor │ itc │ Generate an interceptor declaration │
│ interface │ itf │ Generate an interface │
│ middleware │ mi │ Generate a middleware declaration │
│ module │ mo │ Generate a module declaration │
│ pipe │ pi │ Generate a pipe declaration │
│ provider │ pr │ Generate a provider declaration │
│ resolver │ r │ Generate a GraphQL resolver declaration │
│ service │ s │ Generate a service declaration │
│ library │ lib │ Generate a new library within a monorepo │
│ sub-app │ app │ Generate a new application within a monorepo │
│ resource │ res │ Generate a new CRUD resource │
└───────────────┴─────────────┴──────────────────────────────────────────────┘
Hier wordt u getoond hoe u gebruik kunt maken van de opdrachten en kunt u nu gebruikmaken van de new|n [options] [name]
opdracht om je allereerste Nest.js-project te maken:
$ nest new getting-started-with-nestjs
// Or
$ nest n getting-started-with-nestjs
Vervolgens wordt u gevraagd welke pakketbeheerder u wilt gebruiken:
? Which package manager would you ❤️ to use? (Use arrow keys)
npm
yarn
> pnpm
Kies gerust de pakketbeheerder van jouw keuze, ik ga mee pnpm
. Dit komt omdat het ongeveer drie keer efficiënter en sneller is dan NPM, en met een snel cachesysteem is PNPM ook sneller dan Yarn.
Nadat je een pakketbeheerder hebt gekozen, gaat het installatieproces verder, waarna de Nest.js-app wordt gemaakt.
Nu, kunt u cd
in het nieuw gemaakte project en open het met een editor naar keuze:
$ cd getting-started-with-nestjs
Nu het project is gemaakt, kunnen we het uitvoeren met een van de volgende opdrachten:
$ npm run start
// Or
$ yarn start
// Or
$ pnpm run start
Als je het package.json
bestand, ziet u in het scriptsegment de waarde voor pnpm run start
is nest start
:
"start": "nest start",
Dit betekent dat je de Nest.js-app ook kunt gebruiken door:
$ nest start
Een blik op de Nest.js-projectstructuur
Laten we eens goed bekijken hoe een Nest-app is gestructureerd:
/package.json
De package.json
-bestand is het hart van het Node.js- en bij uitbreiding het Nest.js-project. Het bevat alle metagegevens over het project en definieert verschillende functionele eigenschappen van het project die nodig zijn om applicatie-afhankelijkheden te installeren of projectscripts uit te voeren.
We hebben al gezien wat de mogelijkheden zijn van de start
scripts.
De start:dev
profiel maakt het mogelijk om te kijken naar veranderingen in de applicatie en deze automatisch opnieuw te laden, zonder de noodzaak om de applicatie te stoppen en opnieuw te starten – en het is bedoeld voor ontwikkeling. De start:prod
script is handig wanneer u wilt testen of uw applicatie gereed is voor productie en wanneer u deze in productie implementeert, samen met andere scripts voor het testen van de Nest.js-app.
@nestjs/platform-express
definieert express als de standaard HTTP-server in een Nest-app.
/tsconfig.json
De tsconfig.json
bestand is een bestand geschreven in JSON (JavaScript Object Notation) dat TypeScript-gerelateerde opties definieert die nodig zijn om de Nest-app te compileren.
/nest-cli.json
Dit bevat metadata die nodig zijn om Nest-apps te bouwen, organiseren of implementeren.
/test
Deze map bevat alle bestanden die nodig zijn om Nest-tests uit te voeren. Nest gebruikt het Jest-framework voor het testen met de Jest-configuratie in de jest-e2e.json
bestand.
/src
De src
directory is de bovenliggende map voor de kern van het Nest-project. Het houdt de main.ts
bestand. Dit is het bestand waarin de Nest-app wordt gestart. De taak van de main.ts
bestand moet worden geladen AppModule
waaruit wordt geïmporteerd /src/app.module.ts
.
Verderop in deze handleiding zullen we meer leren over modules; een van de belangrijkste componenten van een Nest.js-applicatie.
De AppModule
is een klasse die als module is gemaakt met behulp van de @Module
decorateur. In de app.module.ts
bestand AppService
oppompen van ./app.service
en AppController
oppompen van ./app.controller
worden ook geïmporteerd.
De AppController
is ook een klasse die is gemaakt met behulp van de @Controller
decorateur, terwijl de AppService
is een klasse die is gemaakt met behulp van de @Injectable
annotatie.
Het leuke van Nest is dat er maar heel weinig decorateurs zijn die metadata aan een klasse toevoegen en dat metadata het doel van die klasse definieert, zodat:
@Controller()
transformeert een klasse in een controller.@Module()
transformeert een klasse in een module.@Injectable()
transformeert een klasse in een aanbieder.
Ook in de src
map is de app.controller.spec.ts
bestand, een testbestand voor controllers.
We kunnen de app uitvoeren met behulp van nest start
.
De app wordt gestart om http://localhost:3000
in uw browser:
We kunnen de inhoud wijzigen die wordt weergegeven op http://localhost:3000
, door naar de app.service.ts
bestand, waarin de provider voor de indexroute is gedefinieerd.
De bouwstenen van een Nest.js-app
Er zijn drie belangrijke componenten van een Nest.js-app:
- Modules
- Controllers
- Providers
Laten we, als we meer te weten komen over de bouwstenen van een Nest-app, eerst het Nest-project opruimen door de app.controller.spec.ts
, ./app.service
, app.module.ts
en ./app.controller
bestanden; gewoon weggaan main.ts
, om een ontwikkelingslevenscyclus vanaf het begin te emuleren.
Op dit punt, wanneer we het geïmporteerde AppModule
bestand van main.ts
, wordt ons gevraagd dat er geen argument voor 'module' is opgegeven.
Om de bouwstenen van een Nest-app te demonstreren, zullen we kijken naar een eenvoudige implementatie van een gebruikersprofiel, door een REST API te bouwen om CRUD-bewerkingen op een object uit te voeren.
Modules
In het src
map maak een nieuwe app.module.ts
bestand en maak vervolgens een AppModule
klasse, die we exporteren.
Importeer vervolgens de AppModule
klasse in main.ts
, en loop nest start
.
Navigeer naar http://localhost:3000
in uw browser en u krijgt een 404-foutmelding:
Dit komt omdat we nog geen route hebben gedefinieerd voor de basis-URL van de Nest-app.
Terug in app.module.ts
, we hebben de AppModule
klasse die wij hebben is nog geen Nest-module. Om er een Nest-module van te maken, voegen we de @Module()
decorateur waaruit wordt geïmporteerd @nestjs/common
dan passeren we een leeg object.
import { Module } from '@nestjs/common';
@Module({})
export class AppModule {}
Nu hebben we een Nest.js-module!
Opmerking: Een module is een klasse die is geannoteerd met a @Module()
decorateur.
Elke Nest-applicatie heeft een rootmodule die als toegangspunt dient om de structuur en relaties van een Nest-applicatie op te lossen.
Het wordt sterk aanbevolen om meerdere modules te gebruiken om de componenten van uw applicatie te organiseren.
De @Module()
decorateur maakt het mogelijk dat ontwikkelaars metadata over een klas kunnen definiëren in de Nest-app.
In het geval dat er meerdere modules zijn, zoals een gebruikersmodule, bestelmodule, chatmodule, enz., wordt de app.module.ts
moet worden gebruikt om alle andere modules van de Nest-app te registreren.
Routes creëren; Controleurs
Om routes te maken in Nest-applicaties zijn controllers nodig. Het doel van een verwerkingsverantwoordelijke is het ontvangen van specifieke verzoeken voor een Nest-applicatie; het beheersen van de aanvraag- en antwoordcyclus voor verschillende routes binnen de applicatie.
Wanneer er een HTTP-verzoek wordt gedaan van de client naar de Nest-app, wordt het verzoek afgehandeld door de route die overeenkomt met de route waarin het verzoek wordt gedaan, en wordt het juiste antwoord geretourneerd.
Om een controller in een Nest-app te maken, moeten we gebruik maken van de @Controller()
decorateur.
In het src
map, maak een nieuw bestand app.contoller.ts
, en daarin kunnen we een Nest-controller definiëren:
import { Controller } from '@nestjs/common';
@Controller({})
export class AppController {}
Dat is het! We hebben een hele mooie controller, maar om een nieuwe route te maken, moeten we eerst onze Nest-app op de hoogte stellen van de aangemaakte controller.
Om dit te bereiken, zorgen we ervoor dat we importeren AppController
in app.module.ts en definieer informatie over de controllers in @Module()
decorateur – als een reeks controllers:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
controllers: [AppController],
})
export class AppModule {}
GET-verzoeken afhandelen
Vervolgens definiëren we een eenvoudig getUser()
traject (met de @Get()
decorateur gebruikt voor het verwerken van HTTP GET-verzoeken naar een opgegeven pad) om als basisroute te dienen, we hebben toegang tot hetzelfde in de browser op https://localhost:3000
:
import { Controller, Get } from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
getUser() {
return 'I am a great person';
}
}
Dit resulteert in:
Hmm, hier retourneren we slechts een string, maar wat als we een object willen retourneren? In plaats van een string kunnen we een object definiëren:
import { Controller, Get } from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
getUser() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
}
Navigeer naar http://localhost:3000
in uw browser en u ziet het object:
Wat dacht je ervan om, buiten de basisroute, een route te maken die vergelijkbaar is met http://localhost:3000/user
voor het ophalen van alle gebruikers?
We kunnen een controller maken die een dergelijke route op een aantal manieren kan afhandelen.
Eén manier zou zijn om een nieuwe methode te definiëren, met behulp van de @Get()
decorateur/handler.
import { Controller, Get } from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
getUser() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
}
Nest.js biedt decorateurs of handlers voor alle verschillende HTTP-methoden, inclusief @Get()
, @Post()
, @Put()
, @Delete()
, @Patch()
, @Options()
en @Head()
.
De @All()
decorateur definieert een eindpunt dat alle verschillende methoden afhandelt.
POST-aanvragen afhandelen
We kunnen ook POST-verzoeken definiëren voor het opslaan van gegevens in de database, met behulp van de @Post()
decorateur:
import { Controller, Post } from '@nestjs/common';
@Controller({})
export class AppController {
@Post()
store() {
return 'Post request successful';
}
}
Vervolgens testen we het POST-verzoek met Postman en merken we dat de string met succes wordt geretourneerd zoals gedefinieerd.
U vraagt zich misschien af: wat als ik ook meer wil doen dan alleen gegevens retourneren? Misschien om gegevens te verzenden.
Daarvoor moet je de gegevens in de routemethode injecteren, zoals weergegeven:
import { Controller, Post, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller({})
export class AppController {
@Post()
store(@Req() req: Request) {
return req.body;
}
}
Wanneer we nu het POST-verzoek testen met Postman, kunnen we de gegevens bekijken die worden verzonden. In dit geval is het gewoon een leeg object:
Bekijk onze praktische, praktische gids voor het leren van Git, met best-practices, door de industrie geaccepteerde normen en bijgevoegd spiekbriefje. Stop met Googlen op Git-commando's en eigenlijk leren het!
Dynamische routering met routeparameters
Stel dat u dynamische gegevens wilt accepteren als onderdeel van een verzoek. Eerst moeten we het token in het pad van de route definiëren, om de dynamische positie op de route/URL te noteren, en vervolgens de @Param()
decorateur, kan de routeparameter als volgt worden benaderd:
import { Controller, Get, Param } from '@nestjs/common';
@Controller({})
export class AppController {
@Get('/:userId')
getUser(@Param() userId: number) {
return userId;
}
}
De userId
is succesvol geretourneerd:
Asynchrone verzoeken afhandelen
Nest.js kan asynchrone verzoeken die een belofte opleveren, op verschillende manieren afhandelen:
import { Controller, Get} from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
async findAll(): Promise {
return [];
}
}
In de bovenstaande benadering wordt asynchroniciteit afgehandeld met behulp van de async
trefwoord. Een andere benadering is het retourneren van waarneembare RxJS-streams:
import { Controller, Get} from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
findAll(): Observable {
return of([]);
}
}
Hier abonneert Nest.js zich op de bron onder de motorkap en wanneer de stream is voltooid, neemt deze automatisch de laatst uitgezonden waarde over.
Omleidingen verwerken in Nest
De @Redirect()
decorateur wordt gebruikt om een antwoord om te leiden naar een andere URL. De @Redirect()
decorateur accepteert twee argumenten: de URL waarnaar moet worden doorverwezen en de statuscode bij doorverwijzing, die beide optioneel zijn:
import { Controller, Get} from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
@Redirect('https://www.ucheazubuko.com', 302)
getSite() {
return { url: 'https://stackabuse.com' };
}
}
Statuscode retourneren
Om de statuscode te retourneren voor elk verzoek dat op de Nest.js-server wordt afgehandeld, moet de @HttpCode(…)
komt er makkelijk doorheen.
In Nest is de standaardstatuscode voor GET-verzoeken 200, een POST-verzoek is 201 en een foutverzoek is 304
De statuscode voor een serververzoek kan worden gedefinieerd zoals hieronder weergegeven:
import { Controller, Post, HttpCode } from '@nestjs/common';
@Controller({})
export class AppController {
@Post()
@HttpCode(204)
create() {
return 'This action adds a new user to the app.';
}
}
Het afhandelen van DELETE-verzoeken
Net als bij het indienen van een POST-verzoek, kan een verwijderverzoek als volgt worden afgehandeld:
import { Controller, Delete, Param } from '@nestjs/common';
@Controller({})
export class AppController {
@Delete('/:userId')
delete(@Param() params: { userId: number }) {
return params;
}
}
UPDATE-verzoeken afhandelen
Een verzoek om specifieke gegevens op de server bij te werken kan worden afgehandeld met behulp van de @Patch()
decorateur:
import { Controller, Patch, Req} from '@nestjs/common';
import { Request } from 'express';
@Controller({})
export class AppController {
@Patch('/:userId')
update(@Req() req: Request) {
return req.body;
}
}
Nu we verschillende manieren hebben gezien om typische controllers te definiëren die we vaak op een robuuste server zouden hebben, is het belangrijk op te merken dat de controller per gebruiksgeval lean, clean en gedefinieerd moet zijn, zodat als er een andere controller is voor het definiëren user
routes, dan moet er een aparte map worden aangemaakt en gereserveerd voor het afhandelen ervan – weg van de AppController
.
Dan in user.controller.ts
, kunnen we alle route-handlers daarin configureren zodat ze worden voorafgegaan door /user/
door code te schrijven zoals hieronder weergegeven:
import { Controller, Get } from '@nestjs/common';
@Controller('/user')
export class UserController {
@Get()
getUser() {
return 'I am from the user controller';
}
}
Registreer u vervolgens UserController
in de arrays van de controllers in app.modules.ts
:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { UserController } from './controllers/user/user.controller';
@Module({
controllers: [AppController, UserController],
})
export class AppModule {}
Wanneer we navigeren naar https:localhost:3000/user
, het keert succesvol terug:
Om de projectmap nog netter te houden dan hij nu is, kunnen we een user.module.ts
bestand waarin we de UserController
:
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
@Module({
controllers: [UserController],
})
export class UserModule {}
Importeer vervolgens UserModule
in app.module.ts
:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { UserModule } from './user/user.module';
@Module({
controllers: [AppController],
imports: [UserModule],
})
export class AppModule {}
Hiermee kunnen we hetzelfde effect bereiken als voorheen.
Opmerking: Nest maakt het eenvoudig om (mo)dules en (co)ntrollers te (g)enereren met behulp van de nest g mo
en nest g co
opdrachten. Specifieke modules, zoals de user
Module en controllers kunnen ook snel worden gemaakt met behulp van de Nest CLI, door de volgende opdrachten uit te voeren: nest g mo user
– om een gebruikersmodule aan te maken, en nest g co user
– om een gebruikerscontroller aan te maken.
Providers
Het ophalen van gegevens uit een database moet worden afgehandeld door providers in plaats van door beheerders, om een abstractielaag te creëren tussen de op de gebruiker gerichte code en de code die interageert met potentieel gevoelige gegevens. Tussen deze lagen kan validatie worden ingesteld om een goede verwerking van de database te garanderen. Met de Nest CLI kunnen we providers creëren door services te genereren:
$ nest g s user
Dit creëert een UserService
waarin we alle bedrijfslogica voor de UserController
, Zodat UserController
behandelt alleen verzoeken en antwoorden. In user.service.ts
, we zien dat de @Injectable()
decorateur wordt gebruikt om de klasse te definiëren. In Nest is het gebruik van de @Injectable()
decorateur is om services, opslagplaatsen of helpersklasse te transformeren in een provider.
Providers worden via de constructor in een klasse geïnjecteerd. Laten we een voorbeeld eens goed bekijken.
Eerder, in user.controller.ts
, hadden we de bedrijfslogica gedefinieerd voor het verkrijgen van het gebruikersobject, maar nu zouden we hetzelfde moeten definiëren in de UserService
:
import { Controller, Injectable } from '@nestjs/common';
@Controller({})
export class AppController {
@Injectable()
get() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria'; };
}
}
Vervolgens in de user.controller.ts
bestand, laten we een constructor definiëren in het UserController
klas. In deze constructor bieden wij een privé userService
, wat een type is van de UserService
klas. Met deze privé kunnen we gebruik maken van de bedrijfslogica die we eerder hadden gedefinieerd voor het ophalen van de gebruikers:
import { Controller, Get } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('/user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUser() {
return this.userService.get();
}
}
Dus de UserController
klasse, hangt nu af van de UserService
klasse in een concept dat bekend staat als
afhankelijkheid injectie.
Op dezelfde manier, de logica in beide user.controller.ts
en user.service.ts
bestanden worden dienovereenkomstig bijgewerkt:
import {
Controller,
Delete,
Get,
Param,
Patch,
Post,
Req,
} from '@nestjs/common';
import { Request } from 'express';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUsers() {
return this.userService.get();
}
@Get('/:userId')
getUser(@Param() param: { userId: number }) {
return this.userService.getUser(param);
}
@Post()
store(@Req() req: Request) {
return this.userService.create(req);
}
@Patch('/:userId')
update(@Req() req: Request, @Param() param: { userId: number }) {
return this.userService.update(req, param);
}
@Delete()
delete(@Param() param: { userId: number }) {
return this.userService.delete(param);
}
}
import { Injectable } from '@nestjs/common';
import { Request } from 'express';
@Injectable()
export class UserService {
get() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
getUser(param: { userId: number }) {
return param;
}
create(req: Request) {
return req.body;
}
update(req: Request, param: { userId: number }) {
return { body: req.body, param };
}
delete(param: { userId: number }) {
return param;
}
}
Laten we nu verifiëren dat de eindpunten werken zoals ze zouden moeten, met behulp van Postman.
Demystificatie van afhankelijkheidsinjectie in Nest.js
Bij het bouwen van kleinere componenten van een applicatie, zoals een klasse of module, kan uw klasse afhankelijk zijn van de functionaliteit van een andere klasse of module, bijvoorbeeld de noodzaak om gebruik te maken van een HTTP-service die door een andere klasse wordt geleverd om API-aanroepen te doen, of servicelagen die interageren met de persistentielaag.
Afhankelijkheden kunnen binnen controllers worden voorzien via afhankelijkheidsinjectie.
Afhankelijkheidsinjectie is een programmeerconcept en -patroon dat uitdrukt hoe delen van een applicatie worden geleverd aan andere delen van de applicatie die deze nodig hebben, op een manier die zorgt voor een hoge cohesie maar losse koppeling.
Nest ondersteunt afhankelijkheidsinjectie en u kunt het in uw Nest-toepassingen gebruiken om de modulariteit van uw project te verbeteren.
Een praktische illustratie wordt als volgt weergegeven:
Stel dat klasse A een bepaalde functionaliteit van klasse B gebruikt. Dan wordt er gezegd dat klasse A afhankelijk is van klasse B. Om klasse B in klasse A te kunnen gebruiken, moeten we dus eerst een instantie van klasse B maken (dat wil zeggen, een Klasse B-object):
const b = new B ()
.
Het overbrengen van de taak van het maken van een instantie van een klasse naar een andere klasse en het rechtstreeks gebruiken van de afhankelijkheid in de klasse waarin wordt voorzien (de injectorcomponent) staat bekend als afhankelijkheidsinjectie.
Advies: Dependency injection, of DI, is een van de fundamentele concepten in frameworks als Spring Boot, Nest.js en Angular.js. Als je er meer over wilt lezen, kun je de officiële Angular-documentatie.
Normaal gesproken zou een klasse zich uitsluitend moeten concentreren op het vervullen van zijn functies, in plaats van te worden gebruikt om verschillende objecten te maken die hij al dan niet nodig heeft.
Voordelen van afhankelijkheidsinjectie.
- Het helpt bij het testen van eenheden.
- Met afhankelijkheidsinjectie wordt de standaardcode verminderd, omdat het initialiseren van afhankelijkheden wordt gedaan door de injectorcomponent.
- Het proces van het verlengen van een aanvraag wordt eenvoudiger.
- Afhankelijkheidsinjectie helpt losse koppeling mogelijk te maken.
Verzoekladingen verkennen
Houd er rekening mee dat we met verschillende verzoekbehandelaars, zoals POST en PATCH, gebruik konden maken van het verzoek dat door de server werd verzonden met behulp van de @Req()
decorateur. Er is echter meer aan de hand.
In plaats van het volledige verzoekobject op te halen, kunnen we alleen gebruik maken van specifieke delen van het verzoekobject die we nodig hebben.
Nest biedt dus verschillende decorateurs die kunnen worden gebruikt met de HTTP-routehandlers om toegang te krijgen tot Express of Fastify-objecten:
Nestdecorateurs | Fastify- of Express-object waartoe toegang wordt verkregen |
`@Request(), @Req()` | `verzoek` |
`@Reactie(), @Res()` | 're's' |
`@Volgende()` | `volgende` |
`@Sessie()` | `verzoek.sessie` |
`@Param(param?:string)` | `req.params` / `req.params[param]` |
`@Body(param?:string)` | `req.body` / `req.body[param]` |
`@Query(param?:string)` | `req.query` / `req.query[param]` |
`@Headers(param?:string)` | `req.headers` / `req.headers[param]` |
`@Ip()` | `req.ip` |
`@HostParam()` | `req.hosts` |
Een typisch voorbeeld is het vervangen van de @Req()
decorateur die we eerder gebruikten om toegang te krijgen tot de hoofdtekst van het resultaat, met de @Body()
waarmee we al directe toegang kunnen krijgen tot de hoofdtekst van een verzoek zonder te boren:
@Post()
store(@Body() body: any) {
return this.userService.create(body);
}
@Patch('/:userId')
update(@Body() body: any, @Param() param: { userId: number }) {
return this.userService.update(body, param);
}
create(body: any) {
return body;
}
update(body: any, param: { userId: number }) {
return { body: body, param };
}
In sommige gevallen wilt u mogelijk alleen specifieke eigenschappen van een aanvraagpayload ophalen. In dat geval moet u een Data Transfer Object (DTO)-schema definiëren. Het Data Transfer Schema is een object dat een kopie definieert van het object dat wordt opgehaald, maar wordt voornamelijk gebruikt om de gegevens over te dragen tussen het object dat moet worden opgeslagen of opgehaald, en de persistentielaag. Omdat dit proces kwetsbaarder is voor aanvallen, bevat de DTO doorgaans niet zoveel gevoelige gegevenspunten. Dankzij deze eigenschap kunt u ook alleen bepaalde velden van een object ophalen.
In Nest wordt aanbevolen om klassen te gebruiken om een Data Transfer Object te definiëren, omdat de waarde van klassen tijdens de compilatie behouden blijft.
Stel dat de hoofdtekst van het verzoek een token bevat en u dergelijke gegevens niet wilt ophalen of bijwerken, dan kan een DTO worden gedefinieerd zoals hieronder weergegeven:
@Patch('/:userId')
update(
@Body() updateUserDto: { name: string; email: string },
@Param() param: { userId: number },
) {
return this.userService.update(updateUserDto, param);
}
update(
updateUserDto: { name: string; email: string },
param: { userId: number },
) {
return { body: updateUserDto, param };
}
U zult echter merken dat we het type voor hebben gedefinieerd updateUserDto
tweemaal; in user.service.ts
en in user.controller.ts
, maar we moeten onze codes DRY (Don't Repeat Yourself) houden, zodat we onszelf niet herhalen rond de codebase.
Hiervoor in een nieuwe map /user/dto
in de /user
directory, moeten we een bestand maken /update-user.dto.ts
met de .dto.ts
extensie waar we het UpdateUserDto
klasse voor gebruik in de user.service.ts
en user.controller.ts
bestanden:
export class UpdateUserDto {
name: string;
email: string;
}
...
import { UpdateUserDto } from './dto/update-user.dto';
@Patch('/:userId')
update(
@Body() updateUserDto: UpdateUserDto,
@Param() param: { userId: number },
) {
return this.userService.update(updateUserDto, param);
}
...
import { UpdateUserDto } from './dto/update-user.dto';
update(updateUserDto: UpdateUserDto, param: { userId: number }) {
return { body: updateUserDto, param };
}
Pipe en validatie
Stel dat het nodig is om de gegevens te valideren die worden verkregen wanneer er een verzoek via de server is gedaan.
In Nest kunnen we de juistheid van alle gegevens die de applicatie binnenkomen of verlaten, testen door gebruik te maken van pipelines die twee afhankelijkheden installeren: class-validator
en class-transformer
.
Een pipe is een klasse die wordt gedefinieerd met de
@Injectable()
decorateur (pijpen zijn dus aanbieders), die de uitvoering uitvoertPipeTransform
koppel. Ze transformeren gegevens naar het gewenste formaat en evalueren gegevens zodanig dat als de gegevens geldig worden bevonden, deze onveranderd worden doorgegeven, anders wordt er een uitzondering gegenereerd. Om een pipe te gebruiken, moet u een instantie van de specifieke pipe-klasse aan de juiste context binden.
De class-validator
pakket maakt het mogelijk om decorateurs en niet-decorateurs te valideren met behulp van validator.js intern. Terwijl de class-transformer
package maakt het mogelijk om objecten te transformeren in een instantie van een klasse, een klasse in een object te transformeren en objecten te serialiseren of te deserialiseren op basis van bepaalde criteria.
De acht door Nest geleverde leidingen zijn:
ValidationPipe
ParseArrayPipe
ParseIntPipe
ParseUUIDPipe
ParseBoolPipe
DefaultValuePipe
ParseEnumPipe
ParseFloatPipe
Om de validatie in Nest in deze handleiding aan te tonen, gebruiken we de ingebouwde ValidationPipe
dat het mogelijk maakt om validatie op verzoek van payloads af te dwingen en goed combineert met de class-validator
pakket; specifieke regels worden gedeclareerd met eenvoudige annotaties in de declaraties van Data Transfer Object/lokale klassen in elke module.
Om te beginnen met het gebruik van de ingebouwde ValidationPipe
waaruit wordt geëxporteerd @nestjs/common
, laten we de class-validator
en class-transformer
pakketjes:
$ npm i --save class-validator class-transformer
# Or
$ yarn add class-validator class-transformer
# Or
$ pnpm install class-validator class-transformer
Navigeer vervolgens naar main.ts
waar we zullen binden ValidationPipe
op het rootniveau van de applicatie om ervoor te zorgen dat alle eindpunten in onze app beschermd zijn tegen het ophalen van ongeldige gegevens:
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Vervolgens voegen we in de Data Transfer Object-declaraties van elke module een paar validatieregels toe door de juiste gegevenscontroles voor elke afzonderlijke gegevens te declareren. In ons geval zouden we passende validatieregels definiëren voor name
en email
in UpdateUserDto
:
import { IsEmail, IsString } from 'class-validator';
export class UpdateUserDto {
@IsString()
name: string;
@IsEmail()
email: string;
}
De @IsString()
decorateur controleert of bepaalde gegevens een echte string zijn, en de @IsEmail()
validator controleert of bepaalde gegevens een e-mail zijn, anders retourneert deze false en genereert een uitzondering.
Als we nu proberen een PATCH
verzoek aan een gebruikersprofiel en voer een nummer in in plaats van een geldig e-mailadres; er wordt bijvoorbeeld een uitzondering gegenereerd:
Hiermee hebben we een hele mooie validatie in onze Nest-app.
Tijdens het valideren met ValidationPipe
, is het ook mogelijk om onze eigenschappen te filteren waarvan we niet willen dat onze methode-handler deze ontvangt. Bijvoorbeeld als onze begeleider alleen maar verwacht name
en email
eigenschappen, maar een verzoek omvat ook a country
eigendom, kunnen we de country
eigenschap van het resulterende object door instelling whitelist
naar true
wanneer we instantiëren ValidationPipe
:
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
await app.listen(3000);
}
bootstrap();
Bindende pijpen op methodeparameterniveau
Er kunnen ook leidingen worden gedefinieerd params
, ook. Hiervoor binden we de pijp op het parameterniveau van de methode.
Vroeger, ook al definieerden we de userId
om een nummer te zijn, zou je merken dat als we een verzoek indienen bij de userId
als string blijkt het succesvol, ongeacht:
Om ervoor te zorgen dat de waarde van userId
moet altijd een getal zijn, wij verklaren dat dit bindend is getUser()
methodehandler met een validatiecontrole die hetzelfde garandeert:
...
import { ParseIntPipe } from '@nestjs/common';
@Get('/:userId')
getUser(@Param('userId', ParseIntPipe) userId: number) {
return this.userService.getUser(userId);
}
getUser(userId: number) {
return { userId };
}
De ParseIntPipe
definieert de ingebouwde ParseInt Pipe en zorgt ervoor dat de gegevens waarmee het wordt uitgevoerd een geheel getal moeten zijn.
Wanneer we nu een GET
verzoek aan een invalide userId
van string “ab”, mislukt de validatie en wordt er een uitzondering gegenereerd met a 400
status code:
Maar met een numerieke waarde verloopt de validatie met succes:
We kunnen ook andere methodehandlers dienovereenkomstig bijwerken om een goede validatie te garanderen:
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
Post,
Req,
} from '@nestjs/common';
import { Request } from 'express';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUsers() {
return this.userService.get();
}
@Get('/:userId')
getUser(@Param('userId', ParseIntPipe) userId: number) {
return this.userService.getUser(userId);
}
@Post()
store(@Req() req: Request) {
return this.userService.create(req);
}
@Patch('/:userId')
update(
@Body() updateUserDto: { name: string; email: string },
@Param('userId', ParseIntPipe) userId: number,
) {
return this.userService.update(updateUserDto, userId);
}
@Delete()
delete(@Param('userId', ParseIntPipe) userId: number) {
return this.userService.delete(userId);
}
}
import { Injectable } from '@nestjs/common';
import { Request } from 'express';
import { UpdateUserDto } from './dto/user-update.dto';
@Injectable()
export class UserService {
get() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
getUser(userId: number) {
return { userId };
}
create(req: Request) {
return req.body;
}
update(updateUserDto: UpdateUserDto, userId: number) {
return { body: updateUserDto, userId };
}
delete(userId: number) {
return { userId };
}
}
Nu hebben we gezorgd voor de beste praktijktechniek voor het valideren van gegevens die op elk moment in onze applicatie binnenkomen, misschien vanuit een externe bron.
Conclusie
In deze gids heb je meer te weten kunnen komen over het nieuwste kind in het Node.js-blok; Nest.js, en alles wat nodig is om u op weg te helpen als u er een applicatie mee wilt bouwen. Je hebt geleerd wat Nest is, wat de functies ervan zijn, hoe je een Nest-project maakt, hoe je binnenkomende gegevens in een Nest-app verwerkt en hoe je de binnenkomende gegevens valideert. Al met al heb je kennis gemaakt met de bouwstenen van elke Nest-app en de waarde die elk onderdeel toevoegt aan een Nest.js-app.
Vanaf dit punt valt er nog zoveel te leren over het bouwen van een applicatie op bedrijfsniveau met Nest, maar je hebt met succes fundamentele concepten kunnen behandelen die je op weg kunnen helpen met alles wat je te wachten staat.
Kijk uit naar een nieuwe gids in de toekomst, waarin we leren hoe we een rustgevende API kunnen bouwen met Nest en MySQL.
Bedankt voor het lezen!