Mittlerweile sind viele von uns sehr vertraut im Entwickeln clientseitiger Apps mit Angular und TypeScript. Bauen wir diese im Handumdrehen, brauchen wir irgendwann wahrscheinlich eine serverseitige API, um mit echten Daten umzugehen. Es wäre nur natürlich, im Backend die gleichen Sprachen zu nutzen wie im Frontend. Also entscheiden wir uns z.B. für Node.js. Doch wenn wir reines Node oder Bibliotheken wie Express.js nutzen, betreten wir Frontend-Entwickler oft unbekanntes Terrain. Uns fehlen Idiome und strukturelle Muster.

Statt im Dickicht von zahlreichen Low-Level Bibliotheken in Verbindung mit Express zu versinken, wollen wir uns NestJS anschauen. Dieses Framework ermöglicht es uns, effiziente, zuverlässige und skalierbare serverseitige Anwendungen zu erstellen. Bemerkenswert daran ist, dass es sich ähnlicher Konzepte bedient wie Angular.

Im Folgenden schauen wir uns an,  wie eine Projektstruktur mit Angular-Front- und Nest-Backend aussehen kann und schreiben unsere ersten API-Routen. Weil es so einfach ist, erstellen wir nebenbei eine API-Dokumentation und werfen einen Blick auf weitere nützliche Plugins. Am Ende sollten wir gerüstet sein, eine Full-Stack-Webanwendung durch die Symbiose von Angular und Nest umzusetzen.

Weitere Experten-Beiträge mit Tipps für die Arbeit mit Angular finden Sie in unserem E-Book. Jetzt kostenlos downloaden.

Angular kurz und bündig

Was wir häufig an Angular schätzen, ist seine klare Struktur und die festen Wege, wie wir Anwendungen schreiben. In einem gut aufgesetzten Angular Open-Source-Projekt findet man sich schnell zurecht, man kennt die Bestandteile, aus denen die Applikation besteht und weiß, welche Wechselwirkung es zwischen ihnen gibt.

Kurz gefasst fühlt sich das wie folgt an: Mit der Angular CLI können wir zügig ein gesamtes Projekt oder später Artefakte samt dem dazugehörigen Boilerplate-Code erzeugen. Unsere Benutzeroberfläche bauen wir zusammen aus einem Baum von vielen kleinen, eher controlartigen Komponenten, die in einigen wenigen seitenartigen Komponenten miteinander verknüpft werden. Die Seiten bedienen sich sogenannten Services, die wiederum für die Datenverarbeitung und Anwendungskommunikation zuständig sind. Zuletzt ordnen wir diese Artefakte in verschiedene Module ein, die jeweils zusammengehörige Pakete bilden und jeweils eigene Anwendungsbereiche abdecken.

Abbildung - Angular Anwendungsarchitektur - NestJS

Eine typische Angular Anwendungsarchitektur: Aufgeteilt in Module liegen unsere verschiedenen Komponentenbäume, die die Oberfläche entstehen lassen und ihre Daten über Services beziehen.

Dabei nutzen wir alle uns zur Verfügung stehenden Werkzeuge wie TypeScript, Linting (die statische Analyse unseres Quellcodes, die uns hilft, gewisse Pattern einzuhalten), das Live-Reload Feature unseres Browsers und die hilfreiche Testinfrastruktur, die uns die CLI bietet.

Beispiel: NgCompanion*

Stellen wir uns vor, wir wollten eine Social Media Plattform namens NgCompanion entwickeln. In dieser meldet man sich an, hinterlässt ein paar Profilinformationen und nutzt die Applikation, um Freunde und Gleichgesinnte in der Umgebung zu finden.

Abbildung - Client-App NgCompanion - NestJS

Screenshot unserer Client-App NgCompanion. Wir sind angemeldet als Andre Gular und sehen in der Übersicht eine Reihe interessanter Bekannte in der Nähe.

