NestJS: Server-Framework auf Steroide 🎉

NestJS ist das Turbo-Framework für NodeJS-Devs! 🚀 Mit TypeScript, OOP & FP wird jede Codezeile zum Vergnügen! 😎

NestJS: Server-Framework auf Steroide 🎉
Photo by Christina @ wocintechchat.com / Unsplash / Image

In einem vorherigen Artikel habe ich schon einmal beschrieben, wie man mit blankem JavaScript und NodeJS eine REST-API bauen kann. Wenn dich der Artikel interessiert, dann schau einmal hier rein 👇

Eigene API mit NodeJS und Express
Du willst dir eine eigene API für deine Dienste bauen? Wir machen es zusammen 👇

Nun ist es aber so, dass es selten bei nur einer Kleinigkeit bleibt. In der Regel wird es später noch Anforderungen geben wie Authentifizierungen, Autorisierung, Ablage von Daten in einer Datenbank oder Ähnliches.

Um das zu vereinfachen und immer wiederkehrenden Code nicht selbst neu schreiben zu müssen, setzt man auf etablierte Frameworks. Im JavaScript/TypeScript-Umfeld hat sich für mich NestJS sehr etabliert und für genau den Fall ausgezeichnet.

Im Kern komme ich aus der Frontend-Entwicklung (um genau zu sein Angular) und wollte mich hier nicht auf eine andere Technologie einlassen und mich in meiner Komfortzone weiter bewegen. Und genau hier kommt NestJS ins Spiel.

Was ist NestJS 🤔

NestJS ist ein serverseitiges, aber durch NodeJS, plattformunabhängiges Framework, welches dir für viele Aufgabenstellungen bereits sehr viele Lösungen und Tools anbietet.

NestJS - A progressive Node.js framework
NestJS is a framework for building efficient, scalable Node.js web applications. It uses modern JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).

Es ist sozusagen die Werkzeugkiste die du im Keller stehen hast und immer darauf zurückgreifst, wenn du etwas spezielles tun willst. Kleines Zitat hierzu aus der Doku:

Nest (NestJS) is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with and fully supports TypeScript (yet still enables developers to code in pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).

Under the hood, Nest makes use of robust HTTP Server frameworks like Express (the default) and optionally can be configured to use Fastify as well!

Nest provides a level of abstraction above these common Node.js frameworks (Express/Fastify), but also exposes their APIs directly to the developer. This gives developers the freedom to use the myriad of third-party modules which are available for the underlying platform.

NestJS unterstützt dich auch aktiv dabei, eine robuste und skalierbare Anwendung zu bauen, in dem es dich an die Hand nimmt und dir eine bereits ausgereifte Architektur mit an die Hand gibt. Nice!

Grundlegendes zu NestJS 🧱

NestJS ist also nicht nur ein weiteres Framework, sondern eine wahre Bereicherung für jeden Entwickler, der seine Anwendungen skalierbar, wartbar und erweiterbar gestalten will. Aber was genau macht NestJS so besonders? Fangen wir bei der Architektur an. NestJS basiert auf Modulen, Providern und Controllern, die in einer hierarchischen Struktur organisiert sind. Dies erlaubt eine klare Trennung der verschiedenen Anwendungsbereiche und fördert so eine modulare und wartbare Codebasis.

NestJS setzt auf TypeScript ☝️

Ein weiterer Punkt, der NestJS hervorhebt, ist die Integration von TypeScript. TypeScript bringt Typsicherheit in die Welt von JavaScript und hilft, viele Fehler schon während der Entwicklungsphase zu erkennen. Für jemanden wie mich, der aus der Angular-Welt kommt, ist das eine sehr willkommene Eigenschaft, da es das Entwickeln sicherer und vorausschauender macht.


Warum du auch TypeScript für deine Projekte nutzen solltest und welche Vorteile es dir bringt, habe ich bereits in einem vorherigen Artikel erklärt 👇

