跳到主要内容

Express.js 速查表

这是一份 Express.js 开发中常用的语法和配置速查表,涵盖了从基础设置到生产部署的核心知识点。

基础设置

Express 版本选择

Express 5.0 于 2024 年 10 月正式发布,带来了 Promise 支持、安全增强等重要改进:

# 安装 Express 5(推荐)
npm install express@5

# 安装 Express 4(稳定版)
npm install express@4

Express 5 环境要求:Node.js 18 或更高版本

Express 5 主要改进

  • 原生支持 Promise 错误处理,async 函数错误自动传递
  • 移除正则路由,防止 ReDoS 攻击
  • 路径匹配语法更新,通配符必须有名称
  • API 签名统一,移除废弃方法

创建应用

Express 应用的起点是调用 express() 函数创建应用实例。这是所有 Express 应用的基础:

const express = require('express');
const app = express();

// 监听端口
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});

内置中间件

Express 4.16.0 之后内置了几个常用的中间件,不再需要单独安装 body-parser

// 解析 JSON 格式的请求体
// Content-Type: application/json
app.use(express.json());

// 解析 URL 编码的请求体(表单数据)
// Content-Type: application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));

// 提供静态文件服务
// 访问 http://localhost:3000/images/logo.png
app.use(express.static('public'));

// 静态文件可配置多个目录和虚拟路径
app.use('/static', express.static('public'));

express.urlencodedextended 选项决定使用哪个库解析数据:true 使用 qs 库(支持嵌套对象),false 使用 querystring 库(不支持嵌套)。

应用配置

// 设置应用配置
app.set('view engine', 'ejs'); // 模板引擎
app.set('views', './views'); // 模板目录
app.set('trust proxy', true); // 信任反向代理
app.set('x-powered-by', false); // 隐藏 Express 标识

// 获取配置值
const engine = app.get('view engine');

// 布尔配置的快捷方法
app.enable('trust proxy'); // 等同于 app.set('trust proxy', true)
app.disable('x-powered-by'); // 等同于 app.set('x-powered-by', false)

路由

路由定义了应用如何响应客户端的特定请求。每个路由可以有一个或多个处理函数。

基本 HTTP 方法

Express 支持所有标准 HTTP 方法:

// GET 请求 - 获取资源
app.get('/users', (req, res) => {
res.json({ users: [] });
});

// POST 请求 - 创建资源
app.post('/users', (req, res) => {
const user = req.body;
res.status(201).json(user);
});

// PUT 请求 - 完整更新资源
app.put('/users/:id', (req, res) => {
res.json({ id: req.params.id, ...req.body });
});

// PATCH 请求 - 部分更新资源
app.patch('/users/:id', (req, res) => {
res.json({ id: req.params.id, updated: req.body });
});

// DELETE 请求 - 删除资源
app.delete('/users/:id', (req, res) => {
res.status(204).send();
});

// 匹配所有 HTTP 方法
app.all('/secret', (req, res, next) => {
console.log('访问秘密区域...');
next();
});

路由参数

路由参数用于捕获 URL 中的动态值,通过 : 定义:

// 单个参数
app.get('/users/:id', (req, res) => {
res.json({ userId: req.params.id });
});

// 多个参数
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({ userId, postId });
});

// 参数带正则验证(只匹配数字)
app.get('/products/:id(\\d+)', (req, res) => {
res.json({ productId: req.params.id });
});

// 可选参数
app.get('/books/:year?', (req, res) => {
const year = req.params.year || new Date().getFullYear();
res.json({ year });
});

查询字符串

查询字符串是 URL 中 ? 后面的部分,通过 req.query 访问:

// GET /search?q=express&page=2&limit=10
app.get('/search', (req, res) => {
const { q, page = 1, limit = 10 } = req.query;
res.json({ keyword: q, page, limit });
});

// 多个同名参数会变成数组
// GET /filter?tag=node&tag=express
app.get('/filter', (req, res) => {
// req.query.tag = ['node', 'express']
res.json({ tags: req.query.tag });
});

路由链式定义

app.route() 可以避免重复写相同路径:

