一、基础篇:Express 核心概念入门
1. 第一个 Express 应用:极简启动
先看一个最基础的 Express 应用,理解其核心结构:
JavaScript
// 引入 Express 模块
const express = require('express');
// 创建 Express 应用实例
const app = express();
// 定义端口
const PORT = 3000;
// 定义根路由:处理 GET 请求
app.get('/', (req, res) => {
res.send('Hello Express!');
});
// 启动服务器监听端口
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
核心解析:
-
express():创建一个 Express 应用实例,封装了 HTTP 服务器的创建和请求处理逻辑。 -
app.get(path, handler):定义路由,监听path路径的 GET 请求,handler是请求处理函数,接收req(请求对象)和res(响应对象)。 -
res.send():发送响应内容,自动设置Content-Type(字符串默认text/html,对象默认application/json)。
2. 路由(Routing):请求的"导航系统"
路由是 Express 处理请求的核心,负责将不同的 HTTP 请求(方法+路径)映射到对应的处理函数。
(1)基本路由:支持所有 HTTP 方法
JavaScript
// GET 请求:获取数据
app.get('/users', (req, res) => {
res.json({ users: ['Alice', 'Bob'] });
});
// POST 请求:创建数据
app.post('/users', (req, res) => {
res.status(201).json({ message: 'User created' });
});
// PUT 请求:更新数据(完整替换)
app.put('/users/:id', (req, res) => {
res.json({ message: `User ${req.params.id} updated` });
});
// DELETE 请求:删除数据
app.delete('/users/:id', (req, res) => {
res.json({ message: `User ${req.params.id} deleted` });
});
(2)路由参数与查询字符串
-
路由参数 :通过
:name定义,存储在req.params中(如/users/:id匹配/users/123,req.params.id = '123')。 -
查询字符串 :URL 中
?后的参数,存储在req.query中(如/users?page=1&limit=10,req.query = { page: '1', limit: '10' })。
JavaScript
app.get('/users/:id/posts', (req, res) => {
const { id } = req.params;
const { page = 1, limit = 10 } = req.query;
res.json({ userId: id, page, limit });
});
(3)路由模块化:用 express.Router() 拆分代码
当路由增多时,将所有路由写在一个文件中会导致代码臃肿。使用 Router 可以将路由按模块拆分:
JavaScript
// routes/users.js:用户模块路由
const express = require('express');
const router = express.Router();
// 注意:路径是相对于挂载点的(这里的 '/' 对应 '/users')
router.get('/', (req, res) => res.json({ users: [] }));
router.get('/:id', (req, res) => res.json({ user: req.params.id }));
module.exports = router;
JavaScript
// app.js:主文件中挂载路由
const userRouter = require('./routes/users');
// 将 userRouter 挂载到 '/users' 路径下
app.use('/users', userRouter);
(4)路由链:简化同一资源的多个路由
对于同一资源(如 /users)的多个 HTTP 方法,可以用 app.route() 链式调用:
JavaScript
app.route('/users')
.get((req, res) => res.json({ users: [] }))
.post((req, res) => res.status(201).json({ message: 'Created' }));
3. 中间件(Middleware):Express 的"灵魂"
中间件是 Express 最核心的设计理念------它是一个函数,在请求-响应周期中被调用,可以访问 req、res 和 next(下一个中间件函数)。中间件可以执行任何代码、修改请求/响应对象、结束请求-响应周期,或调用下一个中间件。
(1)中间件的分类与用法
-
应用级中间件 :绑定到
app实例,对所有请求生效(或指定路径生效)。 -
路由级中间件 :绑定到
express.Router()实例,仅对该路由模块生效。 -
错误处理中间件 :专门处理错误,有 4 个参数
(err, req, res, next)。 -
内置中间件 :Express 自带的中间件(如
express.json、express.static)。 -
第三方中间件 :社区开发的中间件(如
morgan、cors)。
(2)内置中间件:最常用的两个
-
express.json():解析请求体中的 JSON 数据(存储到req.body)。 -
express.urlencoded():解析 URL 编码的请求体(如表单提交,存储到req.body)。 -
express.static():托管静态文件(如 HTML、CSS、图片)。
JavaScript
// 解析 JSON 请求体(必须放在路由之前)
app.use(express.json());
// 解析 URL 编码请求体(extended: false 表示使用 querystring 库解析)
app.use(express.urlencoded({ extended: false }));
// 托管 public 目录下的静态文件(访问 /css/style.css 对应 public/css/style.css)
app.use(express.static('public'));
(3)自定义应用级中间件
JavaScript
// 全局中间件:每个请求都会执行
app.use((req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next(); // 调用下一个中间件,否则请求会挂起
});
// 路径特定中间件:仅对 /api 路径下的请求生效
app.use('/api', (req, res, next) => {
console.log('Accessing API');
next();
});
(4)错误处理中间件
错误处理中间件必须有 4 个参数,且要放在所有中间件和路由之后:
JavaScript
// 模拟一个错误路由
app.get('/error', (req, res, next) => {
const error = new Error('Something went wrong!');
error.status = 500;
next(error); // 将错误传递给错误处理中间件
});
// 错误处理中间件
app.use((err, req, res, next) => {
const status = err.status || 500;
const message = err.message || 'Internal Server Error';
res.status(status).json({ error: message });
});
二、进阶篇:构建生产级应用
1. 统一错误处理:优雅处理同步与异步错误
Express 4.x 默认不会捕获异步代码中的错误(如 async/await 中的 throw),需要手动包装或使用工具。
(1)自定义错误类
先定义一个统一的错误类,方便传递状态码和错误信息:
JavaScript
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true; // 标记为可预期的业务错误(非系统错误)
Error.captureStackTrace(this, this.constructor);
}
}
(2)异步错误包装器
用一个高阶函数包装异步路由,自动捕获错误并传递给错误处理中间件:
JavaScript
const asyncWrapper = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next); // 捕获异步错误,传递给 next(err)
};
};
(3)完整示例
JavaScript
// 路由中使用
app.get('/users/:id', asyncWrapper(async (req, res, next) => {
const user = await User.findById(req.params.id); // 模拟异步数据库查询
if (!user) {
throw new AppError('User not found', 404); // 抛出业务错误
}
res.json({ user });
}));
// 错误处理中间件(区分业务错误和系统错误)
app.use((err, req, res, next) => {
const { statusCode = 500, message, isOperational } = err;
res.status(statusCode).json({
error: isOperational ? message : 'Internal Server Error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack }) // 开发环境返回堆栈
});
});
2. RESTful API 设计实践
Express 非常适合构建 RESTful API,核心原则是"资源导向",用 HTTP 方法表示操作类型。
(1)资源命名与 HTTP 方法对应
| 资源 | GET(获取) | POST(创建) | PUT(完整更新) | PATCH(部分更新) | DELETE(删除) |
|---|---|---|---|---|---|
| /users | 获取所有用户 | 创建新用户 | 批量更新用户(不推荐) | 批量部分更新(不推荐) | 删除所有用户(不推荐) |
| /users/:id | 获取指定 ID 的用户 | 不允许 | 替换指定 ID 的用户 | 部分更新指定 ID 的用户 | 删除指定 ID 的用户 |
(2)示例:用户管理 API
JavaScript
// 模拟用户数据
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
];
// 获取所有用户(支持分页)
app.get('/users', asyncWrapper(async (req, res) => {
const { page = 1, limit = 10 } = req.query;
const start = (page - 1) * limit;
const end = start + parseInt(limit);
const paginatedUsers = users.slice(start, end);
res.json({
data: paginatedUsers,
page: parseInt(page),
limit: parseInt(limit),
total: users.length
});
}));
// 创建用户
app.post('/users', asyncWrapper(async (req, res, next) => {
const { name, email } = req.body;
if (!name || !email) {
throw new AppError('Name and email are required', 400);
}
const newUser = { id: users.length + 1, name, email };
users.push(newUser);
res.status(201).json({ data: newUser });
}));
3. 文件上传:Multer 中间件
Express 本身不支持文件上传,需要使用第三方中间件 multer。
(1)安装与基本配置
Bash
npm install multer
JavaScript
const multer = require('multer');
// 配置存储:指定文件存储路径和文件名
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/'); // 存储到 uploads 目录(需提前创建)
},
filename: (req, file, cb) => {
// 生成唯一文件名:时间戳 + 原始文件名
const uniqueName = `${Date.now()}-${file.originalname}`;
cb(null, uniqueName);
}
});
// 配置文件过滤器:只允许图片上传
const fileFilter = (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true); // 允许上传
} else {
cb(new AppError('Only images are allowed', 400), false); // 拒绝上传
}
};
// 初始化 Multer
const upload = multer({
storage,
fileFilter,
limits: { fileSize: 5 * 1024 * 1024 } // 限制文件大小 5MB
});
(2)单文件与多文件上传
JavaScript
// 单文件上传:字段名为 avatar
app.post('/upload/avatar', upload.single('avatar'), (req, res) => {
res.json({
message: 'Avatar uploaded successfully',
file: req.file // 上传的文件信息(路径、大小等)
});
});
// 多文件上传:字段名为 photos,最多 5 个
app.post('/upload/photos', upload.array('photos', 5), (req, res) => {
res.json({
message: 'Photos uploaded successfully',
files: req.files // 上传的文件数组
});
});
4. 安全最佳实践
生产环境中必须关注安全,以下是 Express 应用的核心安全措施:
(1)Helmet:设置安全 HTTP 头
helmet 是一个安全中间件,通过设置各种 HTTP 头来防止常见的 Web 攻击(如 XSS、点击劫持)。
Bash
npm install helmet
JavaScript
const helmet = require('helmet');
app.use(helmet()); // 启用所有默认安全头
(2)CORS:配置跨域资源共享
如果 API 被前端跨域调用,需要配置 cors 中间件。
Bash
npm install cors
JavaScript
const cors = require('cors');
// 允许所有来源(开发环境用)
app.use(cors());
// 或仅允许特定来源(生产环境用)
app.use(cors({
origin: 'https://your-frontend.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
(3)速率限制:防止暴力攻击
用 express-rate-limit 限制同一 IP 的请求频率。
Bash
npm install express-rate-limit
JavaScript
const rateLimit = require('express-rate-limit');
// 全局速率限制:15 分钟内最多 100 个请求
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests, please try again later'
});
app.use(globalLimiter);
// 登录接口单独限制:15 分钟内最多 5 个请求
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many login attempts, please try again later'
});
app.post('/login', loginLimiter, (req, res) => {
res.json({ message: 'Login successful' });
});
(4)输入验证:防止恶意数据
用 joi 或 express-validator 验证请求参数和请求体。
Bash
npm install joi
JavaScript
const Joi = require('joi');
// 定义用户创建的验证 schema
const userSchema = Joi.object({
name: Joi.string().min(2).max(30).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(18).max(100)
});
// 在路由中使用
app.post('/users', (req, res, next) => {
const { error } = userSchema.validate(req.body);
if (error) {
throw new AppError(error.details[0].message, 400);
}
next(); // 验证通过,继续处理
}, (req, res) => {
res.json({ message: 'User created' });
});
5. 性能优化
生产环境中需要关注性能,以下是几个核心优化点:
(1)压缩响应:使用 compression 中间件
Bash
npm install compression
JavaScript
const compression = require('compression');
app.use(compression()); // 压缩所有响应(放在路由之前)
(2)静态文件缓存
给静态文件设置 maxAge,让浏览器缓存:
JavaScript
app.use(express.static('public', {
maxAge: '1d' // 缓存 1 天
}));
(3)避免同步操作
所有 I/O 操作(数据库、文件系统)都使用异步方式,避免阻塞事件循环。