Warum du nur noch TypeScript nutzen solltest ☝️
JavaScript ist tot, lang lebe TypeScript! Wie TypeScript dich aktiv unterstützt besseren und zuverlässigeren Code zu schreiben, erkläre ich dir in diesem Artikel 🎉

Nun zu den praktischen Aspekten: NestJS bietet eine Vielzahl von Modulen für gängige Aufgaben wie Authentifizierung, Datenbankintegration, Caching, Dateiverarbeitung und vieles mehr. Durch die Nutzung dieser Module kann man sich wiederkehrende Aufgaben sparen und sich auf das Wesentliche, die eigene Geschäftslogik, konzentrieren. NestJS macht es leicht, sauberen, gut strukturierten Code zu schreiben, der sich auch leicht testen lässt.

Aufbau von NestJS 🚧

In der Welt von NestJS sind Controller, Module und Services einige der Kernbestandteile, die es ermöglichen, eine strukturierte und effiziente API zu gestalten. Hier sind praktische Beispiele für diese wichtigen Bausteine:

Controller

Controller in NestJS sind verantwortlich für die Handhabung eingehender Requests und das Zurückgeben von Responses.

Diagram von NestJS

Sie werden durch die Dekorierung von Klassen mit @Controller() definiert. Hier ist ein einfaches Beispiel:

import { Controller, Get, Post, Body, Param } from '@nestjs/common';

@Controller('items')
export class ItemsController {
    @Get()
    findAll(): string {
        return 'This action returns all items';
    }

    @Get(':id')
    findOne(@Param('id') id: string): string {
        return `This action returns a single item with id ${id}`;
    }

    @Post()
    create(@Body() createItemDto: any): string {
        return 'This action adds a new item';
    }
}

In diesem Beispiel definiert der ItemsController Routen für das Abrufen aller Elemente, eines einzelnen Elements anhand seiner ID und das Erstellen eines neuen Elements.

Services

Services sind in NestJS dafür verantwortlich, die Geschäftslogik zu kapseln. Sie sind typischerweise Anbieter, die mit @Injectable() dekoriert sind und in Controller oder andere Services injiziert werden können. Hier ist ein einfaches Beispiel für einen Service:

import { Injectable } from '@nestjs/common';

@Injectable()
export class ItemsService {
    private readonly items = [];

    findAll(): string[] {
        return this.items;
    }

    findOne(id: string): string {
        return this.items.find(item => item.id === id);
    }

    create(item: any): void {
        this.items.push(item);
    }
}

Die Services werden über die Provider im Modul bereitgestellt und sind so über eine Dependency Injection verfügbar.

Provider

In NestJS sind Provider ein fundamentales Konzept, da sie für die meiste Geschäftslogik in einer Anwendung verantwortlich sind. Provider können Services, Repositories, Factories, Helfer und mehr sein. Sie sind in der Regel Klassen, die mit @Injectable() dekoriert sind und in das Dependency Injection System von NestJS eingebunden werden.

Diagram von NestJS

Hier einige Beispiele und Anwendungen von Providern:

Service als Provider
Services sind die am häufigsten genutzten Provider in NestJS. Sie kapseln Geschäftslogik, Datenzugriffe und andere Funktionen, die von verschiedenen Teilen der Anwendung wiederverwendet werden können.

Custom Provider
NestJS ermöglicht es auch, benutzerdefinierte Provider zu erstellen. Dies ist nützlich, wenn man eine komplexe Logik oder Drittanbieterdienste einbinden möchte. Hier ist ein Beispiel für einen benutzerdefinierten Provider:

import { Injectable, Inject } from '@nestjs/common';

const CATS_REPOSITORY = 'CATS_REPOSITORY';

@Injectable()
export class CatsService {
  constructor(@Inject(CATS_REPOSITORY) private catsRepository) {}

  findAll(): any[] {
    return this.catsRepository.findAll();
  }
}

