首頁 >web前端 >js教程 >掌握 Express.js:深入探討

掌握 Express.js:深入探討

Susan Sarandon
Susan Sarandon原創
2025-01-05 06:46:40373瀏覽

Mastering Express.js: A Deep Dive

Express 是 Node.js 中極為常用的 Web 伺服器應用程式框架。本質上,框架是一種遵循特定規則的程式碼結構,具有兩個關鍵特徵:

  • 封裝了API,讓開發者更專注於業務程式碼的撰寫。
  • 已建立流程和標準規格。

Express框架的核心特性如下:

  • 它可以設定中間件來回應各種HTTP請求。
  • 它定義了一個路由表,用於執行不同類型的HTTP請求操作。
  • 支援傳遞給範本參數,實現HTML頁面的動態渲染。

本文將透過實​​作一個簡單的 LikeExpress 類別來分析 Express 如何實現中介軟體註冊、下一個機制以及路由處理。

快速分析

我們先透過兩個 Express 程式碼範例來探索它提供的功能:

Express 官網 Hello World 範例

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}`);
});

入口檔app.js分析

以下是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實例應用程式主要有三個核心方法:

  1. app.use([path,]callback[,callback...]):用來註冊中間件。當請求路徑符合設定的規則時,就會執行對應的中介軟體函數。
    • path:指定呼叫中間件函數的路徑。
    • 回調:回呼函數可以採用多種形式。它可以是單一中間件函數、一系列以逗號分隔的中間件函數、中間件函數陣列或以上所有函數的組合。
  2. app.get() 和 app.post():這些方法與 use() 類似,也是用來註冊中間件。但是,它們綁定到 HTTP 請求方法。只有使用對應的HTTP請求方法才會觸發相關中間件的註冊。
  3. app.listen():負責建立一個httpServer並傳遞server.listen()所需的參數。

程式碼實現

透過對Express程式碼功能的分析,我們知道Express的實現重點在於三點:

  • 中間件函數的註冊過程。
  • 中介軟體功能中的核心next機制。
  • 路由處理,重點是路徑匹配。

基於這些點,我們將在下面實作一個簡單的 LikeExpress 類別。

1. 類別的基本結構

首先明確指出該類別需要實作的主要方法:

  • use():實作通用中間件註冊。
  • get() 和 post():實作HTTP請求相關的中間件註冊。
  • Listen():本質上就是httpServer的listen()函式。在該類別的listen()函數中,建立了一個httpServer,傳入參數,監聽請求,回呼函數(req, res) => {} 已執行。

回顧原生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;

2. 中介軟體註冊

從 app.use([path,]callback[,callback...]) 中,我們可以看到中間件可以是函數數組,也可以是單一函數。為了簡化實現,我們將中間件統一處理為函數數組。 LikeExpress類別中use()、get()、post()這三個方法都可以實作中間件註冊。只是因為請求方式不同,觸發的中間件有所不同。所以我們考慮:

  • 抽像出一個通用的中間件註冊函數。
  • 為這三個方法建立中間件函數數組,用於儲存不同請求對應的中間件。由於use()是所有請求的通用中間件註冊方法,因此儲存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();
};

use()、get() 和 post() 的實現

透過通用的中間件註冊函數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}`);
});

3. 路由匹配處理

當註冊函數的第一個參數是路由時,只有當請求路徑匹配該路由或是其子路由時,才會觸發對應的中間件函數。所以,我們需要一個路由匹配函數,根據請求方法和請求路徑提取匹配路由的中間件數組,供後續的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() 函數:

  • 每次從中間件佇列數組中依序擷取一個中間件。
  • 將 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
    };
}

Leapcell:用於 Web 託管、非同步任務和 Redis 的下一代無伺服器平台

Mastering Express.js: A Deep Dive

最後要介紹一個非常適合部署Express的平台:Leapcell。

Leapcell 是無伺服器平台,具有以下特性:

1. 多語言支持

  • 使用 JavaScript、Python、Go 或 Rust 進行開發。

2.免費部署無限個項目

  • 只需支付使用費用-無請求,不收費。

3. 無與倫比的成本效益

  • 即用即付,無閒置費用。
  • 範例:25 美元支援 694 萬個請求,平均回應時間為 60 毫秒。

4.簡化的開發者體驗

  • 直覺的使用者介面,輕鬆設定。
  • 完全自動化的 CI/CD 管道和 GitOps 整合。
  • 即時指標和日誌記錄以獲取可行的見解。

5. 輕鬆的可擴充性和高效能

  • 自動擴充以輕鬆處理高並發。
  • 零營運開銷-只需專注於建置。

在文件中探索更多內容!

Leapcell Twitter:https://x.com/LeapcellHQ

以上是掌握 Express.js:深入探討的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn