Was ist Express?
Express ist ein minimales Web-Framework für Node.js. Es schreibt dir weder deine Ordnerstruktur, deine Datenbank noch deine Architektur vor. Es bietet dir drei Dinge und hält sich ansonsten zurück: eine Möglichkeit zum URL-Matching, ein Request- und ein Response-Objekt sowie eine middleware-Kette, die diese miteinander verbindet.
Genau diese Zurückhaltung ist der Kernpunkt. Express erschien im Jahr 2010, als das integrierte http-Modul von Node es erforderte, Routing und Body-Parsing manuell zu schreiben. Express reduzierte diese Aufgaben auf wenige Zeilen. Fünfzehn Jahre später ist es immer noch die Standardwahl für Node-APIs, und seine Konzepte wurden von fast jedem nachfolgenden Framework übernommen.
Wenn du Express verstehst, verstehst du die grundlegende Struktur von serverseitigem JavaScript.
Der Request-Response-Zyklus
Im Kern ist eine Express-App eine Funktion, die einen Request empfängt und eine Response sendet. Alles andere ist lediglich ein Komfort-Feature darauf.
import express from "express";
const app = express();
app.get("/", (req, res) => {
res.send("Hello, world");
});
Das req-Objekt kapselt die eingehende Nachricht von Node.js: req.params, req.query, req.body und req.headers liefern Ihnen den Input. Das res-Objekt kapselt die ausgehende Nachricht: res.status(), res.json(), res.send() und res.set() formen den Output. Ein Handler beendet den Request, indem er eine der res-Methoden aufruft.
Routing
Eine Route besteht aus einer Methode, einem Pfad und einem oder mehreren Handlern. Pfade können statisch sein, mit :name parametrisiert werden oder über Muster (Patterns) abgeglichen werden.
app.get("/posts", listPosts);
app.get("/posts/:id", getPost);
app.post("/posts", createPost);
app.put("/posts/:id", replacePost);
app.patch("/posts/:id", updatePost);
app.delete("/posts/:id", deletePost);
Routenparameter werden in req.params übergeben und Query-Strings in req.query.
app.get("/posts/:id", (req, res) => {
const { id } = req.params; // "/posts/42" -> "42"
const { fields } = req.query; // "?fields=title" -> "title"
res.json({ id, fields });
});
Sie können auch mehrere Handler an eine einzige Route übergeben. Diese werden nacheinander ausgeführt, bis einer die Antwort beendet. Auf diese Weise können Sie pro Route middleware für Aufgaben wie Validierung oder Autorisierung hinzufügen.
app.post("/posts", requireAuth, validatePost, createPost);
Middleware: Das eine Konzept, das man beherrschen muss
Middleware ist das Herzstück von Express. Es handelt sich dabei um jede Funktion, die (req, res, next) erhält und entweder die Antwort beendet oder next() aufruft, um fortzufahren.
function logger(req, res, next) {
const start = Date.now();
res.on("finish", () => {
console.log(`${req.method} ${req.url} ${res.statusCode} ${Date.now() - start}ms`);
});
next();
}
app.use(logger);
Middleware wird in der Reihenfolge ausgeführt, in der sie registriert wurde. Diese eine Regel erklärt den Großteil des Express-Verhaltens: Registrieren Sie den JSON-Parser vor den Routen, die req.body lesen, registrieren Sie die Authentifizierung vor geschützten Routen und registrieren Sie den 404-Handler nach jeder Route.
Es gibt drei Arten, die man kennen sollte:
- Application middleware —
app.use(fn)wird für jede Anfrage ausgeführt. - Router middleware —
router.use(fn)wird nur für die Pfade dieses Routers ausgeführt. - Route middleware —
app.get(path, fn, handler)wird nur für diese spezifische Route ausgeführt.
Eine Middleware, die vier Argumente entgegennimmt, ist ein Error Handler und wird nur ausgeführt, wenn etwas next(err) aufruft.
Router halten große Apps übersichtlich
Wenn eine App wächst, wird eine einzelne Datei für alle Routen unhandlich. express.Router() ermöglicht es, zusammengehörige Routen zu gruppieren und sie unter einem Präfix zu mounten.
// routes/posts.js
import { Router } from "express";
const router = Router();
router.get("/", listPosts);
router.post("/", createPost);
export default router;
// app.js
import posts from "./routes/posts.js";
app.use("/api/v1/posts", posts);
Jetzt ist jede Datei für eine Ressource zuständig und das URL-Präfix wird an einer zentralen Stelle definiert. Diese Struktur verhindert, dass Express-Projekte in einem unübersichtlichen Haufen von Endpunkten enden.
Fehlerbehandlung
Fehler sollten an einem zentralen Ort zusammenlaufen. Rufen Sie next(err) von überall aus auf, und Express springt direkt zum ersten Error-Handler mit vier Argumenten.
app.get("/users/:id", async (req, res, next) => {
try {
const user = await db.user.findById(req.params.id);
if (!user) return res.status(404).json({ error: "not_found" });
res.json(user);
} catch (err) {
next(err);
}
});
app.use((err, req, res, next) => {
console.error(err);
res.status(err.status ?? 500).json({
error: err.code ?? "internal_error",
message: err.message,
});
});
Express 5 leitet rejectete Promises automatisch an den Error-Handler weiter, sodass async-Handler keinen Wrapper mehr benötigen. Dennoch macht die explizite Verwendung von try/catch die Absicht deutlich und hält den Code portabel. Registrieren Sie nach allen Routen einen 404-Handler, damit nicht zugeordnete Pfade einen ordnungsgemäßen JSON-Fehler anstelle der Standard-HTML-Seite von Express zurückgeben.
Den Request-Body auslesen
Express parst Request-Bodies standardmäßig nicht. Füge die integrierten Parser vor den Routen hinzu, die diese benötigen.
app.use(express.json()); // application/json
app.use(express.urlencoded({ extended: true })); // form posts
Für Datei-Uploads solltest du multer verwenden. Für eine umfassendere Validierung kombiniere das Parsing mit einer Schema-Library wie Zod oder Joi und lehne ungültige Eingaben frühzeitig ab, noch bevor sie deine Handler erreichen.
Grundlagen für Sicherheit und Produktion
Express ist bewusst minimalistisch gehalten, sodass einige Einzeiler als middleware die wichtigsten Grundlagen abdecken:
import helmet from "helmet";
import cors from "cors";
import rateLimit from "express-rate-limit";
app.use(helmet()); // sensible security headers
app.use(cors({ origin: "https://app.example.com" }));
app.use(rateLimit({ windowMs: 60_000, max: 100 }));
Deaktivieren Sie außerdem x-powered-by, halten Sie Abhängigkeiten aktuell, validieren und begrenzen Sie die Größe von Anfragen und vertrauen Sie niemals Benutzereingaben vom Client. Dies sind dieselben Herausforderungen, mit denen jedes Backend konfrontiert ist; Express bietet Ihnen lediglich einen kompakten, bewährten Ort, um diese zu handhaben.
Testing
Da Handler einfache Funktionen sind, lässt sich Express leicht testen. supertest bootet die App im selben Prozess und ermöglicht echte HTTP-Assertions, ohne einen Port öffnen zu müssen.
import request from "supertest";
import app from "../app.js";
test("GET /posts returns a list", async () => {
const res = await request(app).get("/posts").expect(200);
expect(Array.isArray(res.body)).toBe(true);
});
Trennen Sie die App-Definition (app.js) vom Server-Start (server.js), damit Tests die App importieren können, ohne dass diese auf Anfragen hört. Diese eine Trennung macht Integrationstests trivial.
Best Practices
- Halten Sie Route-Handler schlank; verschieben Sie die Logik in Services und Data-Access-Layer.
- Teilen Sie Routen nach Features mit
express.Router()auf und mounten Sie diese unter einem Versions-Präfix. - Registrieren Sie middleware bewusst und in der richtigen Reihenfolge: zuerst Parser, dann Auth, dann Routen, dann 404 und schließlich Fehler.
- Leiten Sie jeden Fehler mit
next(err)weiter und formatieren Sie Antworten an einer zentralen Stelle. - Validieren und begrenzen Sie alle Eingaben, bevor sie die Business-Logik erreichen.
- Nutzen Sie
helmet,corsund Rate Limiting vom ersten Tag an. - Trennen Sie die App vom Server, damit die Tests schnell bleiben.
Häufige Fehler
express.json()vergessen und sich wundern, warumreq.bodyundefined ist.- Abgelehnte Promises in async-Handlern unter Express 4 ignorieren.
- Den 404- oder Error-Handler vor den Routen registrieren, die er abfangen soll.
- Eine einzelne Routen-Datei auf hunderte von Zeilen anwachsen lassen.
- Bei Fehlern 200 zurückgeben, anstatt die entsprechenden Status-Codes zu verwenden.
req.bodyohne Validierung vertrauen.res.send,res.jsonundres.endunvorhersehbar mischen.
Wie geht es weiter?
Express vermittelt die Konzepte von Request, Response und der middleware-Kette – Grundlagen, die du immer wieder verwenden wirst. Wenn du mehr Geschwindigkeit und integrierte Validierung suchst, lies als Nächstes den Fastify-Guide. Wenn du Struktur und Dependency Injection für ein großes Team benötigst, wechsle zu NestJS. Für Edge- und Serverless-Deployments bietet Hono den gleichen Stil auf einer Web-Standards-Runtime. Und falls dir das alles zu schnell ging, schau dir noch einmal die Node.js basics an, auf denen all dies aufbaut.