Unser Frontend haben wir mit Angular bereits aufgesetzt und entwickelt. Darin befinden sich verschiedene Komponenten, beispielsweise zum Anzeigen unseres Benutzerprofils, der Darstellung eines potenziellen Freundes oder die Seiten-Komponente, die die Liste der Freunde rendert. Diese Listenübersicht bezieht ihre Daten letztlich über einen Angular-Service CompanionsService. Innerhalb dieses Service kann man den HttpService nutzen, um über eine REST API an diese Daten aus dem Backend zu gelangen:

Angular-Service, der über eine REST API die interessanten Bekannten in der Nähe, zur Darstellung in der Client-App, zurückliefert.

Zur Umsetzung dieser REST API wollen wir nun die dazugehörige Node.js Backend-Schnittstelle entwickeln. Dies könnten wir mit der Node Bibliothek Express tun. Damit sind wir sehr flexibel, müssten uns die Bibliothek über npm installieren und könnten dann direkt loslegen. Standardmäßig stehen uns dann aber nicht direkt all die Tools wie TypeScript, Live-Reload oder eine klar vorgegebene Struktur zur Verfügung. Best Practices sind zwar vorhanden, müssen wir uns jedoch selbst erst mühsam erarbeiten und in unserem Projekt umsetzen. Für Frontend-Entwickler, die seltener Backend-Systeme aufsetzen, eine ziemlich große Einstiegshürde. Könnte das nicht etwas zugänglicher sein?

Vor- und Nachteile beim Einsatz eines Frameworks wie Express.js zur Umsetzung einer Node.js Backend-Schnittstelle:

  • Große Flexibilität
  • Wenig Abhängigkeiten
  • Schmales, unkompliziertes Framework
  • Wenig vorgegebene Struktur
  • Viel manuelle Konfiguration notwendig
  • Standardmäßig kein TypeScript
  • Live-Reload muss erst konfiguriert werden
  • Architektur ist dem Anwendungsentwickler überlassen
  • Wenig Wiedererkennungswert: Jede Express-Applikation mag anders aussehen

NestJS

Genau hier kommt NestJS, kurz Nest, ins Spiel. Laut Webseite “ein progressives Node.js-Framework für die Erstellung effizienter, zuverlässiger und skalierbarer serverseitiger Anwendungen”. Es wurde von Kamil Mysliwiec ins Leben gerufen, ist unter MIT Lizenz Open Source, hat zahlreiche Unterstützer und wurde 2019 als das auf GitHub populärste Node.js-Framework gekürt. Zum Zeitpunkt dieses Artikels ist das Framework in Version 7 erschienen. Es verfügt über eine ausgezeichnete Dokumentation, die uns auch über dessen Kernfunktionalität informieren kann. Unter der Haube nutzt Nest standardmäßig Express, kann aber hier auch andere Bibliotheken wie beispielsweise Fastify einsetzen. Es liefert eine vorgeschriebene Applikationsarchitektur für unser Backend, die sich aus ähnlichen Artefakte wie Angular zusammensetzen und mit Dekoratoren annotiert werden.

Aufsetzen des Backend-Projekts

Auch Nest verfügt über eine CLI, mit der wir unsere Backend-Anwendung erstellen können. Es bietet sich an, sowohl das Front- als auch das Backend in einem gemeinsamen Repository, aber mit eigenständigen Abhängigkeits-Definitionen, eingetragen in der package.json, zu erstellen. So können wir später gemeinsam genutzte Datentransfer-Objekte im Wurzelverzeichnis anlegen und zwischen beiden Welten teilen.

Mit folgendem Befehl erstellen wir uns ein neues Nest-Projekt, welches wir wie vorgeschlagen neben unserem Frontend-Projekt in einem eigenen backend Ordner verorten:

> npx @nestjs/cli new ng-companion-backend

Wir nutzen das npx Kommando, dass uns seit npm Version 5.2.0 erlaubt, Pakete ohne die Notwendigkeit einer globalen Installation zu nutzen. Da danach Nest und seine CLI selbst als Abhängigkeit im Projekt vorhanden ist, brauchen wir zu keinem eine globale Installation.

Öffnen wir nun den neu erstellten Ordner und werfen einen Blick in die dort erstellten Dateien und die vorgegebene Ordnerstruktur, so könnten uns einige Parallelen in Bezug auf die Dateistruktur einer Angular-Applikation auffallen, wie beispielsweise die nest-cli.json als zentrale Konfigurationsdatei ähnlich zur angular.json.

