Express 中间件(App 中间件、Router 中间件、Express 内置中间件、第三方中间件、错误处理中间件)

120 阅读4分钟
Express 什么是中间件

简单理解中间件就是:Express调用的函数;函数调用时会被 Express 传入 3 个参数:reqresnext。参数与我们上篇文章讲路由的参数一致。

Express 认识中间件

在 Express 中,中间件(Middleware)  是核心机制之一,它可以拦截请求和响应的处理流程,对请求数据进行处理、修改响应行为,或执行通用逻辑(如日志记录、权限验证、数据解析等)。理解中间件的工作原理和使用场景,是掌握 Express 开发的关键。

中间件函数可以做什么
  1. 它可以执行任何代码;
  2. 更改 reqres 对象;
  3. 结束请求响应周期;
  4. 调用 stack 栈中的下一个中间件;
中间件的执行机制
  1. 匹配上的中间件会按照顺序执行;
  2. 一次只执行一个中间件,如果要执行下一个匹配上的中间件,调用 next()
  3. 如果当前中间件没有结束请求响应周期,必须调用 next();
中间件分类
  1. App 中间件
  2. Router 中间件
  3. Express 内置中间件
  4. 第三方中间件
  5. 错误处理中间件 比较特殊的中间件,上方提到了中间件有 3 个参数,它有 4 个参数
APP中间件
  1. use 中间件

    将指定的中间件函数或多个函数挂载到指定路径:当请求路径的基础部分与指定路径匹配时,中间件函数就会被执行。

    • 使用 app.use() 方法将中间件提供给 Express 调用
    • use() 方法的第一个参数默认就是'/',跟路径可省略
    • use() 方法可以匹配所有 HTTP 方法
    import express from 'express';
    import ejs from 'ejs';
    import { fileURLToPath } from 'url';
    import { dirname, join } from 'path';
    import htmlRouter from './router/html.js';
    import userRouter from './router/api/user.js';
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);
    const app = express();
    const port = 3000;
    
    app.use(express.static(join(__dirname, 'public')));
    app.set('views', join(__dirname, '/template'));
    app.set('view engine', 'html');
    app.engine('html', ejs.__express);
    
    
    // use() 方法的第一个参数默认就是'/',跟路径可省略;并匹配所有 HTTP 方法
    app.use((req, res, next) => {
      console.log(1, `${req.method} ${req.url}`);
      next() // 执行下一个匹配上的中间件
    })
    
    // 也可以在一个 use() 方法中注册多个中间件
    app.use(
        '/', 
        (req, res, next) => {
          console.log(2, `${req.method} ${req.url}`);
          next() // 执行下一个匹配上的中间件
        },
        (req, res, next) => {
          console.log(3, `${req.method} ${req.url}`);
          next() // 执行下一个匹配上的中间件
        },
    )
    
    // 当路径是 ‘/’ 返回 html
    app.use('/', htmlRouter); 
    
    // 当路径是 ‘/’ 时匹配不上,所以不会被执行
    // 当路径是 ‘/user’ 时候被执行,上方的匹配 ‘/’ 匹配也都会被执行
    app.use('/user', userRouter); 
    
    app.listen(port, () => {
      console.log(`Server is running on port ${port}`);
    });
    
  2. METHOD 和 all 中间件

    路由一个 HTTP 请求,其中 METHOD 是请求的 HTTP 方法,如 GET、PUT、POST 等,需为小写形式。因此,实际的方法有 app.get()app.post()app.put()等。

    • METHODall 方法是路径相等才会进行匹配
    • all() 方法会被所有 HTTP 方法所执行
    import express from 'express';
    import ejs from 'ejs';
    import { fileURLToPath } from 'url';
    import { dirname, join } from 'path';
    import htmlRouter from './router/html.js';
    import userRouter from './router/api/user.js';
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);
    const app = express();
    const port = 3000;
    
    app.use(express.static(join(__dirname, 'public')));
    app.set('views', join(__dirname, '/template'));
    app.set('view engine', 'html');
    app.engine('html', ejs.__express);
    
    // 模拟 get 请求
    // 被匹配 get 请求并执行
    app.get('/', (req, res, next) => {
      console.log(1, `${req.method} ${req.url}`);
      next()
    })
    
    // 非 get 请求不会被执行
    app.post('/', (req, res, next) => {
      console.log(1, `${req.method} ${req.url}`);
      next()
    })
    
    // all() 方法 会被所有 HTTP 方法匹配,所以会执行
    app.all('/', (req, res, next) => {
      console.log(2, `${req.method} ${req.url}`);
      next()
    })
    
    // 路径不相等,不会被执行
    app.all('/user/list', (req, res, next) => {
      console.log(2, `${req.method} ${req.url}`);
      next()
    })
    
    app.use('/', htmlRouter);
    
    app.use('/user', userRouter);
    
    app.listen(port, () => {
      console.log(`Server is running on port ${port}`);
    });
    
    • next() 我们也可以使用 next('route') 传递 route 跳出当前匹配执行一下条路由匹配内容
    import express from 'express';
    import ejs from 'ejs';
    import { fileURLToPath } from 'url';
    import { dirname, join } from 'path';
    import htmlRouter from './router/html.js';
    import userRouter from './router/api/user.js';
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);
    const app = express();
    const port = 3000;
    
    app.use(express.static(join(__dirname, 'public')));
    app.set('views', join(__dirname, '/template'));
    app.set('view engine', 'html');
    app.engine('html', ejs.__express);
    
    app.get('/',
      (req, res, next) => {
        console.log(1, `${req.method} ${req.url}`);
        // 执行 next('route') 会结束当前匹配,当前匹配下方的匹配规则 console.log(2, ...)将不会被执行
        next('route');
      },
      (req, res, next) => {
        console.log(2, `${req.method} ${req.url}`);
        next();
      },
    )
    
    app.use('/', htmlRouter);
    
    app.use('/user', userRouter);
    
    app.listen(port, () => {
      console.log(`Server is running on port ${port}`);
    });
    