const catsRepository = {
  findAll() {
    // Implementierung der Methode
  }
};

const customProviders = [
  {
    provide: CATS_REPOSITORY,
    useValue: catsRepository,
  },
];

Factory Provider
Factory Provider sind eine weitere Form von Providern, bei denen eine Factory-Funktion verwendet wird, um den Provider zu erstellen. Dies kann nützlich sein, wenn die Erstellung des Providers komplizierter ist oder von bestimmten Bedingungen abhängt.

import { Injectable, Inject, FactoryProvider } from '@nestjs/common';

@Injectable()
export class LoggerService {
  log(message: string) {
    console.log(message);
  }
}

const loggerFactory: FactoryProvider = {
  provide: 'LOGGER',
  useFactory: () => {
    const logger = new LoggerService();
    // Konfiguration des Loggers
    return logger;
  },
};

Hier wird ein LoggerService als Factory Provider erstellt, indem eine Factory-Funktion definiert wird, die den LoggerService instanziiert und zurückgibt.

Module

Module sind ein Weg, um Anwendungen und Komponenten in NestJS zu organisieren. Ein Modul kapselt Provider, Controller und andere Module.

Diagram von NestJS

Hier ist ein einfaches Beispiel:

import { Module } from '@nestjs/common';
import { ItemsController } from './items.controller';
import { ItemsService } from './items.service';

@Module({
  controllers: [ItemsController],
  providers: [ItemsService],
})
export class ItemsModule {}

Middleware

Middleware sind Funktionen, die vor den Routen-Handlern ausgeführt werden. Sie können für eine Vielzahl von Aufgaben verwendet werden, wie z.B. Logging, Authentifizierung, und so weiter.

Diagram von NestJS