Abbildung - Front- und Backend im Monorepo - NestJS

Screenshot von Front- und Backend im Monorepo. Initiale Ordner- und Dateistruktur einer Nest-Applikation.

Mit folgendem Befehl starten wir unser Backend im Watch-Modus und können das Backend standardmäßig über die URL http://localhost:3000 erreichen:

> npm run start:dev

Abbildung - Backend durch :dev im Watch-Modus

Startet das Backend durch :dev im Watch-Modus und liefert es auf http://localhost:3000 aus.

 

Bestandteile einer Nest-Applikation

Schauen wir uns die Inhalte des src Ordners an, sehen wir die typischen Bestandteile einer Nest-Applikation. Der initiale Einstiegspunkt ist die Datei main.ts. Hier findet das Bootstrapping des Backends statt. Diese Datei ist auch der Ort, an dem grundlegende Änderungen an der Plattformkonfiguration vorgenommen werden, wenn z.B. statt Express Fastify als unterliegendes Framework genutzt werden soll.


Inhalt der main.ts zum Start des Nest Backends. Die Konfiguration der Plattform, das Vornehmen zusätzlicher Einstellungen wie CORS, das Zuweisen des Ports oder das Hinzufügen von optionalen Plugins findet hier statt.

Neben diesem Einstiegspunkt findet man eine Reihe von Dateien mit dem Präfix app im Quellcode-Verzeichnis. Hier erkennt man alle grundlegenden Bausteine einer Nest-Applikation, die sehr verwandt zu den Bausteinen eines Angular Frontends sind.

Controller (app.controller.ts)

Ähnlich der Components in Angular sind Controller in Nest die Einstiegspunkte zur Verarbeitung verschiedener URL-Routen, sozusagen die äußerste Schnittstelle der Applikation zum Benutzer.

Service (app.service.ts)

Während die Controller nur für die erste Verarbeitung der von außen erreichbaren URL-Routen zuständig sind, leiten sie anschließend weiter zu den verschiedenen Services, die wiederum den Kern der Applikationslogik enthalten sollten. Ähnlich wie auch Services in Angular, können Controller Services mit Hilfe der Dependency Injection über den Konstruktor anfordern.

Module (app.module.ts)

Zuletzt teilt man wie bei Angular in einem Nest Backend verschiedene zusammengehörige Bereiche in Module auf, um so eine wartbare, austausch- und erweiterbare Gesamtarchitektur aufzubauen. In den Modulen werden dafür notwendige Controller und Services registriert.

Implementierung der ersten Backend Logik

Wir möchten nun die für unser Frontend notwendige GET-Routen implementieren, die uns die Liste aller Freunde in der Nähe sowie Details zu einem bestimmten Freund, den wir per Id auswählen, liefern:

  • http://localhost:3000/companions
    liefert die Liste aller verfügbaren Freunde
  • http://localhost:3000/companions/:id
    liefert Details zu dem Freund einer bestimmten ID

Controller

Dazu erstellen wir uns mit Hilfe der Nest CLI einen Controller zur Behandlung dieser Routen. Die CLI fügt diesen Controller auch automatisch dem nächstgelegene Modul hinzu und erstellt ebenfalls einen Unit-Test mit der Endung .spec.ts, wie wir es aus Angular kennen:

> nest generate controller companions

> nest g co <controller-name> # Kurzform

Erstellen eines Nest Controllers mit der CLI in Lang- oder Kurzform.

Analog zu Komponenten in Angular (@Component Decorator), ist der Controller mit dem Decorator @Controller versehen. Hier sollte der Name der Route (companions) angegeben werden. Wir definieren nun über den @Get Decorator jeweils eine Methode, die die Routen-Anfragen entgegennimmt und verarbeitet:


Grundgerüst der ersten zwei GET-Routen zur Verarbeitung der Routen /companions und /companions/:id. An Stelle von Promises, können auch Observables verwendet werden.

