Express.js beherrschen: Ein tiefer Einstieg
James Reed
Infrastructure Engineer · Leapcell

Express ist ein äußerst häufig verwendetes Webserver-Anwendungs-Framework in Node.js. Im Wesentlichen ist ein Framework eine Code-Struktur, die sich an bestimmte Regeln hält und zwei Hauptmerkmale aufweist:
- Es kapselt APIs und ermöglicht es Entwicklern, sich stärker auf das Schreiben von Business-Code zu konzentrieren.
- Es verfügt über etablierte Prozesse und Standardspezifikationen.
Die Kernfunktionen des Express-Frameworks sind wie folgt:
- Es kann Middleware konfigurieren, um auf verschiedene HTTP-Anforderungen zu reagieren.
- Es definiert eine Routentabelle für die Ausführung verschiedener Arten von HTTP-Anfrageaktionen.
- Es unterstützt das Übergeben von Parametern an Vorlagen, um eine dynamische Darstellung von HTML-Seiten zu erreichen.
Dieser Artikel analysiert, wie Express die Middleware-Registrierung, den Next-Mechanismus und das Routen-Handling durch die Implementierung einer einfachen LikeExpress-Klasse implementiert.
Express-Analyse
Lassen Sie uns zunächst die Funktionen untersuchen, die es durch zwei Express-Codebeispiele bietet:
Express Offizielle Webseite Hello World Beispiel
const express = require('express'); const app = express(); const port = 3000; app.get('/', (req, res) => { res.send('Hello World!'); }); app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`); });
Analyse der Entry-Datei app.js
Das Folgende ist der Code der Entry-Datei app.js
des Express-Projekts, das durch das express-generator
-Scaffolding generiert wurde:
// Handle errors caused by unmatched routes const createError = require('http-errors'); const express = require('express'); const path = require('path'); const indexRouter = require('./routes/index'); const usersRouter = require('./routes/users'); // `app` ist eine Express-Instanz const app = express(); // View engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); // Parse JSON format data in post requests and add the `body` field to the `req` object app.use(express.json()); // Parse the urlencoded format data in post requests and add the `body` field to the `req` object app.use(express.urlencoded({ extended: false })); // Static file handling app.use(express.static(path.join(__dirname, 'public'))); // Register top-level routes app.use('/', indexRouter); app.use('/users', usersRouter); // Catch 404 errors and forward them to the error handler app.use((req, res, next) => { next(createError(404)); }); // Error handling app.use((err, req, res, next) => { // Set local variables to display error messages in the development environment res.locals.message = err.message; // Decide whether to display the full error according to the environment variable. Display in development, hide in production. res.locals.error = req.app.get('env') === 'development'? err : {}; // Render the error page res.status(err.status || 500); res.render('error'); }); module.exports = app;
Aus den obigen beiden Code-Segmenten können wir ersehen, dass die Express-Instanz app
hauptsächlich drei Kernmethoden hat:
app.use([path,] callback [, callback...])
: Wird verwendet, um Middleware zu registrieren. Wenn der Anfragepfad mit den festgelegten Regeln übereinstimmt, wird die entsprechende Middleware-Funktion ausgeführt.path
: Gibt den Pfad für den Aufruf der Middleware-Funktion an.callback
: Die Callback-Funktion kann verschiedene Formen annehmen. Es kann eine einzelne Middleware-Funktion, eine Reihe von durch Kommas getrennten Middleware-Funktionen, ein Array von Middleware-Funktionen oder eine Kombination aus all dem oben Genannten sein.
app.get()
undapp.post()
: Diese Methoden ähnelnuse()
, auch zur Registrierung von Middleware. Sie sind jedoch an HTTP-Anfragemethoden gebunden. Nur wenn die entsprechende HTTP-Anfragemethode verwendet wird, wird die Registrierung der relevanten Middleware ausgelöst.app.listen()
: Verantwortlich für das Erstellen eines HTTP-Servers und das Übergeben der vonserver.listen()
benötigten Parameter.
Code-Implementierung
Basierend auf der Analyse der Funktionen des Express-Codes wissen wir, dass sich die Implementierung von Express auf drei Punkte konzentriert:
- Der Registrierungsprozess von Middleware-Funktionen.
- Der Kern-Next-Mechanismus in den Middleware-Funktionen.
- Routen-Handling, mit Fokus auf Pfadabgleich.
Basierend auf diesen Punkten werden wir im Folgenden eine einfache LikeExpress-Klasse implementieren.
1. Grundstruktur der Klasse
Klären Sie zunächst die Hauptmethoden, die diese Klasse implementieren muss:
use()
: Implementiert die allgemeine Middleware-Registrierung.get()
undpost()
: Implementieren die Middleware-Registrierung in Bezug auf HTTP-Anfragen.listen()
: Im Wesentlichen ist es dielisten()
-Funktion von httpServer. In derlisten()
-Funktion dieser Klasse wird ein httpServer erstellt, Parameter werden durchgeleitet, Anfragen werden abgehört und die Callback-Funktion(req, res) => {}
wird ausgeführt.
Überprüfen Sie die Verwendung des nativen Node httpServer:
const http = require("http"); const server = http.createServer((req, res) => { res.end("hello"); }); server.listen(3003, "127.0.0.1", () => { console.log("node service started successfully"); });
Dementsprechend ist die Grundstruktur der LikeExpress-Klasse wie folgt:
const http = require('http'); class LikeExpress { constructor() {} use() {} get() {} post() {} // httpServer callback function callback() { return (req, res) => { res.json = function (data) { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(data)); }; }; } listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); } } module.exports = () => { return new LikeExpress(); };
2. Middleware-Registrierung
Von app.use([path,] callback [, callback...])
können wir sehen, dass es sich bei Middleware um ein Array von Funktionen oder um eine einzelne Funktion handeln kann. Um die Implementierung zu vereinfachen, verarbeiten wir die Middleware einheitlich als ein Array von Funktionen. In der LikeExpress-Klasse können die drei Methoden use()
, get()
und post()
alle die Middleware-Registrierung implementieren. Nur die ausgelöste Middleware variiert aufgrund unterschiedlicher Anfragemethoden. Also betrachten wir:
- Abstrahieren einer allgemeinen Middleware-Registrierungsfunktion.
- Erstellen von Arrays von Middleware-Funktionen für diese drei Methoden, um die Middleware zu speichern, die unterschiedlichen Anforderungen entspricht. Da
use()
eine allgemeine Middleware-Registrierungsmethode für alle Anforderungen ist, ist das Array, dasuse()
-Middleware speichert, die Vereinigung der Arrays fürget()
undpost()
.
Middleware-Warteschlangen-Array
Das Middleware-Array muss sich in einem öffentlichen Bereich befinden, damit die Methoden in der Klasse problemlos darauf zugreifen können. Daher platzieren wir das Middleware-Array in der constructor()
-Konstruktorfunktion.
constructor() { // Liste der gespeicherten Middleware this.routes = { all: [], // Allgemeine Middleware get: [], // Middleware für Get-Anfragen post: [], // Middleware für Post-Anfragen }; }
Middleware-Registrierungsfunktion
Middleware-Registrierung bedeutet, die Middleware im entsprechenden Middleware-Array zu speichern. Die Middleware-Registrierungsfunktion muss die eingehenden Parameter analysieren. Der erste Parameter kann eine Route oder Middleware sein, daher muss zunächst ermittelt werden, ob es sich um eine Route handelt. Wenn dies der Fall ist, geben Sie sie so aus, wie sie ist. Andernfalls ist die Standardeinstellung die Stammroute, und konvertieren Sie dann die verbleibenden Middleware-Parameter in ein Array.
register(path) { const info = {}; // Wenn der erste Parameter eine Route ist if (typeof path === "string") { info.path = path; // Konvertieren Sie ab dem zweiten Parameter in ein Array und speichern Sie es im Middleware-Array info.stack = Array.prototype.slice.call(arguments, 1); } else { // Wenn der erste Parameter keine Route ist, ist die Standardeinstellung die Stammroute und alle Routen werden ausgeführt info.path = '/'; info.stack = Array.prototype.slice.call(arguments, 0); } return info; }
Implementierung von use()
, get()
und post()
Mit der allgemeinen Middleware-Registrierungsfunktion register()
ist es einfach, use()
, get()
und post()
zu implementieren. Speichern Sie einfach die Middleware in den entsprechenden Arrays.
use() { const info = this.register.apply(this, arguments); this.routes.all.push(info); } get() { const info = this.register.apply(this, arguments); this.routes.get.push(info); } post() { const info = this.register.apply(this, arguments); this.routes.post.push(info); }
3. Routenabgleichsverarbeitung
Wenn der erste Parameter der Registrierungsfunktion eine Route ist, wird die entsprechende Middleware-Funktion nur ausgelöst, wenn der Anfragepfad mit der Route übereinstimmt oder eine Unterroute davon ist. Daher benötigen wir eine Routenabgleichsfunktion, um das Middleware-Array der übereinstimmenden Route entsprechend der Anfragemethode und dem Anfragepfad zu extrahieren, damit die nachfolgende callback()
-Funktion ausgeführt werden kann:
match(method, url) { let stack = []; // Browser's built-in icon request if (url === "/favicon") { return stack; } // Get routes let curRoutes = []; curRoutes = curRoutes.concat(this.routes.all); curRoutes = curRoutes.concat(this.routes[method]); curRoutes.forEach((route) => { if (url.indexOf(route.path) === 0) { stack = stack.concat(route.stack); } }); return stack; }
Extrahieren Sie dann in der Callback-Funktion callback()
des HTTP-Servers die Middleware, die ausgeführt werden muss:
callback() { return (req, res) => { res.json = function (data) { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(data)); }; const url = req.url; const method = req.method.toLowerCase(); const resultList = this.match(method, url); this.handle(req, res, resultList); }; }
4. Implementierung des Next-Mechanismus
Die Parameter der Express-Middleware-Funktion sind req
, res
und next
, wobei next
eine Funktion ist. Nur durch Aufrufen dieser Funktion können die Middleware-Funktionen nacheinander ausgeführt werden, ähnlich wie next()
in ES6 Generator. In unserer Implementierung müssen wir eine next()
-Funktion mit den folgenden Anforderungen schreiben:
- Extrahieren Sie jedes Mal eine Middleware aus dem Middleware-Warteschlangen-Array.
- Übergeben Sie die Funktion
next()
an die extrahierte Middleware. Da das Middleware-Array öffentlich ist, wird bei jeder Ausführung vonnext()
die erste Middleware-Funktion im Array entnommen und ausgeführt, wodurch der Effekt der sequenziellen Ausführung von Middleware erzielt wird.
// Core next mechanism handle(req, res, stack) { const next = () => { const middleware = stack.shift(); if (middleware) { middleware(req, res, next); } }; next(); }
Express Code
const http = require('http'); const slice = Array.prototype.slice; class LikeExpress { constructor() { // List of stored middleware this.routes = { all: [], get: [], post: [], }; } register(path) { const info = {}; // If the first parameter is a route if (typeof path === "string") { info.path = path; // Convert to an array starting from the second parameter and store it in the stack info.stack = slice.call(arguments, 1); } else { // If the first parameter is not a route, the default is the root route, and all routes will execute info.path = '/'; info.stack = slice.call(arguments, 0); } return info; } use() { const info = this.register.apply(this, arguments); this.routes.all.push(info); } get() { const info = this.register.apply(this, arguments); this.routes.get.push(info); } post() { const info = this.register.apply(this, arguments); this.routes.post.push(info); } match(method, url) { let stack = []; // Browser's built-in icon request if (url === "/favicon") { return stack; } // Get routes let curRoutes = []; curRoutes = curRoutes.concat(this.routes.all); curRoutes = curRoutes.concat(this.routes[method]); curRoutes.forEach((route) => { if (url.indexOf(route.path) === 0) { stack = stack.concat(route.stack); } }); return stack; } // Core next mechanism handle(req, res, stack) { const next = () => { const middleware = stack.shift(); if (middleware) { middleware(req, res, next); } }; next(); } callback() { return (req, res) => { res.json = function (data) { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(data)); }; const url = req.url; const method = req.method.toLowerCase(); const resultList = this.match(method, url); this.handle(req, res, resultList); }; } listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); } } module.exports = () => { return new LikeExpress(); };
Leapcell: The Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis
Finally, let me introduce a platform that is very suitable for deploying Express: Leapcell.
Leapcell is a serverless platform with the following characteristics:
1. Multi-Language Support
- Develop with JavaScript, Python, Go, or Rust.
2. Deploy unlimited projects for free
- Pay only for usage — no requests, no charges.
3. Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
4. Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
5. Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the documentation!
Leapcell Twitter: https://x.com/LeapcellHQ