Skip to main content

Fastify-Plugins als Bausteine für eine Backend-Node.js-API

Artikel von
blog feature snyk container custom base recommendations

28. Mai 2024

0 Min. Lesezeit

Bei der Entwicklung von Backend-Node.js-APIs hebt sich Fastify mit seinem Plugin-Ökosystem und seiner Architektur von herkömmlichen Frameworks wie Express ab. Das hocheffiziente Web-Framework mit geringem Overhead überzeugt durch bemerkenswerte Geschwindigkeit und eine schlanke Architektur. Im Zentrum des Designs von Fastify steht eine robuste Plugin-Architektur, die die asynchronen Möglichkeiten von Node.js voll ausschöpft. So setzt Fastify neue Leistungsmaßstäbe unter den Node.js-Frameworks und rückt die zentrale Rolle der Fastify-Plugins in den Fokus.

Die Weiterentwicklung von Fastify wird von einer wachsenden Open-Source-Community vorangetrieben. So bleibt das Framework relevant und auf dem neuesten Stand der Web-App-Entwicklung. Wenn Sie 2024 im Bereich Backend-Webentwicklung tätig sind, hat sich Fastify als wertvolles Framework für die Entwicklung schneller, skalierbarer und sicherer Webanwendungen bewährt.

Warum Fastify und seine Plugins eine robuste Backend-Node.js-API ermöglichen

Bei der Entwicklung moderner Webanwendungen ist Fastify für viele Entwickler eine überzeugende Wahl. Das für Node.js entwickelte Web-Framework vereint Leistung, Flexibilität und entwicklerfreundliche Funktionen. In diesem Abschnitt sehen wir uns die Vorteile von Fastify für die Entwicklung moderner Anwendungen an.

Moderne ESM-Unterstützung

Fastify unterstützt ECMAScript-Module (ESM) direkt. Das ist wichtig, denn ESM ist das offizielle Format für JavaScript-Module in Paketen. Mit Fastify können Sie die Import- und Export-Syntax direkt verwenden, ohne dass eine Transpilierung erforderlich ist.

Hier sehen Sie ein einfaches Beispiel für einen Fastify-Server mit ESM:

import fastify from 'fastify';

const server = fastify();

server.get('/', async (request, reply) => {
  return { hello: 'world' }
});

server.listen({port: 3000}, (err, address) => {
  if (err) throw err
  server.log.info(`Server listening at ${address}`)
});

Saubere Promise-basierte Routendefinitionen mit async/await

Fastify erleichtert die Verarbeitung von Routen mit der async/await-Syntax. Das Ergebnis ist saubererer und besser lesbarer Code als bei herkömmlichen Callback-Mustern.

Express unterstützt async/await in Routendefinitionen nicht nativ. Das führt oft zu verschachtelten Callbacks oder dazu, dass Sie Route-Handler in try/catch-Blöcke einschließen müssen, um Fehler abzufangen. Dank der nativen async/await-Unterstützung von Fastify können Sie asynchronen Code mit Promises einfacher schreiben und warten.

Hier sehen Sie ein Beispiel dafür, wie Fastify async/await in einer Routendefinition verwendet:

server.get('/api/tasks', async (request, reply) => {
  const tasks = await Tasks.findAll();
  return tasks;
});

Hohe Leistung dank schneller Radix-Baum-Auflösung für Routen

Fastify ist auf hohe Leistung ausgelegt. Das Framework nutzt eine schnelle Radix-Baum-Auflösung für Routen – einen hocheffizienten Routing-Algorithmus. Dadurch kann Fastify viele Routen mit minimalem Overhead verarbeiten und auch unter hoher Last kurze Antwortzeiten erzielen.

Eine vielseitige Fastify-Plugin-Architektur mit Kapselung

Fastify bietet eine vielseitige Plugin-Architektur mit Kapselung. So können Sie Ihre Anwendung ganz einfach in isolierte Komponenten aufteilen, die jeweils über eigene Routen, Plugins und Decorators verfügen. Das sorgt für eine bessere Code-Organisation und verringert das Risiko von Namenskonflikten.