app.route('/articles')
.get((req, res) => {
res.json({ action: '获取文章列表' });
})
.post((req, res) => {
res.status(201).json({ action: '创建文章' });
})
.put((req, res) => {
res.json({ action: '更新文章' });
})
.delete((req, res) => {
res.status(204).send();
});

路由模块化

使用 express.Router() 创建可复用的路由模块:

// routes/users.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
res.json({ users: [] });
});

router.post('/', (req, res) => {
res.status(201).json({ created: true });
});

module.exports = router;

// app.js
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);

Router 配置选项:

const router = express.Router({
caseSensitive: true, // 区分大小写
mergeParams: true, // 保留父路由的 params
strict: true // 严格模式,/foo 和 /foo/ 不同
});

请求对象 (req)

req 对象代表 HTTP 请求,包含了客户端发送的所有信息。

常用属性

属性说明示例值
req.params路由参数{ id: '123' }
req.query查询字符串{ page: '1' }
req.body请求体(需中间件){ name: 'John' }
req.headers请求头对象{ 'content-type': 'application/json' }
req.methodHTTP 方法'GET', 'POST'
req.url完整请求路径'/users?page=1'
req.path路径部分'/users'
req.hostname主机名'localhost'
req.ip客户端 IP'127.0.0.1'
req.protocol协议'http'
req.secure是否 HTTPSfalse
req.xhr是否 AJAXfalse

常用方法

// 获取请求头(不区分大小写)
const contentType = req.get('Content-Type');
const auth = req.get('authorization');

// 检查 Accept 头
req.accepts('json'); // 'json' 或 false
req.accepts(['json', 'html']); // 返回匹配的类型

// 检查 Content-Type
req.is('json'); // 'json' 或 false
req.is('application/json'); // true 或 false

// 获取参数化的路由
const route = req.route; // 当前匹配的路由对象

文件上传信息

使用 multer 中间件后,可通过以下属性访问文件:

// 单文件
app.post('/upload', upload.single('file'), (req, res) => {
console.log(req.file);
// {
// fieldname: 'file',
// originalname: 'photo.jpg',
// mimetype: 'image/jpeg',
// size: 102400,
// path: 'uploads/abc123'
// }
});

// 多文件
app.post('/photos', upload.array('photos'), (req, res) => {
console.log(req.files); // 文件数组
});

响应对象 (res)

res 对象用于向客户端发送响应。

发送响应

// 发送文本(自动设置 Content-Type)
res.send('Hello World');

// 发送 JSON
res.json({ message: 'success', data: {} });

// 发送带状态码的响应
res.status(404).json({ error: 'Not Found' });
res.status(201).send('Created');

// 发送状态码和对应文本
res.sendStatus(200); // 发送 'OK'
res.sendStatus(404); // 发送 'Not Found'

// 发送空响应
res.end();

设置响应头

// 设置单个响应头
res.set('Content-Type', 'application/json');

// 设置多个响应头
res.set({
'Cache-Control': 'no-cache',
'X-Custom-Header': 'value'
});

// CORS 相关头
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE');
// 设置 Cookie
res.cookie('name', 'value', {
maxAge: 900000, // 有效期(毫秒)
httpOnly: true, // 仅服务器可访问
secure: true, // 仅 HTTPS
sameSite: 'strict', // CSRF 防护:strict | lax | none
domain: 'example.com', // 域名
path: '/' // 路径
});

// 清除 Cookie
res.clearCookie('name');
res.clearCookie('name', { path: '/admin' }); // 需要相同的 path/domain

重定向

// 重定向到相对路径(默认 302)
res.redirect('/login');

// 永久重定向(301)
res.redirect(301, '/new-url');

// 重定向到外部 URL
res.redirect('https://example.com');

// 返回上一页
res.redirect('back'); // 基于 Referer 头

文件操作

// 下载文件(提示保存对话框)
res.download('/files/report.pdf');
res.download('/files/report.pdf', '报告.pdf'); // 自定义文件名

// 发送文件(直接显示)
res.sendFile('/path/to/file.html');
res.sendFile('index.html', { root: './public' });

