Express 是 Node.js 中極為常用的 Web 伺服器應用程式框架。本質上,框架是一種遵循特定規則的程式碼結構,具有兩個關鍵特徵:
Express框架的核心特性如下:
本文將透過實作一個簡單的 LikeExpress 類別來分析 Express 如何實現中介軟體註冊、下一個機制以及路由處理。
我們先透過兩個 Express 程式碼範例來探索它提供的功能:
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}`); });
以下是express-generator腳手架產生的Express專案的入口檔案app.js的程式碼:
// 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;
從上面兩段程式碼可以看出,Express實例應用程式主要有三個核心方法:
透過對Express程式碼功能的分析,我們知道Express的實現重點在於三點:
基於這些點,我們將在下面實作一個簡單的 LikeExpress 類別。
首先明確指出該類別需要實作的主要方法:
回顧原生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}`); });
相應地,LikeExpress類別的基本結構如下:
// 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;
從 app.use([path,]callback[,callback...]) 中,我們可以看到中間件可以是函數數組,也可以是單一函數。為了簡化實現,我們將中間件統一處理為函數數組。 LikeExpress類別中use()、get()、post()這三個方法都可以實作中間件註冊。只是因為請求方式不同,觸發的中間件有所不同。所以我們考慮:
中間件數組需要放置在公共區域,以便於類別中的方法存取。所以,我們把中間件數組放在constructor()建構子中。
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"); });
中間件註冊是指將中介軟體儲存在對應的中間件陣列中。中間件註冊函數需要解析傳入的參數。第一個參數可能是路由,也可能是中介軟體,所以需要先判斷是否為路由。如果是,則原樣輸出;否則預設為根路由,然後將剩餘的中間件參數轉為數組。
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(); };
透過通用的中間件註冊函數register(),很容易實作use()、get()、post(),只需將中間件儲存在對應的陣列中即可。
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}`); });
當註冊函數的第一個參數是路由時,只有當請求路徑匹配該路由或是其子路由時,才會觸發對應的中間件函數。所以,我們需要一個路由匹配函數,根據請求方法和請求路徑提取匹配路由的中間件數組,供後續的callback()函數執行:
// 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;
然後,在httpServer的回呼函數callback()中,提取出需要執行的中間件:
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"); });
Express中間件函數的參數為req、res、next,其中next是一個函數。只有呼叫它,中間件函數才能依序執行,類似ES6 Generator中的next()。在我們的實作中,我們需要寫一個具有以下要求的 next() 函數:
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 }; }
最後要介紹一個非常適合部署Express的平台:Leapcell。
Leapcell 是無伺服器平台,具有以下特性:
在文件中探索更多內容!
Leapcell Twitter:https://x.com/LeapcellHQ
以上是掌握 Express.js:深入探討的詳細內容。更多資訊請關注PHP中文網其他相關文章!