Hier sehen Sie ein Beispiel für ein Fastify-Plugin, das den Route-Handler um eine Hilfsfunktion erweitert:

import fp from 'fastify-plugin';

export default fp(async function (fastify, opts) {
  fastify.decorate('utility', () => 'This is a utility function');
});

So verwenden Sie dieses Plugin:

// import and get a fastify server instance,
// and then:
server.register(import('./utility-plugin.js'));

server.get('/', async (request, reply) => {
  return server.utility();
});

Leichtgewichtige Dependency Injection

Das Plugin-System von Fastify dient auch als leichtgewichtiges Dependency-Injection-System (DI). Damit können Sie gemeinsame Hilfsfunktionen und Dienste ganz einfach in Ihrer Anwendung teilen, ohne auf Singletons oder globale Variablen zurückzugreifen.

Im folgenden Beispiel wird eine Datenbankverbindung in verschiedenen Teilen der Anwendung gemeinsam genutzt:

fastify.register(async function (fastify, opts) {
  const db = await someDbConnectionLibrary(opts.connectionString);

  fastify.decorate('db', db);
});

Eine einfache Backend-Node.js-API mit Fastify

In diesem Blogbeitrag geht es um die grundlegenden Bausteine für die Entwicklung von Backend-Node.js-APIs mit Fastify und den empfohlenen Plugins im Jahr 2024. Wir befassen uns eingehend mit den wesentlichen Komponenten einer Fastify-Anwendung – vom Routing und der Middleware bis hin zu Validierung und Serialisierung.

Angesichts der Bedeutung von Cybersicherheit in der digitalen Welt von heute betrachten wir außerdem kurz Aspekte der Websicherheit und wie Sie diese mit Fastify-Plugins berücksichtigen können. Dieser wichtige Aspekt wird bei der übereilten Anwendungsentwicklung häufig übersehen.

Stellen Sie zunächst sicher, dass die neueste LTS-Version von Node.js auf Ihrem System installiert ist (zum Zeitpunkt der Erstellung dieses Beitrags ist es v20.11.1). Initialisieren Sie dann ein neues Node.js-Projekt und installieren Sie Fastify mit npm, wie unten gezeigt:

$ mkdir fastify-app && cd fastify-app
$ npm init -y
$ npm install --save fastify

Bearbeiten Sie die Datei package.json und fügen Sie auf oberster Ebene die Schlüsseldefinition type: module hinzu, um ESM zu aktivieren:

{
  "type": "module",
}

Anschließend erstellen wir die Grundstruktur einer Fastify-Anwendung in der Datei server.js im Stammverzeichnis des Projekts:

// Import Fastify using ESM syntax
import Fastify from "fastify";

const fastify = Fastify({ logger: true });

// Defining a route
fastify.get("/", async (request, reply) => {
  return { hello: "world" };
});