Damit wir bei der zweiten Route Zugriff auf die in der URL übergebene ID bekommen, müssen wir lediglich den Parameternamen im @Get Decorator angeben und den entsprechenden Methodenparameter mit @Param markieren. Nest kann an dieser Stelle sowohl mit synchronen wie auch asynchronen Methoden auf Basis von Promises oder Observables umgehen.

Services

Um nun die eigentlichen Daten beispielsweise aus verschiedenen Quellen, wie z.B. einer Datenbank zu laden, zu kombinieren und aufzubereiten, lagern wir diese Kernfunktionalität in Services aus, die wiederum der Controller nutzt. Wir erstellen auch diesen mit der CLI:

> nest generate service companions

> nest g s <service-name> # Kurzform

Erstellen eines Nest Services mit der CLI in Lang- oder Kurzform.

Ein solcher Service muss wie bei Angular mit dem Decorator @Injectable annotiert sein. Ein Service kann wiederum andere Services beinhalten und beliebige komplexe Logik implementieren. An unserer Stelle gehen wir davon aus, dass wir eine existierende Datenbank-Schnittstelle nutzen und von dieser entsprechende Informationen abfragen.

Angedeutete Service-Implementierung zur Datenbeschaffung unserer Companions.

Der Service kann daraufhin in Modulen bereitgestellt und von Controllern angefragt werden. Dazu genügt es, sich den Service über den Konstruktor injizieren zu lassen. Ähnlich zu Container-Komponenten in Angular erkennt man, dass der Controller in Nest nur eine sehr schmale Schicht ist, der Nutzerdaten entgegennimmt, diese für die Weiterverarbeitung aufbereitet und die eigentliche Logik innerhalb verschiedener Services stattfindet.


Injizierung des zuvor implementierten CompanionsService und dessen Verwendung innerhalb des Controllers.

Module

Zuletzt müssen Controller und Services noch in einem Modul registriert werden. Dies wurde durch die CLI bereits in dem aktuell einzigen Modul, dem app.module, vorgenommen. Es könnte jedoch sinnvoll sein, dass wir unseren Bereich Companions in ein eigenes gleichnamiges Modul verschieben. Auch dieses kann mit der CLI erstellt werden:

> nest generate module companions

> nest g mo <service-name> # Kurzform

Erstellen eines Nest Moduls mit der CLI in Lang- oder Kurzform.

In dieses Modul platzieren wir in unserem Fall unseren Controller sowie den Service.


Das CompanionsModule enthalten dazugehörige Controller und Services.

Einzelne Module können dann wiederum auf der obersten App-Ebene mit in die Applikation aufgenommen werden. So könnte man neben dem CompanionsModule noch viele weitere andersartige Teilbereiche des Backends unabhängig davon implementieren.


Das Root-Modul AppModule bündelt alle sogenannten Feature-Module, wie beispielsweise unser CompanionsModule.

POST-Route

Haben wir aktuell nur Daten aus dem Backend abgefragt, so ist es auch möglich, Daten an das Backend zu senden. Möchten wir aus dem Frontend heraus zum Beispiel die Kontaktaufnahme mit einem ausgewählten Freund initiieren, so können wir hierzu einen POST-Request implementieren. Dazu müssen wir den @Post Decorator an einer Methode verwenden und geben als Parameter das mit @Body annotierte Datentransfer-Objekt an. Es bietet sich an, für größere Objekte eigene TypeScript Klassen anzulegen. Nest empfiehlt hier Klassen gegenüber Interfaces, da diese nicht zur Laufzeit entfernt werden und Nest mit Middlewares oder Pipes zusätzliche Transformationen vornehmen kann.


Implementierung einer POST Route, die über ihren Body das zuvor als TypeScript Klasse definierte Datentransfer-Objekt entgegennimmt.

Das Ergebnis

Mit dem Befehl npm start können wir unser Backend kompilieren und starten lassen. Über die URL http://localhost:3000/companions können wir uns alle Bekannten als JSON Objekt anfordern. Die gleiche Route würde unser Angular Client nutzen. Über die POST-Route kann unsere App gegenüber dem Backend eine Kontaktanfrage zu stellen.

Abbildung - Ergebnis der GET-Route

Das Ergebnis der GET-Route über http://localhost:3000/companions.

Darüber hinaus

