Lesezeit: 8 Minuten
Nest.js für Node.js-Applikationen
Node.js ist mittlerweile kein neuer Spieler mehr, wenn es um Backend-Technologien geht. Allerdings steigt die Akzeptanz und Verbreitung der Plattform erst in den letzten Jahren zunehmend, sodass sich immer mehr Entwickler mit dieser Technologie auseinandersetzen müssen. Einer der größten Vorteile von Node.js, der gleichzeitig ein Nachteil der Plattform ist, ist der leichtgewichtige Charakter. Node.js verfügt…
Node.js ist mittlerweile kein neuer Spieler mehr, wenn es um Backend-Technologien geht. Allerdings steigt die Akzeptanz und Verbreitung der Plattform erst in den letzten Jahren zunehmend, sodass sich immer mehr Entwickler mit dieser Technologie auseinandersetzen müssen. Einer der größten Vorteile von Node.js, der gleichzeitig ein Nachteil der Plattform ist, ist der leichtgewichtige Charakter. Node.js verfügt lediglich über eine Handvoll von Kernmodulen mit deren Hilfe sich sogar umfangreiche Webapplikationen umsetzen lassen. Der Preis hierfür ist jedoch, dass alle wichtigen Features, beispielsweise Routing oder ein Plugin-Layer für Webapplikationen, von Hand umgesetzt werden müssen. Außerdem gibt Node.js keinerlei Struktur für Applikationen vor. Das beginnt beim Fehlen eines Typsystems und endet bei der Verzeichnisstruktur. Node.js-Entwickler sind also völlig frei in der Gestaltung von Applikationen, was gerade für Einsteiger eine nahezu unüberwindbare Hürde darstellt. Und genau an dieser Stelle kommen Frameworks wie Nest.js ins Spiel.
Nest.js ist ein Framework, das in TypeScript geschrieben ist, und eine Abstraktionsebene über dem HTTP-Modul von Node.js darstellt. Zwischen Node.js und Nest.js liegt noch eine weitere Abstraktionsschicht, die durch ein Web-Application-Framework gebildet wird. Standardmäßig kommt hier Express zum Einsatz. Nest.js ist jedoch so flexibel gestaltet, dass sich diese Schicht mit wenigen Zeilen Code austauschen lässt. Jeder Abstraktionslayer fügt zusätzliche Features und Schnittstellen hinzu, die die Implementierung von Anwendungen erheblich erleichtern. Mit TypeScript wird die Einstiegshürde für Umsteiger aus streng typisierten Sprachen deutlich reduziert. Gleichzeitig kommen Entwickler in den Genuss der Vorteile einer typisierten Sprache.
Im Gegensatz zu Node.js macht Nest.js sehr wohl Vorgaben, was die Architektur einer Applikation anbelangt. Nest.js lehnt sich sehr stark an die Entwurfsmuster des Frontend-Frameworks Angular an. Beispiele hierfür sind die Dependency Injection, die Arbeit mit Decorators oder das MV*-Muster (nachzulesen als MVC, MVVM, MVP). Aktuell ist Express der Defacto-Standard unter den Frameworks, wenn es um die Entwicklung von Webapplikation geht. Seit etlichen Jahren hat sich das Framework jedoch nicht mehr weiterentwickelt. Mit Nest.js kommt wieder frischer Wind in die serverseitige Entwicklung mit JavaScript, da es moderne Entwurfsmuster aus dem Frontend aufgreift und serverseitig umsetzt.
Erste Schritte mit Nest.js
Ein Ziel der Entwickler von Nest.js ist, dass es für einen Einsteiger leichter wird, mit der Umsetzung einer Applikation zu beginnen. Hierfür haben sie sich Inspiration aus der Frontend-Welt geholt und ein intuitives CLI zur Verfügung gestellt, mit dem sich eine Applikation mit einem einzigen Kommando initialisieren lässt. Installiert wird die Nest-CLI mit dem Kommando
npm install -g @nestjs/cli.
Nach der Installation wird die Applikation mit
nest new todo-app
initialisiert. Dabei steht todo app
für den Projektnamen. Dieser wird gleichzeitig als Verzeichnisname verwendet. Während der Initialisierung können bestimmte Aspekte der Applikation konfiguriert werden. Beispielsweise fragt die CLI nach Metadaten wie Beschreibung, Versionsnummer, Autor und schließlich dem zu verwendenden Paketmanager. Nachdem die Nest-CLI die grundlegende Struktur erzeugt und alle erforderlichen Abhängigkeiten heruntergeladen hat, kann die Applikation im Entwicklungsbetrieb mit dem Kommando
npm run start:dev
gestartet werden. In diesem Fall wird nodemon
dafür verwendet, den Prozess bei Änderungen automatisch neu zu starten, damit diese sofort wirksam werden. Die Applikation ist nach dem Start im Browser über http://localhost:3000 erreichbar.
Die Struktur einer Nest.js-Applikation
Wie erwähnt macht Nest.js eine Reihe von Vorgaben für die Struktur der Applikation. Diese orientieren sich an den Richtlinien von Angular. Die von der CLI erzeugte Basis-Applikation verfügt mit Modul, Controller und Service über die drei wichtigsten Bausteine einer Applikation.
Module
Das Modul spielt hierbei die Rolle eines Containers, in dem die Elemente für die Dependency Injection registriert werden. Das Modul wird durch den @Module
-Decorator eingeleitet, mit dessen Hilfe lassen sich Metainformationen für das Modul definieren. Die Struktur des App-Moduls sieht beispielsweise wie folgt aus:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Mit der Eigenschaft imports
können weitere Module importiert werden. Mit controllers
werden Klassen importiert, die für bestimmte Pfade der Applikation verantwortlich sind und eingehende Anfragen beantworten. Über providers
werden schließlich Services registriert, die zur Datenhaltung und zur Kapselung der Applikationslogik verwendet werden.
Ein eigenes Modul kann über die CLI mit einem Befehl hinzugefügt werden. Das folgende Kommando erzeugt ein Modul mit dem Namen todo
:
nest generate module todo
Controller
Ein Controller ist eine TypeScript-Klasse, die mit dem @Controller
-Decorator versehen wird.
Der folgende Quellcode zeigt ein Beispiel für einen einfachen Nest.js-Controller.
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
Die Dependency Injection in Nest.js ist, analog zu Angular, als Constructor Injection implementiert. Wird hier nach dem Zugriffsmodifikator eine Variable vom Typ eines registrierten Services angegeben, sorgt der Dependency Injector dafür, dass eine Instanz des Services erzeugt und an die Controller-Klasse übergeben wird. Ist eine Methode des Controllers mit dem @Get
-Decorator versehen, bedeutet das, dass es sich hierbei um eine Routing-Methode handelt. Im Beispiel wird die getHello-Methode ausgeführt, wenn ein Benutzer die URL http://localhost:3000/ aufruft. Der Rückgabewert dieser Methode ist gleichzeitig die Antwort an den Benutzer. Im Beispiel wird das Ergebnis des Funktionsaufrufs appService.getHello
zurückgegeben. Um beispielsweise einen todo
-Controller zu erstellen, wird folgender Befehl verwendet:
nest generate controller todo
Weist ein Controller denselben Namen auf wie ein Modul, wird dieser dem Modul automatisch zugeordnet. Ansonsten kann diese Zuordnung auch erreicht werden, indem der Modulname dem Controllernamen voran gestellt und durch einen Slash getrennt wird. Die Zuordnung sorgt außerdem dafür, dass der Controller automatisch im Modul eingetragen und damit verwendet werden kann. Neben der Controller-Datei wird eine Datei mit der Endung .spec.ts
erzeugt. Diese Datei enthält einen ersten Unittest für den Controller. Tests können in einer Nest.js-Applikation mit dem Kommando npm test
ausgeführt werden. Standardmäßig wird das Test-Framework jest
von Nest.js eingesetzt.
Service
Der @Injectable
-Decorator leitet eine Service-Klasse ein. Diese kann sowohl Datenlieferant als auch Logikcontainer in einer Applikation sein.
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
Im Beispiel liefert die getHello
-Methode lediglich die Zeichenkette Hello World!
, die an den Client gesendet wird. Eine Änderung des Rückgabewertes von Hello World!
zu Hello Client!
führt durch die Kombination aus nodemon
und TypeScript-Compiler dazu, dass der Serverprozess automatisch neu gestartet wird. Der Benutzer muss anschließend lediglich das Browserfenster neu laden, damit auch dort die Änderungen wirksam werden.
Ein todo-Service wird wie folgt zum Projekt hinzugefügt:
nest generate service todo
Auch hier gilt: Hat der Service denselben Namen wie ein bestehendes Modul, wird die Datei im Verzeichnis des Moduls angelegt und der Service im Modul registriert. Anschließend kann der Service innerhalb des Moduls über die Dependency Injection eingebunden und verwendet werden.
Ähnlich wie beim Controller, wird beim Erzeugen eines Services auch eine Test-Datei angelegt.
Fazit
Nest.js bedient sich im Gegensatz zu vielen anderen Node.js-Frameworks und Libraries moderner Paradigmen, mit denen sich Applikationen schnell und effizient umsetzen lassen. Die Verbreitung des Frameworks wächst stetig und mit ihr auch die Community, die sich um das Framework gebildet hat. Durch seine offene Plugin-Infrastruktur ermöglicht es Nest.js einem Entwickler Erweiterungen für das Kernframework zu schreiben und den Funktionsumfang so einfach zu erweitern.