Router中间件
  1. use 中间件
  2. METHOD 和 all 中间件

use、METHOD、all 中间件与 app中间件使用方法一致,不做代码示例了。把 app 换成自己创建的路由即可

Express 内置中间件
  1. urlencoded([options]): 处理请求体 application/x-www-form-urlencoded 格式的数据。
import express from 'express';
import {urlencoded} from "express";
app.use(urlencoded({ extended: true }));
  1. json([options]): 处理请求体 application/json 格式的数据。
import express from 'express';
import {json} from "express";
app.use(json());
  1. static(root, [options]): 提供静态文件资源
import express from 'express';
// static as staticMiddleware  表示给方法体内的 static 方法起一个别名叫 staticMiddleware
import {urlencoded, json, static as staticMiddleware} from "express";
import {dirname, join} from 'path';

// 获取当前模块的文件路径 
const __filename = fileURLToPath(import.meta.url); 

// 获取当前模块所在的目录路径 
const __dirname = dirname(__filename);

// 对外暴漏 public 文件夹,可通过URL 访问到内部资源,如:图片、样式、js、html 等
app.use(staticMiddleware(join(__dirname, 'public')));
Express 第三方中间件 cookie-parser

cookie-parser 是一个 Node.js 中间件,常用于 Express 应用中解析 HTTP 请求里的 Cookie 头信息。它能把 Cookie 字符串转换为 JavaScript 对象,方便开发者在应用里访问和操作客户端发送的 Cookie。下面详细介绍其安装、使用第三方中间件的方法、配置和示例。

在项目根目录下安装中间件

npm install cookie-parser

使用方法 在 Express 应用里引入并使用 cookie-parser 中间件,它会将解析后的 Cookie 挂载到 req.cookies 对象上。若提供了密钥,还能解析签名 Cookie 到 req.signedCookies 对象。

import express from 'express';
import cookieParser from 'cookie-parser';

const app = express();
const port = 3000;

// 使用 cookie-parser 中间件,可传入密钥用于解析签名 Cookie
app.use(cookieParser('your-secret-key'));

// 设置 Cookie 的路由
app.get('/set-cookie', (req, res) => {
  // 设置一个普通 Cookie
  res.cookie('username', 'JohnDoe', { maxAge: 900000, httpOnly: true });
  // 设置一个签名 Cookie
  res.cookie('signedCookie', 'signedValue', { signed: true });
  res.send('Cookies have been set');
});

// 获取 Cookie 的路由
app.get('/get-cookie', (req, res) => {
  // 获取普通 Cookie
  const username = req.cookies.username;
  // 获取签名 Cookie
  const signedValue = req.signedCookies.signedCookie;
  res.json({ username, signedValue });
});

// 删除 Cookie 的路由
app.get('/delete-signed-cookie', (req, res) => {
  // 删除名为 signedCookie 的签名 Cookie
  res.clearCookie('signedCookie', { signed: true });
  res.send('Signed cookie has been deleted');
});

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});
错误处理中间件

什么是错误处理中间件?错误处理中间件是一种特殊的中间件,用于捕获和处理应用程序中出现的同步或异步错误,并统一处理错误响应(如返回错误状态码、错误信息等)。它是 Express 错误处理机制的核心,能避免未处理的错误导致应用崩溃,同时让错误处理逻辑更集中、规范。

错误处理中间件的特点

参数要求: 与普通中间件不同,错误处理中间件必须包含 4 个参数(err, req, res, next),缺一不可。 err:捕获到的错误对象,其余三个参数与中间件一致

注册顺序: 必须注册在 所有路由中间件和普通中间件之后,否则无法捕获前面中间件抛出的错误。

访问 用户路由 代码示例:

import express from 'express';
import {urlencoded, json, static as staticMiddleware} from "express";
import ejs from 'ejs';
import {fileURLToPath} from 'url';
import {dirname, join} from 'path';
import htmlRouter from './router/html.js';
import userRouter from './router/api/user.js';

const app = express();
const port = 3000;
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

app.set('views', join(__dirname, '/template'));
app.set('view engine', 'html');
app.engine('html', ejs.__express);

app.use(staticMiddleware(join(__dirname, 'public')));
app.use(urlencoded({ extended: true }));
app.use(json());

// 页面路由
app.use('/', htmlRouter);

// 是否登录中间件
const isLongin = (req, res, next) => {
  // 当前用户是否登录
  if (req.cookies.token) {
    // 是否过期
    if (isExpired) {
      res.json({code: '401', msg: '登录过期,请重新登录'});
    } else {
      next();
    }
  } else {
    // 未登录,问题反馈给错误处理中间件
    next(new Error('用户未登录'));
  }
};

// 用户路由
app.use('/user', isLongin, userRouter);

// 错误处理中间件
app.use((err, req, res, next) => {
  // 默认为 500 错误,若状态码存在则使用自定义状态码
  const statusCode = err.statusCode || 500;
  
  // 接收到错误处理并执行返回相对应的 json
  res.status(statusCode).json({
    code: '-1',
    msg: err.message,
  });
})


app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});