首页 >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