Die vorherigen Abschnitte geben einen ersten Einstieg in die Backend-Entwicklung mit Nest. Mit Hilfe der vorgestellten Konzepte lassen sich bereits erste Full-Stack Anwendungen schreiben. Wir wollen nun noch einen kurzen Blick auf weitere nützliche Bestandteile von Nest werfen.

Unit Testing

Wie wir gesehen haben, erstellt die Nest CLI automatisch .spec-Dateien, in denen wir Tests für unsere Controller und Services schreiben können. Dabei nutzt Nest Jest als Testing Framework, welches auch zunehmend in der Angular Community Anklang findet. Der Aufbau und die Syntax folgen dabei wie bei Angular mit Karma und Jasmine dem BDD Stil. Ein manuelles Aufsetzen einer Testinfrastruktur entfällt, und wir können zeitgleich zur Umsetzung unsere Tests schreiben.

Swagger Dokumentation

Ähnlich wie man im Frontend Tools wie Storybook nutzen kann, um Komponenten ohne eine laufende Anwendung in Isolation zu entwickeln, möchte man vielleicht auch das Backend unabhängig eines darauf zugreifenden Clients implementieren. Hierzu bietet Nest die Möglichkeit, eine interaktive API Dokumentation mit Hilfe von Swagger zu erstellen.

Als erstes fügen wir das Swagger Plugin zu unserem Projekt hinzu:

> npm install --save @nestjs/swagger swagger-ui-express

Hinzufügen der Abhängigkeiten von Swagger und Swagger-UI.

Anschließend initialisieren wir Swagger beim Bootstrapping in der main.ts und können dort erste Dokumentations-Metadaten hinterlegen:


In der main.ts initialisieren wir die automatische Dokumentation mit Hilfe von Swagger.

Nun haben wir über Dekoratoren die Möglichkeit, unsere API zu beschreiben. Alle möglichen Dokumentationsarten können der Nest Homepage entnommen werden. Wir können beispielsweise unsere GET-Route mit folgendem Decorator versehen:


Annotation der GET-Route mit den Dekoratoren @Api… des Swagger Plugins.

Starten wir nun wieder unser Backend, können wir unter http://localhost:3000/api auf unsere Dokumentation zugreifen und sogar mit dem Backend interagieren. Über die Oberfläche können wir Routen-Parameter setzen oder ganze POST-Bodies definieren und brauchen so dazu kein lauffähiges Frontend.

Abbildung - Swagger Dokumentation - NestJS

Die Swagger Dokumentation über http://localhost:3000/api.

Eine Lösung zu jeder Aufgabe

Über diese Funktionalitäten hinaus bietet Nest eine Vielzahl verschiedener Hilfsmittel zur Lösung gängiger Problemstellungen. Deren vollständige Vorstellung würde den Rahmen des Artikels sprengen, die offizielle Dokumentation sollte aber als gute Anlaufstelle dienen. Beispielsweise gibt es auch Unterstützung für WebSockets, GraphQL, NoSQL und SQL Datenbanken, Microservices und vieles mehr.

Angular ♥ NestJS – Fazit

Als Angular Frontend-Entwickler, der gelegentlich in die Backend-Entwicklung eintauchen möchte, findet man mit NestJS ein Framework, bei dem man ähnlichen Konzepten begegnet, sich in eine saubere, wegweisende Architekturen eingliedern und mit den gleichen Sprachen und Tools wie im Frontend entwickeln kann. Wie Angular besitzt auch Nest eine hervorragende Developer-Experience, einen leichten Einstieg durch seine CLI und eine ausgezeichnete Online-Dokumentation. Es liefert nicht zuletzt durch sein sehr umfangreiches Set an Plugins und Rezepten einen großen Baukasten, von dem man profitieren kann. Aus meiner Sicht sind Angular und Nest zwei Frameworks, die schlichtweg füreinander geschaffen wurden und mit deren Hilfe die Entwicklung von Full-Stack Web-Anwendungen zu einem abgerundeten, angenehmen Erlebnis wird.

David Würfel

Große Auswahl an günstigen Domain-Endungen – schon ab 0,08 € /Monat
Jetzt Domain-Check starten