async function startServer() {
  try {
    // Start the server
    const address = await fastify.listen({
      port: 3000,
      host: "localhost",
    });

    // Log the server start information using the address returned by Fastify
    // Fastify will already log the server start information by default
    // But here is an example of how you can log the server start information
    // fastify.log.info(`Server starting up at ${address}`);
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
}

startServer();

Die API-Route in einem Fastify-Plugin kapseln

Fastify empfiehlt die Verwendung von Plugins, um Ihren Code zu organisieren und zu kapseln. So können Sie zusammengehörigen Code bündeln und Konflikte zwischen verschiedenen Teilen Ihrer Anwendung vermeiden.

Wandeln wir unsere „Hello World“-Route in ein Fastify-Plugin um:

const fastify = require('fastify')({ logger: true });

const helloRoute = async (fastify, options) => {
  fastify.get('/', async (request, reply) => {
    return { hello: 'world' };
  });
};

fastify.register(helloRoute);

const start = async () => {
  try {
    await fastify.listen(3000);
    fastify.log.info(`server listening on ${fastify.server.address().port}`);
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};
start();

Request- und Response-Schemas für Routen verwenden

Mit Fastify können Sie eingehende Requests validieren und ausgehende Responses mithilfe von JSON-Schemas strukturieren. So stellen Sie die Datenintegrität Ihrer Anwendung sicher und vereinfachen die Dokumentationserstellung.

So definieren Sie ein Request- und Response-Schema für die „Hello World“-Route:

const helloRoute = async (fastify, options) => {
  fastify.route({
    method: 'GET',
    url: '/',
    schema: {
      response: {
        200: {
          type: 'object',
          properties: {
            hello: { type: 'string' },
          },
        },
      },
    },
    handler: async (request, reply) => {
      return { hello: 'world' };
    },
  });
};

fastify.register(helloRoute);

Fastify überprüft nun automatisch die Response der „Hello World“-Route anhand des angegebenen Schemas. So lassen sich potenzielle Fehler erkennen und Sie stellen sicher, dass sich Ihre Anwendung wie erwartet verhält.

Fastify-Plugins als Bausteine für Fastify-Anwendungen

In diesem Abschnitt sehen wir uns vier wichtige Fastify-Plugins an: @fastify/pino, @fastify/cors, @fastify/env und @fastify/swagger sowie @fastify/swagger-ui. Diese Plugins bilden eine hilfreiche Grundlage für jede Fastify-Anwendung, mit der Sie eine Backend-Node.js-API entwickeln.

Der Pino-Logger für Fastify

Pino ist ein besonders leistungsstarker Logger für Node.js. Eigene Plugins ermöglichen die Integration mit anderen Logging-Systemen wie Sentry und weiteren.

Sie können sich zunutze machen, dass Fastify Pino als Standard-Logger verwendet. Wenn Sie Fastify mit { logger: true } initialisieren, verwenden Sie Pino bereits im Hintergrund. Wenn Sie mehr Kontrolle über die Logging-Stufe oder andere Pino-spezifische Einstellungen wünschen, können Sie der logger-Eigenschaft jedoch ein Konfigurationsobjekt übergeben.

Während der Entwicklung wünschen Sie sich vielleicht eine besser lesbare und farbenfrohere Protokollausgabe. Dafür können wir das Paket pino-pretty verwenden. Installieren Sie zunächst pino und pino-pretty:

npm install --save pino pino-pretty

Konfigurieren Sie anschließend pino-pretty wie folgt:

import pino from "pino";

const logger = pino({
  transport: {
    target: "pino-pretty",
    options: {
      colorize: true,
    },
  },
});

logger.info("Hello from pino-pretty");

const fastify = Fastify({
  logger: logger,
});

Im obigen Beispiel initialisieren wir einen Logger mit Pino und konfigurieren pino-pretty als Transportziel. Mit der Option colorize aktivieren wir eine farbige Protokollausgabe. Anschließend übergeben wir unsere benutzerdefinierte Variable logger an die Fastify-Option logger.

So wird die Protokollausgabe in ein besser lesbares, verständlicheres Format umgewandelt:

[12:13:43.499] INFO (95477): hello from pino-pretty
[12:13:43.517] INFO (95477): Server listening at http://[::1]:3000
[12:13:43.518] INFO (95477): Server listening at http://127.0.0.1:3000

Pino bietet in Fastify folgende Vorteile:

  • Schnelleres Logging: Pino wurde in erster Linie entwickelt, um der schnellste Logger für Node.js zu sein.

  • Vielseitiger Logger: Pino unterstützt verschiedene Logging-Stufen und kann Protokolle als JSON formatieren.

Das Fastify-Plugin @fastify/cors

Das Plugin @fastify/cors bietet eine einfache Möglichkeit, CORS (Cross-Origin Resource Sharing) in Ihrer Fastify-Anwendung zu nutzen.

Vorteile:

  • Sicher: Unterstützt Sie dabei, den Datenfluss zwischen verschiedenen Ursprüngen zu verwalten.

  • Anpassbar: Ermöglicht Ihnen, eine benutzerdefinierte Funktion für die CORS-Optionen festzulegen.

Hier sehen Sie ein einfaches Beispiel dafür, wie Sie das Plugin @fastify/cors hinzufügen und verwenden:

// after creating the fastify instance
// let's register the fastify cors plugin:
fastify.register(require('@fastify/cors'), { 
  origin: '*',
  methods: ['GET','POST', 'PUT', 'DELETE']
})

Anschließend können Sie prüfen, ob die CORS-Konfiguration funktioniert, indem Sie eine cURL-Anfrage an den Fastify-Server senden:

curl -i http://localhost:3000

Die Response sollte den Header Access-Control-Allow-Origin enthalten. Das zeigt, dass die CORS-Konfiguration wie erwartet funktioniert:

HTTP/1.1 200 OK
access-control-allow-origin: *
content-type: application/json; charset=utf-8
content-length: 17
Date: Sun, 18 Feb 2024 10:20:15 GMT
Connection: keep-alive
Keep-Alive: timeout=72

Das Fastify-Plugin @fastify/env

Node.js-Entwickler verwenden häufig Umgebungsvariablen, um ihre Anwendungen zu konfigurieren, und greifen dafür oft auf die Datei .env zurück.

Hier kommt das Fastify-Plugin @fastify/env ins Spiel. Sie können dotenv oder die integrierte Node.js-Option --env-file zum Laden der Konfiguration verwenden. Mit diesem Fastify-Plugin können Sie jedoch direkt in Ihrer Fastify-Anwendung auf die Konfiguration dieser Umgebungsvariablen zugreifen. Außerdem bietet es Konfigurationsvalidierung und Typkonvertierung.

Hier sehen Sie ein einfaches Beispiel dafür, wie Sie das Plugin @fastify/env hinzufügen und verwenden. Installieren Sie zunächst das Plugin und erstellen Sie eine einfache .env-Datei:

npm install --save @fastify/env
echo "PORT=3001" > .env

Aktualisieren Sie anschließend den Code in server.js, um das neue Fastify-Plugin zu registrieren:

fastify.register(import('@fastify/env'), {
  dotenv: true,
  schema: {
    type: 'object',
    required: [ 'PORT' ],
    properties: {
      PORT: {
        type: 'string',
        default: 3000
      }
    }
  }
})

Wenn Sie das Fastify-Plugin @fastify/env registrieren, können Sie über das Objekt fastify.config auf die in der Datei .env definierte Konfiguration zugreifen. Allerdings können wir es nicht einfach direkt verwenden. Dazu müssen wir ein neues Konzept der Fastify-Plugin-Architektur berücksichtigen.

Bei der asynchronen Plugin-Architektur von Fastify müssen Sie warten, bis Fastify alle Plugins initialisiert hat, bevor diese angewendet werden. So stellen Sie sicher, dass die Plugins korrekt registriert und einsatzbereit sind. Wenn Sie diesen Schritt überspringen, tritt beim Zugriff auf das Objekt fastify.config ein Fehler auf.

Daher müssen wir unsere neue Funktion startServer() wie folgt aktualisieren:

try {
    // wait for all plugins to run 
    await fastify.ready();
    // Start the server
    const address = await fastify.listen({
      // now we can access the fastify.config.PORT configuration
      port: fastify.config.PORT,
      host: "localhost",
    });

Vorteile des Plugins @fastify/env:

  • Validierung von Umgebungsvariablen: Stellt sicher, dass erforderliche Umgebungsvariablen vorhanden sind.

  • Typkonvertierung: Wandelt Umgebungsvariablen in die erforderlichen Datentypen um.

  • Leichtgewichtige Dependency Injection: Ermöglicht Ihnen, Konfigurationen ganz einfach in Ihrer Anwendung gemeinsam zu nutzen, überall dort, wo Sie auf die Fastify-Anwendungsinstanz fastify zugreifen können.

Backend-Node.js-APIs mit den Fastify-Plugins @fastify/swagger und @fastify/swagger-ui dokumentieren

Das Plugin @fastify/swagger generiert eine Swagger-Dokumentationsseite für Ihre Fastify-Anwendung und stellt sie bereit. @fastify/swagger-ui bietet eine integrierte Benutzeroberfläche für Ihre Swagger-Dokumentation, auf die Sie über einen Webbrowser zugreifen können. Die Fastify-Anwendung selbst stellt diese Swagger-UI-Webseite bereit.

Wie zuvor fügen wir zunächst diese Plugins zu unserer Fastify-Anwendung hinzu:

npm install --save @fastify/swagger @fastify/swagger-ui

Anschließend registrieren wir die beiden Plugins in unserer Datei server.js:

fastify.register(import('@fastify/swagger'), {
  routePrefix: '/documentation',
  swagger: {
    info: {
      title: 'Test swagger',
      description: 'testing the fastify swagger api',
      version: '0.1.0'
    },
    host: 'localhost',
    schemes: ['http'],
    consumes: ['application/json'],
    produces: ['application/json']
  },
  exposeRoute: true
})

fastify.register(import('@fastify/swagger-ui'))

Starten Sie den Fastify-Server neu und rufen Sie die URL http://localhost:3001/documentation in Ihrem Webbrowser auf. Dort sehen Sie die generierte Swagger-UI-Dokumentationsseite. Damit steht Ihnen eine vollständig funktionsfähige Swagger-Dokumentationsseite für Ihre Fastify-Anwendung zur Verfügung.

Das Fastify-Plugin @fastify/swagger fügt Swagger-Dokumentation für Backend-Node.js-APIs hinzu.

Ein Hinweis zur Sicherheit: In einer Produktionsumgebung sollten Sie die Swagger-UI-Dokumentationsseite mit Authentifizierung und Autorisierung schützen. Verwenden Sie außerdem Ihre Node.js-Produktionsserver nicht, um die Swagger-UI-Dokumentationsseite bereitzustellen.

Vorteile:

  • Einfache API-Dokumentation: Generiert automatisch die API-Dokumentation.

  • Anpassbar: Ermöglicht die Anpassung der Dokumentationsseite.

Alles zusammenführen

Wie wir gesehen haben, bietet das vielseitige Ökosystem von Fastify zahlreiche Plugins, die Ihre Entwicklung von Backend-Node.js-APIs erheblich verbessern können. Mit pino und pino-pretty erweitern Sie die Logging-Funktionen, mit @fastify/cors sorgen Sie für CORS-Sicherheit im Browser, mit @fastify/env verwalten Sie Umgebungsvariablen und mit @fastify/swagger und @fastify/swagger-ui dokumentieren Sie Ihre API. Diese Tools vereinfachen Ihre Arbeitsabläufe, verbessern die Leistung und steigern die Qualität Ihrer Anwendungen.

Unsere Erkenntnisse zu Fastify-Plugins lassen sich wie folgt zusammenfassen:

Modulare Entwicklung voranbringen: Die Plugin-Architektur von Fastify fördert eine modulare Entwicklung und unterstützt eine übersichtliche, wartungsfreundliche Codebasis. Durch die Integration dieser Plugins können sich Entwickler auf die Geschäftslogik statt auf Boilerplate-Code konzentrieren. So lässt sich die Entwicklung beschleunigen, ohne Leistung oder Skalierbarkeit zu beeinträchtigen.

Produktivität und Leistung steigern: Jedes Plugin deckt mit seinem speziellen Fokus gängige Herausforderungen der Webentwicklung ab und ermöglicht es Ihnen, robuste, effiziente und sichere APIs zu erstellen. Die einfache Integration in Fastify und die damit verbundenen Vorteile – etwa erweitertes Logging, Cross-Origin Resource Sharing, Umgebungskonfiguration und API-Dokumentation – tragen direkt zu einer höheren Entwicklerproduktivität und einer besseren Anwendungsleistung bei.

Zu weiteren Erkundungen anregen: Wir haben einige wichtige Plugins vorgestellt, doch das Fastify-Ökosystem bietet zahlreiche weitere Plugins für unterschiedlichste Anforderungen – von der Authentifizierung über die Datenbankintegration bis hin zur Ratenbegrenzung und mehr. Wir möchten Entwickler dazu ermutigen, die Fastify-Plugin-Seite zu erkunden und mit diesen Tools zu experimentieren, um herauszufinden, wie sie ihren API-Entwicklungsprozess weiter optimieren und verbessern können.

Sie können sich gerne den vollständigen Quellcode zu allen oben aufgeführten Beispielen einer Backend-Node.js-API mit den besprochenen Fastify-Plugins ansehen.