Express is an extremely commonly used web server application framework in Node.js. Essentially, a framework is a code structure that adheres to specific rules and has two key characteristics:
The core features of the Express framework are as follows:
This article will analyze how Express implements middleware registration, the next mechanism, and route handling by implementing a simple LikeExpress class.
Let's first explore the functions it provides through two Express code examples:
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}`); });
The following is the code of the entry file app.js of the Express project generated by the express-generator scaffolding:
// 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` is an Express instance 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;
From the above two code segments, we can see that the Express instance app mainly has three core methods:
Based on the analysis of the functions of the Express code, we know that the implementation of Express focuses on three points:
Based on these points, we will implement a simple LikeExpress class below.
First, clarify the main methods that this class needs to implement:
Review the usage of the native Node httpServer:
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}`); });
Accordingly, the basic structure of the LikeExpress class is as follows:
// 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` is an Express instance 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;
From app.use([path,] callback [, callback...]), we can see that middleware can be an array of functions or a single function. To simplify the implementation, we uniformly process the middleware as an array of functions. In the LikeExpress class, the three methods use(), get(), and post() can all implement middleware registration. Only the triggered middleware varies due to different request methods. So we consider:
The middleware array needs to be placed in a public area for easy access by methods in the class. So, we put the middleware array in the constructor() constructor function.
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"); });
Middleware registration means storing the middleware in the corresponding middleware array. The middleware registration function needs to parse the incoming parameters. The first parameter may be a route or middleware, so it is necessary to first determine whether it is a route. If it is, output it as it is; otherwise, the default is the root route, and then convert the remaining middleware parameters into an array.
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(); };
With the general middleware registration function register(), it is easy to implement use(), get(), and post(), just store the middleware in the corresponding arrays.
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}`); });
When the first parameter of the registration function is a route, the corresponding middleware function will be triggered only when the request path matches the route or is its sub-route. So, we need a route matching function to extract the middleware array of the matching route according to the request method and request path for the subsequent callback() function to execute:
// 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` is an Express instance 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;
Then, in the callback function callback() of the httpServer, extract the middleware that needs to be executed:
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"); });
The parameters of the Express middleware function are req, res, and next, where next is a function. Only by calling it can the middleware functions be executed in sequence, similar to next() in ES6 Generator. In our implementation, we need to write a next() function with the following requirements:
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(); };
constructor() { // List of stored middleware this.routes = { all: [], // General middleware get: [], // Middleware for get requests post: [], // Middleware for post requests }; }
Finally, let me introduce a platform that is very suitable for deploying Express: Leapcell.
Leapcell is a serverless platform with the following characteristics:
Explore more in the documentation!
Leapcell Twitter: https://x.com/LeapcellHQ
The above is the detailed content of Mastering Express.js: A Deep Dive. For more information, please follow other related articles on the PHP Chinese website!