// 设置附件头(配合 fs.createReadStream)
res.attachment('data.csv');
res.send('col1,col2\nval1,val2');

模板渲染

// 先设置模板引擎
app.set('view engine', 'ejs');

// 渲染模板
res.render('index', {
title: '首页',
user: { name: 'John' }
});

中间件

中间件是 Express 的核心机制,每个中间件可以访问 req、res 和 next。

中间件结构

// 标准中间件签名
function middleware(req, res, next) {
// 1. 执行代码
console.log('请求处理中');

// 2. 修改 req 或 res
req.user = { id: 1 };

// 3. 调用 next() 或结束响应
next(); // 传递控制权
}

// 错误处理中间件(四个参数)
function errorHandler(err, req, res, next) {
res.status(500).json({ error: err.message });
}

应用级中间件

// 匹配所有请求
app.use((req, res, next) => {
console.log('请求到达');
next();
});

// 匹配特定路径前缀
app.use('/api', (req, res, next) => {
console.log('API 请求');
next();
});

// 路由中使用的中间件
app.get('/protected',
authMiddleware, // 第一个中间件
checkPermission, // 第二个中间件
(req, res) => { // 最终处理
res.json({ data: 'protected data' });
}
);

路由级中间件

const router = express.Router();

// 只对该路由器生效
router.use((req, res, next) => {
console.log('路由级中间件');
next();
});

router.get('/list', (req, res) => {
res.json({ list: [] });
});

错误处理

// 触发错误
app.get('/error', (req, res, next) => {
const err = new Error('出错了');
err.status = 400;
next(err);
});

// 错误处理中间件(必须放最后)
app.use((err, req, res, next) => {
const status = err.status || 500;
res.status(status).json({
error: {
message: err.message,
// 生产环境不暴露堆栈
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
}
});
});

Express 5 异步错误

Express 5 原生支持 Promise 错误处理,这是最重要的改进之一:

// Express 4.x - 必须手动捕获和传递错误
app.get('/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: '用户不存在' });
}
res.json(user);
} catch (err) {
next(err); // 必须手动传递
}
});

// Express 5.x - 错误自动传递
app.get('/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
// 如果 findById 抛出错误,会自动传递给错误处理中间件

if (!user) {
throw new Error('用户不存在'); // 也会被自动捕获
}

res.json(user);
});

// 错误处理中间件(Express 4/5 通用)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: process.env.NODE_ENV === 'production'
? '服务器错误'
: err.message
});
});

注意:成功完成操作时仍需手动调用 next() 或发送响应。

常用第三方中间件

安全中间件

const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

// 设置安全相关的 HTTP 头
app.use(helmet());

// 跨域资源共享
app.use(cors({
origin: 'https://example.com',
methods: ['GET', 'POST'],
credentials: true
}));

// 请求限流
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 最多 100 次请求
message: { error: '请求过于频繁' }
});
app.use('/api', limiter);

日志中间件

const morgan = require('morgan');

// 开发环境:彩色输出
app.use(morgan('dev'));

// 生产环境:Apache 格式
app.use(morgan('combined'));

// 自定义格式
morgan.token('user-id', req => req.user?.id);
app.use(morgan(':method :url :status :user-id'));
const cookieParser = require('cookie-parser');
const session = require('express-session');

// Cookie 解析
app.use(cookieParser('secret-key'));

// Session
app.use(session({
secret: 'your-secret',
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // 仅 HTTPS
maxAge: 86400000 // 24 小时
}
}));

JWT 认证

const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;

// 生成 Token
function generateToken(user) {
return jwt.sign(
{ id: user.id, role: user.role },
SECRET,
{ expiresIn: '7d' }
);
}

// 验证 Token
function verifyToken(token) {
try {
return jwt.verify(token, SECRET);
} catch {
return null;
}
}

// 认证中间件
function auth(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];

if (!token) {
return res.status(401).json({ error: '未提供 Token' });
}

const decoded = verifyToken(token);
if (!decoded) {
return res.status(401).json({ error: 'Token 无效或已过期' });
}