Hier ist ein einfaches Middleware-Beispiel in NestJS:

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 made to: ${req.baseUrl}`);
    next();
  }
}

Guards

Guards sind verwendet für Authentifizierung und Autorisierung. Sie entscheiden, ob eine bestimmte Anfrage fortgesetzt werden soll.

Diagram von NestJS

Hier Beispiel, wie man einen einfachen Guard in NestJS implementieren könnte:

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';

@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return user && user.role === 'admin';
  }
}

Exception Filters

Exception Filters ermöglichen eine anpassbare Fehlerbehandlung. Sie können definieren, wie unerwartete Fehler und Ausnahmen behandelt werden sollen.

Diagram von NestJS

Hier ist ein einfaches Beispiel für einen Exception Filter:

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception.getStatus();

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: ctx.getRequest().url,
    });
  }
}

Interceptors

Interceptors bieten eine Möglichkeit, zusätzliche Logik vor oder nach der Ausführung einer Methode einzuführen. Sie sind nützlich für Logging, Umformen von Responses und vieles mehr.

Diagram von NestJS

Hier ist ein Beispiel:

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');
    const now = Date.now();

    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}

Pipes

Pipes sind verwendet für Daten Transformation und Validierung. Diese können eingesetzt werden, um Eingabedaten zu überprüfen oder zu bearbeiten, bevor sie einen Handler erreichen. Hier ist ein Beispiel für einen einfachen Pipe:

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (!value) {
      throw new BadRequestException('No data provided');
    }
    // Perform validation logic
    return value;
  }
}

Die CLI 🪄

Die NestJS CLI (Command Line Interface) ist ein wesentliches Werkzeug, das den Entwicklungsprozess beschleunigt, indem es das Einrichten, Entwickeln und Verwalten von NestJS-Anwendungen vereinfacht. Mit der CLI können neue Projekte, Module, Controller, Services und mehr generiert werden, was zu einer konsistenten und effizienten Entwicklung beiträgt. Die CLI wird wie folgt installiert:

npm install -g @nestjs/cli
# oder
yarn global add @nestjs/cli

Erstellen eines neuen Projekts

Mit der CLI kann man ein neues NestJS-Projekt mit einer vorkonfigurierten Projektstruktur erstellen. Nach der Installation führt man einfach den folgenden Befehl aus:

nest new project-name

Dieser Befehl erstellt ein neues Verzeichnis mit dem Namen project-name und richtet ein neues NestJS-Projekt mit allen erforderlichen Abhängigkeiten ein.

Generieren von Komponenten 📃

Die CLI erleichtert das Hinzufügen von Komponenten zu Ihrem Projekt. Dadurch lassen sich schnell Dinge erzeugen. Dazu zählen:

Erstellen eines Controllers:

nest generate controller cats

Dieser Befehl erstellt einen neuen Controller mit dem Namen cats.controller.ts und das zugehörige Testfile cats.controller.spec.ts im Verzeichnis src/cats.

Erstellen eines Services:

nest generate service cats

Dieser Befehl erstellt einen neuen Service namens cats.service.ts und das zugehörige Testfile cats.service.spec.ts.

Erstellen eines Moduls:

nest generate module cats

Dieser Befehl erstellt ein neues Modul namens cats.module.ts.

Starten der Anwendung 🏎️

Um Ihre NestJS-Anwendung zu starten, navigier in das Projektverzeichnis und führen den folgenden Befehl aus:

npm run start

Weitere nützliche CLI-Befehle 🤖

  • Linting: Um die Code-Qualität zu prüfen, kann man npm run lint verwenden.
  • Build: Um ein Projekt für die Produktion zu kompilieren, verwenden man npm run build.

Die NestJS CLI bietet eine Vielzahl weiterer Funktionen und Optionen, die Ihren Workflow optimieren und die Entwicklung von NestJS-Anwendungen erleichtern. Durch die Verwendung der CLI kann man sicherstellen, dass die Projektstruktur konsistent und gut organisiert ist und den Best Practices folgt. Weitere Informationen und eine detaillierte Dokumentation findet man in der offiziellen NestJS-Dokumentation.

Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).

Coole weitere Features ✨

GraphQL leicht gemacht

Mit NestJS kannst du auf einfache Weise GraphQL-APIs implementieren. Das Framework bietet eine nahtlose Integration mit Apollo und ermöglicht dir, mit wenigen Zeilen Code leistungsstarke und flexible GraphQL-Schnittstellen zu erstellen. Das ist besonders cool, wenn du auf moderne Datenabfrage und -manipulation setzt.

Hybride Anwendungsarten

NestJS ist nicht nur auf REST oder GraphQL beschränkt. Du kannst hybride Anwendungen erstellen, die beispielsweise gleichzeitig REST- und GraphQL-Endpunkte bieten. Das erhöht die Flexibilität und macht NestJS zu einem vielseitigen Werkzeug in deinem Entwicklerarsenal.

WebSockets integriert

Echtzeitfunktionalitäten sind in modernen Anwendungen oft unverzichtbar. NestJS macht die Arbeit mit WebSockets einfach, indem es die Integration von Socket.io oder anderen Bibliotheken vereinfacht. So kannst du ohne großen Aufwand Echtzeitkommunikation in deine Anwendungen einbauen.

CQRS-Modul

Für komplexere Anwendungsfälle, in denen eine Trennung zwischen Befehls- (Command) und Abfrageverantwortung (Query) erforderlich ist, bietet NestJS ein CQRS-Modul an. Dies unterstützt dich beim Aufbau von skalierbaren und wartbaren Softwarearchitekturen.

Dekorative Programmierung

Mit dem dekorativen Ansatz von NestJS kannst du deine Intentionen klar und präzise ausdrücken. Ob du Routen definierst, Abhängigkeiten injizierst oder Guards und Interceptors anwendest – alles geschieht mit einer sauberen und lesbaren Syntax.

Erstklassige Mikroservice-Unterstützung

NestJS ist nicht nur für die Erstellung monolithischer Anwendungen geeignet. Es bietet robuste Unterstützung für Mikroservices mit einer Vielzahl von Transportmechanismen, darunter MQTT, Redis, RabbitMQ und mehr. Dies ermöglicht dir, skalierbare und effiziente Mikroservice-Architekturen zu entwerfen.

Dynamische Module

Ein weiteres cooles Feature sind dynamische Module. Mit ihnen kannst du Module konfigurieren und sogar Module basierend auf verschiedenen Umgebungen oder Bedingungen erstellen. Das bietet dir eine enorme Flexibilität und Kraft, insbesondere in komplexen und dynamischen Anwendungsszenarien.

OpenAPI (Swagger) Integration

NestJS bietet eine integrierte Unterstützung für OpenAPI, auch bekannt als Swagger. Mit wenigen Dekoratoren in deinen Controllern und Modellen kannst du eine vollständige OpenAPI-Spezifikation deiner API automatisch generieren lassen. Das bedeutet, du erhältst interaktive Dokumentation, Client-SDK-Generation und vieles mehr, praktisch ohne zusätzlichen Aufwand. Dies ist besonders nützlich, um API-Endpunkte für Frontend-Entwickler oder externe Partner schnell und klar zu dokumentieren. Es verbessert die Zugänglichkeit und Verständlichkeit deiner API erheblich.

Prisma Integration

Prisma ist ein nextgen ORM für Node.js und TypeScript, das sich durch seine einfache Handhabung und Leistung auszeichnet. NestJS kann nahtlos mit Prisma integriert werden, um eine effiziente und einfache Datenbankabstraktion und -manipulation zu ermöglichen. Mit Prisma kannst du deine Datenbankschemata als Modelle definieren und leistungsfähige Abfragen schreiben, die typsicher sind, was die Entwicklung und Wartung von datenintensiven Anwendungen vereinfacht.

Hot Reload (mit Webpack HMR)

Entwickler lieben schnelles Feedback. NestJS unterstützt Hot Reload durch Webpack's Hot Module Replacement (HMR). Damit kannst du Änderungen an deinem Code vornehmen, die in Echtzeit in die laufende Anwendung eingespielt werden, ohne dass ein Neustart erforderlich ist. Dies spart wertvolle Entwicklungszeit und erhöht die Produktivität, da du sofortiges Feedback über die Auswirkungen deiner Änderungen erhältst. Um dies zu nutzen, musst du lediglich deinen NestJS-Server mit der entsprechenden HMR-Konfiguration starten, und schon kannst du die Änderungen live sehen, sobald du deinen Code speicherst.

CRUD Generator

Mit dem CRUD Generator von NestJS kannst du mit wenig Aufwand schnell effektive und standardisierte CRUD-Operationen für deine Anwendung erstellen. Dieses Tool automatisiert die Erstellung von typischen CRUD-Modulen, Controllern und Services, sodass du dich auf die spezifischen Anforderungen und die Geschäftslogik deiner Anwendung konzentrieren kannst, statt immer wiederkehrenden Code manuell zu schreiben.

Fazit 💡

Zusammenfassend lässt sich sagen, dass NestJS eine hervorragende Wahl für moderne, serverseitige Anwendungen ist. Die Kombination aus modernem JavaScript, Typsicherheit durch TypeScript, einer soliden Architektur und einer großen Auswahl an vorbereiteten Modulen macht es zu einem unschätzbaren Werkzeug für Entwickler.

NestJS nimmt uns viel Arbeit ab und lässt uns gleichzeitig genug Raum, um unsere Anwendungen nach unseren Vorstellungen zu gestalten. Ob für kleine Projekte oder große Unternehmensanwendungen – NestJS ist definitiv einen Blick wert.

Zudem überzeugt es durch die Anlehnung an die Konzepte von Angular und ist dadurch prädestiniert für Angular-Entwickler.

Also, warum nicht gleich in das nächste Projekt mit NestJS springen und selbst erleben, wie es die Entwicklung vereinfacht und beschleunigt?