03 — 核心框架:App 与中间件


3.1 中间件是什么

Express 的核心就是中间件(Middleware)

一个请求过来,会依次经过一个个中间件,每个中间件可以:

  • 读取/修改 request 对象
  • 读取/修改 response 对象
  • 结束响应(直接返回)
  • 调用 next() 把控制权交给下一个中间件
bash 复制代码
请求 → 中间件1 → 中间件2 → 中间件3 → 路由处理 → 响应
                      ↓
                 出错 → 错误处理中间件

中间件的类型

  1. 应用级中间件 :用 app.use() 挂载,对所有请求生效
  2. 路由级中间件 :绑定在 Router 上,只对该路由生效
  3. 错误处理中间件 :有 4 个参数 (error, req, res, next),专门处理错误
  4. 内置中间件 :Express 自带的,比如 express.json()
  5. 第三方中间件 :比如 corsmorgan

3.2 App 的组织方式

不要把所有东西都塞在 server.ts 里。我们分成两个文件:

  • app.ts --- 创建 Express App,注册中间件和路由
  • server.ts --- 启动服务,监听端口

这样做的好处是测试方便 :测试时直接 import createApp() 拿到 app 实例,不需要启动服务器。


3.3 完整的 App 创建

src/app.ts

typescript 复制代码
import express from 'express';
import { errorHandler } from './middlewares/error-handler.js';
import { notFound } from './middlewares/not-found.js';
import { requestLogger } from './middlewares/logger.js';
import { createRouter } from './routers/index.js';

export function createApp() {
  const app = express();

  // ===== 安全相关 =====
  app.disable('x-powered-by');  // 隐藏 Express 标识,防止被针对性攻击

  // ===== 全局中间件(按顺序执行)=====

  // 1. JSON 请求体解析
  app.use(express.json({ limit: '10mb' }));

  // 2. 请求日志(只记录 API 请求)
  app.use('/api', requestLogger);

  // ===== 路由 =====
  app.use('/api', createRouter());

  // ===== 错误处理(注意顺序!必须在路由之后)=====
  app.use(notFound);       // 404
  app.use(errorHandler);   // 全局错误捕获

  return app;
}

中间件的顺序很重要

Express 按代码书写顺序执行中间件。常见的坑:

  • ❌ 把 express.json() 放在路由之后 → 拿不到请求体
  • ❌ 把错误处理中间件放在路由之前 → 捕获不到路由里的错误
  • ❌ 把鉴权中间件放在路由之后 → 等于没鉴权

正确顺序:


3.4 服务启动入口

src/server.ts

typescript 复制代码
import { createApp } from './app.js';

const PORT = Number(process.env.PORT) || 3000;
const HOST = process.env.HOST || '0.0.0.0';

const app = createApp();

const server = app.listen(PORT, HOST, () => {
  console.log(`Server running at http://${HOST}:${PORT}`);
});

// 端口占用处理
server.once('error', (error: NodeJS.ErrnoException) => {
  if (error.code === 'EADDRINUSE') {
    console.error(`端口 ${PORT} 已被占用`);
  } else {
    console.error(error);
  }
  process.exitCode = 1;
});

// 优雅退出:收到关闭信号时,先关闭服务器再退出
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
  process.once(signal, () => {
    console.log('正在关闭服务器...');
    server.close((error) => {
      if (error) {
        console.error(error);
        process.exitCode = 1;
      } else {
        console.log('服务器已关闭');
      }
    });
  });
}

为什么要优雅退出

  • 正在处理的请求能正常完成
  • 可以关闭数据库连接、清理资源
  • 不会突然中断导致数据不一致

3.5 环境配置

不要把端口号、数据库地址这些硬编码在代码里,用环境变量管理。

src/config/server.ts

typescript 复制代码
export const SERVER_PORT = Number(process.env.SERVER_PORT) || 3000;
export const SERVER_HOST = process.env.SERVER_HOST || '0.0.0.0';
export const SERVER_NAME = process.env.SERVER_NAME || 'my-backend';

根目录创建 .env

env 复制代码
SERVER_PORT=3000
SERVER_HOST=0.0.0.0
SERVER_NAME=my-backend

如果用 .env 文件,需要安装 dotenv 并在入口文件最顶部引入:

bash 复制代码
npm install dotenv

src/server.ts 最顶部加:

typescript 复制代码
import 'dotenv/config';

3.6 常用的中间件清单

中间件 作用 安装
express.json() 解析 JSON 请求体 Express 内置
express.urlencoded() 解析表单请求体 Express 内置
express.static() 托管静态文件 Express 内置
cors 跨域 npm install cors
helmet 安全头 npm install helmet
morgan 访问日志 npm install morgan
cookie-parser 解析 Cookie npm install cookie-parser
express-rate-limit 限流 npm install express-rate-limit

3.7 小结

  • Express 的核心是中间件,顺序很重要
  • 把 App 创建和服务启动分成两个文件,方便测试
  • 配置用环境变量,不要硬编码
  • 错误处理中间件要放在路由之后
相关推荐
小蒜学长1 小时前
基于小程序的绘画作品创作与分享社区系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·绘画作品·创作分享社区
云浪1 小时前
从 0 手写一个 MCP Server:让 Copilot 调用 Tool 完成四则运算
javascript·node.js·mcp
江湖十年1 小时前
Go GOPROXY 兜底链的坑:从一次诡异的 CI lint 失败说起
后端·面试·go
考虑考虑9 小时前
docker compose V2版本新属性
运维·后端·自动化运维
GreenTea11 小时前
7000 万 QPS、500 PB:OpenAI 如何用一个 Python 存储平台撑住 10 亿用户
后端·架构
Flynt11 小时前
Java 27 升级实测:默认值动得比新特性多,有个老参数会让 JVM 直接起不来
java·jvm·后端
Bs_MoneyMagnet11 小时前
基于springboot+vue的个人健康管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·vue3·springboot3·计算机毕业设计
GreenTea11 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
Bs_MoneyMagnet14 小时前
基于springboot+vue的心理咨询预约与随访平台的设计与实现 源码+文档
vue.js·spring boot·后端·spring·毕业设计·旅游·计算机毕业设计