req.user = decoded;
next();
}

// 使用
app.post('/login', (req, res) => {
const user = authenticate(req.body);
const token = generateToken(user);
res.json({ token });
});

app.get('/profile', auth, (req, res) => {
res.json({ user: req.user });
});

数据库操作 (Mongoose)

const mongoose = require('mongoose');

// 连接数据库
mongoose.connect(process.env.DATABASE_URL);

// 定义 Schema
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, unique: true },
age: { type: Number, min: 0 },
createdAt: { type: Date, default: Date.now }
});

// 创建模型
const User = mongoose.model('User', userSchema);

// CRUD 操作
// 查询所有
const users = await User.find();

// 条件查询
const user = await User.findOne({ email: '[email protected]' });
const user = await User.findById('507f1f77bcf86cd799439011');

// 创建
const newUser = await User.create({ name: 'John', email: '[email protected]' });

// 更新
await User.findByIdAndUpdate(id, { name: 'New Name' }, { new: true });

// 删除
await User.findByIdAndDelete(id);

// 计数
const count = await User.countDocuments({ age: { $gte: 18 } });

// 分页
const page = 1, limit = 10;
const results = await User.find()
.skip((page - 1) * limit)
.limit(limit);

文件上传 (Multer)

const multer = require('multer');

// 基本配置
const upload = multer({
dest: 'uploads/',
limits: {
fileSize: 5 * 1024 * 1024 // 5MB
},
fileFilter: (req, file, cb) => {
const allowedTypes = ['image/jpeg', 'image/png'];
if (!allowedTypes.includes(file.mimetype)) {
return cb(new Error('只允许上传图片'));
}
cb(null, true);
}
});

// 单文件上传
app.post('/avatar', upload.single('avatar'), (req, res) => {
res.json({ file: req.file });
});

// 多文件上传(同名字段)
app.post('/photos', upload.array('photos', 10), (req, res) => {
res.json({ count: req.files.length });
});

// 多字段上传
app.post('/profile', upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'documents', maxCount: 5 }
]), (req, res) => {
res.json({ files: req.files });
});

// 自定义存储
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({ storage });

测试 (Supertest)

const request = require('supertest');
const app = require('./app');

// 测试 GET 请求
test('GET /users 返回用户列表', async () => {
const res = await request(app)
.get('/users')
.expect('Content-Type', /json/)
.expect(200);

expect(Array.isArray(res.body)).toBe(true);
});

// 测试 POST 请求
test('POST /users 创建用户', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'Test', email: '[email protected]' })
.expect(201);

expect(res.body.name).toBe('Test');
});

// 测试带认证的请求
test('GET /profile 需要认证', async () => {
const res = await request(app)
.get('/profile')
.set('Authorization', 'Bearer invalid-token')
.expect(401);
});

// 测试文件上传
test('POST /upload 上传文件', async () => {
const res = await request(app)
.post('/upload')
.attach('file', __dirname + '/fixtures/test.jpg')
.expect(200);
});

进程管理 (PM2)

# 启动应用
pm2 start app.js

# 集群模式(利用多核)
pm2 start app.js -i max

# 常用命令
pm2 list # 查看所有进程
pm2 logs # 查看日志
pm2 monit # 监控面板
pm2 restart all # 重启所有
pm2 stop all # 停止所有
pm2 delete all # 删除所有

# 持久化
pm2 save # 保存当前进程列表
pm2 startup # 设置开机自启

# 配置文件 ecosystem.config.js
module.exports = {
apps: [{
name: 'my-app',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env_production: {
NODE_ENV: 'production',
PORT: 3000
}
}]
};

pm2 start ecosystem.config.js --env production

Docker 部署

# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=mongodb://mongo:27017/mydb
depends_on:
- mongo
mongo:
image: mongo:7
volumes:
- mongo_data:/data/db

volumes:
mongo_data:
# 构建镜像
docker build -t my-express-app .

# 运行容器
docker run -p 3000:3000 my-express-app

# Docker Compose
docker-compose up -d # 后台运行
docker-compose logs -f # 查看日志
docker-compose down # 停止并删除

