Ein Node.js BFF für alle Ihre Frontend-Bedürfnisse
Olivia Novak
Dev Intern · Leapcell

Einleitung
Im heutigen schnelllebigen digitalen Umfeld sind Anwendungen selten auf eine einzige Plattform beschränkt. Wir interagieren mit Diensten über Webbrowser, mobile Apps und manchmal sogar Desktop-Clients. Diese Multi-Plattform-Realität stellt Backend-Entwickler oft vor eine große Herausforderung: Wie erstellt man eine einzige, monolithische API, die die unterschiedlichen Daten- und Interaktionsanforderungen jeder einzelnen Frontend-Anwendung effizient bedient? Der Versuch, eine "One-Size-Fits-All"-API zu erzwingen, führt normalerweise zu übermäßigem oder unzureichendem Datenabruf, erhöhten Netzwerklasten und komplexen Datentransformationslogiken, die über verschiedene Frontend-Codebasen verstreut sind. Dies belastet nicht nur die Frontend-Entwickler, sondern erschwert auch die Backend-Evolution. Die Lösung liegt in der strategischen Entkopplung der API-Bedürfnisse des Frontends von den Kern-Backend-Diensten, und genau hier glänzt eine Backend for Frontend (BFF)-Schicht, die mit Node.js erstellt wurde. Sie fungiert als intelligentes Bindeglied, das API-Antworten für die genauen Anforderungen jedes konsumierenden Clients maßschneidert und dadurch die Leistung optimiert, die Frontend-Entwicklung vereinfacht und eine unabhängige Weiterentwicklung ermöglicht.
Das Node.js BFF-Paradigma verstehen
Bevor wir uns mit den Implementierungsdetails befassen, wollen wir einige Kernkonzepte klären, die dem Node.js BFF-Muster zugrunde liegen.
Microservices
Eine Microservices-Architektur zerlegt eine monolithische Anwendung in eine Reihe kleiner, unabhängig bereitstellbarer Dienste, von denen jeder eine spezifische Geschäftsfunktion ausführt. Obwohl sie Vorteile wie Skalierbarkeit und unabhängige Entwicklung bietet, stellen Microservices oft granulare APIs bereit, die möglicherweise nicht direkt den Datenanforderungen eines Frontends entsprechen.
Backend for Frontend (BFF)
Ein Backend for Frontend (BFF) ist ein Architekturmuster, bei dem ein API-Gateway oder -Dienst speziell für einen einzigen Frontend-Anwendungstyp (z. B. ein Web-BFF, ein Mobile-BFF) erstellt wird. Im Gegensatz zu einem Allzweck-API-Gateway konzentriert sich ein BFF auf die Transformation und Aggregation von Daten aus mehreren Backend-Diensten in ein Format, das perfekt auf sein bestimmtes Frontend zugeschnitten ist. Es fungiert als Abstraktionsschicht, die das Frontend vor der Komplexität der zugrunde liegenden Microservices abschirmt.
Node.js
Node.js ist eine Open-Source-Laufzeitumgebung für JavaScript, die JavaScript-Code außerhalb eines Webbrowsers ausführt. Seine nicht-blockierende, ereignisgesteuerte Architektur und sein reiches Ökosystem an Modulen (npm) machen es zu einer ausgezeichneten Wahl für die Erstellung hochskalierbarer und performanter Netzwerkanwendungen wie API-Gateways und BFFs.
Das Prinzip maßgeschneiderter APIs
Das Kernprinzip hinter dem BFF-Muster sind maßgeschneiderte APIs. Anstatt eine generische API bereitzustellen, bietet das BFF eine API, die für einen bestimmten Client optimiert ist. Eine mobile App benötigt beispielsweise möglicherweise eine Teilmenge von Daten oder eine andere Datenstruktur als eine Webanwendung aufgrund von Bildschirmplatz, Netzwerkeinschränkungen oder unterschiedlichen Benutzerabläufen. Das BFF abstrahiert diese Unterschiede und präsentiert jedem Frontend eine saubere, optimierte API.
Wie Node.js das BFF befähigt
Node.js eignet sich aus mehreren wichtigen Gründen besonders gut zum Erstellen von BFFs:
- JavaScript überall: Die Verwendung von JavaScript sowohl im Frontend als auch im BFF ermöglicht Code-Sharing, reduziert den Kontextwechsel für Entwickler und sorgt für eine einheitliche Entwicklungserfahrung.
- Asynchrones I/O: Das nicht-blockierende I/O-Modell von Node.js ist ideal für die Verarbeitung zahlreicher gleichzeitiger Anfragen, wie sie in einem API-Gateway üblich sind, und aggregiert effizient Daten aus mehreren Backend-Diensten, ohne die Event-Schleife zu blockieren.
- Reiches Ökosystem: Das riesige npm-Ökosystem bietet sofort verfügbare Bibliotheken für die HTTP-Kommunikation (z. B.
axios
,node-fetch
), Routing (z. B. Express.js, Fastify), Datentransformation (z. B. Lodash) undauthentifizierung, was die Entwicklung erheblich beschleunigt. - Leistung: Obwohl Node.js keine CPU-intensive Leistung bietet, zeichnet es sich bei I/O-gebundenen Aufgaben aus, was es zu einer performanten Wahl für die Orchestrierung von API-Aufrufen macht.
Erstellen eines Node.js BFF-Beispiels
Lassen Sie uns dies anhand eines praktischen Beispiels veranschaulichen. Stellen Sie sich eine E-Commerce-Anwendung mit separaten Microservices für Products
, Users
und Orders
vor. Unser Ziel ist es, eine Webanwendung und eine Mobile-Anwendung zu bedienen, die jeweils unterschiedliche Anforderungen an eine Produktlistenseite haben.
Szenario: Produktlistenseite
- Webanwendung: Benötigt
productId
,productName
,description
,price
,imageUrl
,category
undaverageRating
. - Mobile Anwendung: Benötigt
productId
,productName
,thumbnailUrl
,price
und eine vereinfachteshortDescription
für die schnelle Anzeige.
Kern-Backend-APIs (Hypothetisch)
GET /products/{id} # Gibt detaillierte Produktinformationen zurück GET /users/{id} # Gibt Benutzerdetails zurück GET /orders/{id} # Gibt Bestelldetails zurück
Node.js BFF-Implementierung mit Express.js
Lassen Sie uns zunächst eine einfache Express.js-Anwendung für unser BFF einrichten.
// app.js const express = require('express'); const axios = require('axios'); // Für HTTP-Anfragen an Backend-Dienste const cors = require('cors'); // Zur Behandlung von CORS für verschiedene Frontends const app = express(); const PORT = 3000; // Middleware app.use(express.json()); app.use(cors()); // CORS nach Bedarf für bestimmte Ursprünge konfigurieren // --- Such-API von Backend-Diensten (URLs durch tatsächliche URLs ersetzen) --- const PRODUCT_SERVICE_URL = 'http://localhost:3001/products'; const RATING_SERVICE_URL = 'http://localhost:3002/ratings'; // Annahme eines separaten Bewertungsdienstes // --- Web-BFF-Endpunkt für Produktliste --- app.get('/bff/web/products', async (req, res) => { try { // Alle Produkte abrufen const { data: products } = await axios.get(PRODUCT_SERVICE_URL); // Für jedes Produkt die durchschnittliche Bewertung abrufen (Beispiel für Aggregation) const productsWithRatings = await Promise.all(products.map(async (product) => { try { const { data: ratingInfo } = await axios.get(`${RATING_SERVICE_URL}/${product.id}/average`); return { productId: product.id, productName: product.name, description: product.longDescription, price: product.price, imageUrl: product.mainImage, category: product.category, averageRating: ratingInfo.averageRating || 0, // Standardmäßig 0, wenn keine Bewertung }; } catch (error) { console.error(`Bewertung für Produkt ${product.id} konnte nicht abgerufen werden:`, error.message); return { productId: product.id, productName: product.name, description: product.longDescription, price: product.price, imageUrl: product.mainImage, category: product.category, averageRating: 0, // Fehler ordnungsgemäß behandeln }; } })); res.json(productsWithRatings); } catch (error) { console.error('Fehler beim Abrufen von Webprodukten:', error.message); res.status(500).json({ message: 'Webprodukte konnten nicht abgerufen werden' }); } }); // --- Mobile BFF-Endpunkt für Produktliste --- app.get('/bff/mobile/products', async (req, res) => { try { // Alle Produkte abrufen const { data: products } = await axios.get(PRODUCT_SERVICE_URL); // Daten für mobile spezifische Anforderungen transformieren const mobileProducts = products.map(product => ({ productId: product.id, productName: product.name, thumbnailUrl: product.thumbnailImage, // Mobile bevorzugt möglicherweise kleinere Thumbnails price: product.price, shortDescription: product.shortDescription || product.longDescription.substring(0, 100) + '...', // Beschreibung kürzen })); res.json(mobileProducts); } catch (error) { console.error('Fehler beim Abrufen von Mobilprodukten:', error.message); res.status(500).json({ message: 'Mobilprodukte konnten nicht abgerufen werden' }); } }); // BFF-Server starten app.listen(PORT, () => { console.log(`Node.js BFF hört auf Port ${PORT}`); });
In diesem Beispiel:
-
/bff/web/products
: Dieser Endpunkt richtet sich speziell an die Webanwendung. Er ruft alle Produkte ab, ruft dann asynchron durchschnittliche Bewertungen für jedes Produkt von einem separatenRATING_SERVICE_URL
ab und formt schließlich die Daten in das exakte Format, das das Web-Frontend erwartet (z. B.averageRating
,imageUrl
). Dies kann bei Bedarf weitere Datenaggregationen aus zusätzlichen Diensten beinhalten (z. B. Benutzerbewertungen, Lagerinformationen). -
/bff/mobile/products
: Dieser Endpunkt ist für die mobile Anwendung konzipiert. Er ruft dieselben Produktdaten ab, wendet jedoch unterschiedliche Transformationen an:- Er verwendet
thumbnailUrl
anstelle vonimageUrl
. - Er generiert eine
shortDescription
, indem er dielongDescription
kürzt oder ein dediziertes Feld für kurze Beschreibungen vom Backend verwendet. - Er lässt
description
,category
undaverageRating
weg, um die Payloadgröße und die visuelle Unordnung auf kleineren Bildschirmen zu reduzieren.
- Er verwendet
Diese unterschiedliche Routing- und Datentransformationslogik innerhalb des BFF trennt die Frontend-Anwendungen effektiv von der Komplexität der zugrunde liegenden Microservices und den Datenbedürfnissen voneinander. Jedes Frontend kann nun eine perfekt zugeschnittene API nutzen, ohne übermäßigen Datenabruf oder clientseitige Datenmanipulation durchzuführen, was zu optimierter Leistung und vereinfachter Entwicklung führt.
Anwendungsszenarien
Das Node.js BFF-Muster ist in verschiedenen Szenarien sehr vorteilhaft:
- Unterschiedliche Client-Typen: Wenn Sie deutlich unterschiedliche Frontends (Web, iOS, Android, Smart-TVs) haben, die unterschiedliche Datenstrukturen oder Detailgrade benötigen.
- Microservices-Architektur: Wenn Backend-Dienste granular sind und ihre direkte Bereitstellung für Frontends zu komplexer clientseitiger Orchestrierung führen würde.
- Schnelle Frontend-Iterationen: Ermöglicht Frontend-Teams, unabhängig von ihren spezifischen API-Bedürfnissen zu iterieren, ohne Kern-Backend-Änderungen zu beeinträchtigen oder darauf zu warten.
- Integration von Altsystemen: Kann als Fassade über älteren, unflexibleren Backend-Systemen dienen und neuen Frontends eine moderne API präsentieren.
- Authentifizierung und Autorisierung: Das BFF kann die Logik für Authentifizierung und Autorisierung zentralisieren und eine zusätzliche Sicherheitsebene hinzufügen und die Client-seitigen Sicherheitsbedenken vereinfachen.
- Leistungsoptimierung: Reduziert Daten-Payloads und Netzwerkanfragen für bestimmte Clients, indem Daten serverseitig aggregiert und gefiltert werden.
Fazit
Das Node.js Backend for Frontend-Muster ist ein leistungsstarker architektonischer Ansatz, der die Herausforderungen bei der Bedienung verschiedener Frontend-Anwendungen von einem komplexen Microservices-Backend löst. Als maßgeschneiderter Vermittler optimiert ein Node.js BFF den Datenfluss, vereinfacht die Frontend-Entwicklung und fördert die unabhängige Weiterentwicklung von Client-Anwendungen und Kern-Backend-Diensten. Es stellt sicher, dass jedes Frontend genau die Daten erhält, die es benötigt, in dem Format, das es erwartet, was zu höherer Leistung und einer wartungsfreundlicheren Gesamtarchitektur führt. Erwägen Sie ein Node.js BFF, wenn Ihre Anwendung mehrere verschiedene Clients bedient, da es die Lücke zwischen granularen Backend-Diensten und spezialisierten Frontend-Anforderungen intelligent schließt.