- State Caching mit NGXS in Angular-Anwendungen - 22. Mai 2020
- Websockets mit Nest.js - 30. Juli 2019
- Konditionale Custom Validator in Angular - 22. Juli 2019
Mit Node.js hält JavaScript immer mehr Einzug auf der Serverseite. Den großen Sprung in die Enterprise-Welt hat Node.js aber noch nicht geschafft. Das Framework Nest.js tritt an, dies zu ändern.
Das Hauptproblem, warum Node.js noch nicht abgehoben ist, sehen die Entwickler von Nest.js in der Architektur von Node.js-Anwendungen. Mit ihrem Framework wollen sie das Entwickeln von testbaren, skalierbaren und wartbaren Node.js-Anwendungen ermöglichen. Ob das gelungen ist, und um einen Überblick über Nest.js zu geben, wird das Framework im Folgenden näher betrachtet.
Die Entwickler:innen schreiben selbst, dass sie sich von Angular inspirieren ließen. Das erkennt man schon daran, dass es out-of-the-box mit TypeScript kommt. Im Hintergrund verwendet Nest.js das bekannte Framework Express. Außerdem ist Nest.js kompatibel mit vielen Bibliotheken wie beispielsweise Fastify oder bereits verfügbaren Third-Party Plugins.
Ein neues Projekt starten
Natürlich kommt Nest.js mit einer Command Line Interface, die Nest CLI. Um die CLI global zu installieren, benutzen wir folgenden Command:
$ npm i -g @nestjs/cli
Zur Demonstration schreiben wir eine Anwendung, die Daten von zwei Ländern vergleicht. Damit wir nicht komplett von “Scratch” beginnen müssen, klonen wir das Starter-Projekt vom Nest.js Repository:
git clone https://github.com/nestjs/typescript-starter.git world-comparison
cd world-comparison
npm install
Die Anwendung kann man mit dem bekannten Node-Command starten:
$ npm run start
Wir wollen aber nodemon verwenden, was bereits im Starterprojekt enthalten ist. Nodemon trackt Codeänderungen und startet den Server automatisch neu.
$ nodemon
Der Server startet auf dem Port 3000. Falls der Port bereits verwendet wird, kann der Port in der Datei src/main.ts
geändert werden.
Der Controller
Das Starterprojekt enthält bereits auch einen ersten Controller namens AppController
. Ein Blick in den Controller lässt schon erahnen, was er macht:
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
Der Controller empfängt GET-Requests und führt die Methode getHello()
aus. Die Methode wiederum holt sich vom AppService
einen String und gibt ihn zurück. Rufen wir also localhost:3000 auf, sehen wir im Browser den String “Hello World!”.
Schauen wir uns den AppController
genauer an. Zunächst ist es eine simple JavaScript-Klasse, angereichert mit einem @Controller()
Decorator. Im Decorator kann eine Route angegeben werden, z.B. @Controller('world')
. Dadurch ändert sich der Pfad zu localhost:3000/world.
@Get()
markiert eine Methode, die bei einem GET-Request ausgeführt wird. Gibt die Methode ein JavaScript Objekt oder Array zurück, wird der Return-Value automatisch serialisiert.
Controller und Service anlegen
Mithilfe der Nest CLI lassen sich Controller schnell erstellen:
nest g controller controllers/density
Der neue Controller soll etwas mehr Funktionalität erhalten. Er soll zwei Ländernamen als Queryparameter empfangen und zurückgeben, welches Land dichter besiedelt ist. Die Daten holen wir über die offene API von restcountries.eu.
Der Controller soll dazu über einen Service mit der API kommunizieren. Also erstellen wir einen Service mit der CLI:
nest g service services/country
Der neue Controller DensityController
und der neue Service CountryService
wurden automatisch im AppModule importiert:
@Module({
imports: [],
controllers: [AppController, DensityController],
providers: [AppService, CountryService],
})
export class AppModule {
}
Die Projektstruktur sieht nun wie folgt aus:
HTTP Requests mit Nest.js
Die Länder-Daten werden mit HTTP Requests von restcountries.eu abgerufen. Hierfür wird der HTTP Client Axios in Nest.js verwendet. HttpService
ist Teil von @nestjs/common
und lässt sie von dort im CountryService
importieren:
import { HttpService } from '@nestjs/common';
Außerdem muss HttpModule
im AppModule
-Kontext verfügbar sein. Daher importieren wir HttpModule
in AppModule
und fügen es den Imports
hinzu.
import { HttpModule, Module } from '@nestjs/common';
// ...
@Module({
imports: [
HttpModule,
],
// ...
})
export class AppModule {
}
Anschließend fügen wir der noch leeren CountryService
-Klasse einen Konstruktor und eine Methode hinzu:
@Injectable()
export class CountryService {
constructor(private readonly httpService: HttpService) { }
getCountry(country: string): Observable<AxiosResponse<CountryDto>> {
return this.httpService.get(`https://restcountries.eu/rest/v2/name/${country}`);
}
}
Betrachten wir den Rückgabewert von getCountry()
genauer. Die get
-Methode von HttpService
gibt ein Observable zurück. Dies ist ein generischer Typ, mit dem ein Typargument angegeben werden muss. Hierfür stellt Axios die Interface AxiosResponse
bereit. Das Typargument von AxiosResponse
ist CountryDto
. Dieses Data Transfer Object müssen wir noch anlegen:
$ nest g class services/country/CountryDto
Mit der Angabe des Pfades wird das DTO in dem Pfad erstellt, wo CountryService
liegt.
Die API von restcountries.eu gibt sehr viele Informationen eines Landes zurück. Für unseren Zweck brauchen wir nur “name”, “population” und “area”. Deshalb definieren wir das DTO wie folgt:
export class CountryDto {
readonly name: string;
readonly population: number;
readonly area: number;
}
Das DTO muss noch im Service importiert werden:
import { CountryDto } from './country-dto';
Der Service ist nun bereit, um im Controller verwendet zu werden. Hier wird die Logik implementiert. Die Schnittstelle soll die Bevölkerungsdichte zweier Länder vergleichen. Dazu ruft der CountryService
die Informationen beider Länder mit jeweils einem Request ab. Wir warten auf die Antwort der restcountries-API, indem wir async/await
verwenden. getCountry()
gibt ein Observable zurück, welches mit toPromise()
zu einem Promise umgewandelt wird. Sobald die Daten beider Länder verfügbar sind, vergleichen wir die Bevölkerungsdichte und geben ein Promise zurück.
@Get()
async getDensestCountry(
@Query('country1') country1: string,
@Query('country2') country2: string,
): Promise<string> {
const res1 = await this.countryService.getCountry(country1).toPromise();
const res2 = await this.countryService.getCountry(country2).toPromise();
const c1: CountryDto = res1.data[0];
const c2: CountryDto = res2.data[0];
if (c1.population / c1.area > c2.population / c2.area) {
return `${c1.name} is denser than ${c2.name}.`;
} else {
return `${c2.name} is denser than ${c1.name}.`;
}
}
Wir haben nun eine Schnittstelle, die auf der Route /density
abrufbar ist und zwei Queryparameter conutry1
und country2
erwartet. Das wollen wir mit Postman testen:
Die Schnittstelle gibt erfolgreich einen String zurück!
Nächste Schritte
Die Dokumentation von Nest.js ist übersichtlich gestaltet und bietet Informationen von Authentifizierung über Middleware bis Datenbankanbindung. Wer das Beispiel-Projekt aus diesem Blogartikel lokal ausführen möchte, findet den Code auf GitHub.