环境变量

// 加载 .env 文件
require('dotenv').config();

// 常用环境变量
const config = {
port: process.env.PORT || 3000,
env: process.env.NODE_ENV || 'development',
database: {
url: process.env.DATABASE_URL,
name: process.env.DATABASE_NAME
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
}
};
# .env.example(提交到仓库)
NODE_ENV=development
PORT=3000
DATABASE_URL=mongodb://localhost:27017/mydb
JWT_SECRET=your-secret-key

# .env(不提交,添加到 .gitignore)
NODE_ENV=production
DATABASE_URL=mongodb://prod-server:27017/mydb
JWT_SECRET=your-production-secret

HTTP 状态码速查

状态码含义使用场景
2xx 成功
200OK一般成功响应
201CreatedPOST 创建资源成功
204No ContentDELETE/PUT 成功,无返回体
3xx 重定向
301Moved Permanently永久重定向
302Found临时重定向
304Not Modified缓存有效
4xx 客户端错误
400Bad Request参数错误、格式错误
401Unauthorized未登录、Token 无效
403Forbidden无权限
404Not Found资源不存在
409Conflict资源冲突(如重复创建)
422Unprocessable Entity验证失败
429Too Many Requests请求过于频繁
5xx 服务器错误
500Internal Server Error服务器内部错误
502Bad Gateway上游服务错误
503Service Unavailable服务不可用

常见错误处理

// 404 处理
app.use((req, res) => {
res.status(404).json({ error: '路由不存在' });
});

// 全局错误处理
app.use((err, req, res, next) => {
console.error(err.stack);

// 处理特定错误类型
if (err.name === 'ValidationError') {
return res.status(400).json({ error: err.message });
}

if (err.name === 'UnauthorizedError') {
return res.status(401).json({ error: '认证失败' });
}

if (err.name === 'SyntaxError') {
return res.status(400).json({ error: 'JSON 格式错误' });
}

// 默认 500 错误
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? '服务器错误'
: err.message
});
});

最佳实践清单

  • 安全:使用 helmet、验证输入、参数化查询、HTTPS
  • 性能:启用压缩、静态文件缓存、数据库连接池
  • 日志:请求日志、错误日志、结构化日志格式
  • 错误处理:全局错误中间件、友好的错误消息
  • 测试:单元测试、集成测试、测试覆盖率
  • 文档:API 文档(Swagger/OpenAPI)、代码注释
  • 监控:健康检查端点、性能监控、告警机制

Express 5 迁移速查

自动迁移工具

# 运行所有 codemods
npx codemod@latest @expressjs/v5-migration-recipe

破坏性变化速查表

Express 4.xExpress 5.x说明
app.del()app.delete()方法名变更
req.param('name')req.params/body/query.name明确来源
res.json(obj, status)res.status(status).json(obj)状态码顺序
res.send(status)res.sendStatus(status)发送状态文本
res.redirect('back')res.redirect(req.get('Referrer') || '/')移除魔法字符串
res.sendfile()res.sendFile()驼峰命名
req.acceptsCharset()req.acceptsCharsets()方法名复数化
/*/*splat/{*splat}通配符必须有名称
/:file.:ext?/:file{.:ext}可选参数新语法

路由参数变化

// Express 4.x - 通配符
app.get('/*', handler);

// Express 5.x - 通配符必须有名称
app.get('/*splat', handler); // 不匹配根路径
app.get('/{*splat}', handler); // 匹配所有路径包括根路径

// Express 4.x - 正则路由
app.get('/user/:id(\\d+)', handler);

// Express 5.x - 使用验证中间件
app.get('/user/:id',
param('id').isInt(),
handler
);

请求体变化

// Express 4.x - req.body 默认为 {}
console.log(req.body); // {}

// Express 5.x - req.body 默认为 undefined
console.log(req.body); // undefined

// 安全访问
const data = req.body || {};
const { name = '' } = req.body || {};

完整迁移指南

详细的迁移说明和代码示例,请参阅 Express 5.x 新特性与迁移