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 创建和服务启动分成两个文件,方便测试
  • 配置用环境变量,不要硬编码
  • 错误处理中间件要放在路由之后
相关推荐
独泪了无痕5 小时前
Spring Data Redis 快速上手
spring boot·redis·后端
m0_639906949 小时前
Linux 操作系统入门:目录结构、服务排查常用命令、Shell 脚本基础
linux·运维·后端
holly就是我10 小时前
AI 编程助手为什么跑偏?用开源 ccglass 看清 Prompt、工具调用和 Token 消耗
人工智能·后端
鸿蒙开发10 小时前
共享 Schema 与 SchemaFactory
前端·后端·harmonyos
用户7884773163410 小时前
小程序后端三选一:云开发、.NET WebAPI、Node,我按客户现状分了三种答案
后端
aiopencode10 小时前
局域网设备抓包怎么做?用 TraceEagle 抓电视、游戏机和 IoT 流量
后端·ios
智驭未来掌门人10 小时前
CLion 开发 Qt 项目的优势
后端
EnCi Zheng10 小时前
后端怎么把AI接口稳定安全低成本接进业务:超时重试、密钥隔离与成本熔断清单
人工智能·后端·安全·架构·api
Bazingga10 小时前
从一家酒店讲懂Docker
后端
SEO_juper11 小时前
Node.js 重定向链检测工具:301 跳了几跳、有没有成环,一次看清
xml·开发语言·python·node.js·json·bash