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. 第三方中间件 :比如 cors、morgan 等

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 创建和服务启动分成两个文件,方便测试
  • 配置用环境变量,不要硬编码
  • 错误处理中间件要放在路由之后
相关推荐
To_OC5 小时前
从一头雾水到跑通全流程:我用一个周末啃透了JWT登录鉴权
前端·后端·http
前端兰博6 小时前
05-Redis
redis·后端
databook7 小时前
从手动检查到自动监控:一个数据质量工作流的实现
后端·python·数据分析
【JAVA】玩家7 小时前
Spring核心原理全解析:从零到生产实战
java·后端·spring
东风破_7 小时前
从 Neo4j 到 GraphRAG:用 Text-to-Cypher 构建图检索 RAG
人工智能·后端
llqbzllll9 小时前
Spring AI 工具调用不是反射一下就结束:用 2.0.1 跑通失败恢复与调用上限
人工智能·后端
狼爷10 小时前
Rust/Go/Java/Python/PHP 大比拼:负载下后端框架到底差多少?
java·后端·编程语言
HaipengYu10 小时前
深度解密 OpenCode 2 插件加载机制:Dual-Version (V1/V2) 适配实战与官方文档的“隐形陷阱”
node.js
mantou13210 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
程序员老赵11 小时前
Docker 部署 DeepSeek Harness:轻松搭建局域网里的 AI Agent 平台
后端·ai编程·deepseek