Express 进阶实战
更新: 8/24/2026字数: 0 字 时长: 0 分钟
上一篇我们掌握了 Express 的基础用法(路由、中间件、请求响应)。 本篇进入进阶内容,重点解决真实项目中最常见的问题:
- 路由如何优雅拆分
- 错误如何统一处理
- 异步代码如何正确捕获错误
- 如何做参数校验
- 如何实现 JWT 登录鉴权
- 推荐的项目结构
一、路由模块化(强烈推荐)
随着接口变多,把所有路由写在 app.js 里会难以维护。推荐使用 express.Router() 拆分。
1. 目录结构示例
bash
src/
├── app.js
├── server.js
├── routes/
│ ├── index.js
│ ├── user.js
│ └── auth.js
├── controllers/
│ ├── userController.js
│ └── authController.js
├── middleware/
│ ├── auth.js
│ ├── errorHandler.js
│ └── validate.js
└── utils/
└── jwt.js2. 路由拆分示例
routes/user.js
js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
const auth = require('../middleware/auth');
// 公开接口
router.get('/', userController.getUsers);
// 需要登录的接口
router.get('/profile', auth, userController.getProfile);
router.put('/profile', auth, userController.updateProfile);
module.exports = router;routes/index.js
js
const express = require('express');
const router = express.Router();
const userRouter = require('./user');
const authRouter = require('./auth');
router.use('/users', userRouter);
router.use('/auth', authRouter);
module.exports = router;app.js
js
const express = require('express');
const routes = require('./routes');
const app = express();
app.use(express.json());
app.use('/api', routes); // 统一加前缀 /api
module.exports = app;二、统一错误处理(非常重要)
1. 自定义错误类
js
// utils/AppError.js
class AppError extends Error {
constructor(message, statusCode = 400) {
super(message);
this.statusCode = statusCode;
this.isOperational = true; // 标记为可预期的业务错误
}
}
module.exports = AppError;2. 异步错误捕获(关键)
Express 默认不会自动捕获 async/await 抛出的错误,需要手动处理。
推荐封装一个高阶函数:
js
// utils/catchAsync.js
const catchAsync = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
};
module.exports = catchAsync;使用方式:
js
const catchAsync = require('../utils/catchAsync');
const AppError = require('../utils/AppError');
exports.getUser = catchAsync(async (req, res, next) => {
const user = await User.findById(req.params.id);
if (!user) {
return next(new AppError('用户不存在', 404));
}
res.json({ code: 200, data: user });
});3. 全局错误处理中间件
js
// middleware/errorHandler.js
const AppError = require('../utils/AppError');
module.exports = (err, req, res, next) => {
console.error(err);
// 已知的业务错误
if (err.isOperational) {
return res.status(err.statusCode).json({
code: err.statusCode,
message: err.message
});
}
// 未知错误
res.status(500).json({
code: 500,
message: '服务器内部错误'
});
};在 app.js 最后挂载:
js
const errorHandler = require('./middleware/errorHandler');
// 404
app.use((req, res, next) => {
next(new AppError(`接口不存在: ${req.originalUrl}`, 404));
});
// 全局错误处理(必须放在最后)
app.use(errorHandler);三、参数校验(推荐使用 Zod 或 Joi)
以 Zod 为例(轻量、TypeScript 友好):
bash
npm install zodmiddleware/validate.js
js
const validate = (schema) => {
return (req, res, next) => {
try {
req.body = schema.parse(req.body); // 校验并清洗数据
next();
} catch (err) {
next(err); // 交给全局错误处理
}
};
};
module.exports = validate;使用示例
js
const { z } = require('zod');
const validate = require('../middleware/validate');
const loginSchema = z.object({
username: z.string().min(3),
password: z.string().min(6)
});
router.post('/login', validate(loginSchema), authController.login);四、JWT 登录鉴权实战
1. 安装依赖
bash
npm install jsonwebtoken bcryptjs
npm install -D dotenv2. 工具函数
utils/jwt.js
js
const jwt = require('jsonwebtoken');
const secret = process.env.JWT_SECRET || 'your_jwt_secret';
exports.signToken = (payload) => {
return jwt.sign(payload, secret, { expiresIn: '7d' });
};
exports.verifyToken = (token) => {
return jwt.verify(token, secret);
};3. 鉴权中间件
middleware/auth.js
js
const { verifyToken } = require('../utils/jwt');
const AppError = require('../utils/AppError');
const catchAsync = require('../utils/catchAsync');
module.exports = catchAsync(async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return next(new AppError('请先登录', 401));
}
const token = authHeader.split(' ')[1];
const decoded = verifyToken(token);
// 这里可以查数据库确认用户是否还存在
req.user = decoded; // 把用户信息挂到 req 上
next();
});4. 登录接口示例
js
const bcrypt = require('bcryptjs');
const { signToken } = require('../utils/jwt');
const AppError = require('../utils/AppError');
const catchAsync = require('../utils/catchAsync');
exports.login = catchAsync(async (req, res, next) => {
const { username, password } = req.body;
// 1. 查找用户(示例用假数据)
const user = { id: 1, username: 'admin', password: 'hashed_password' };
// 2. 验证密码
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return next(new AppError('用户名或密码错误', 401));
}
// 3. 签发 token
const token = signToken({ id: user.id, username: user.username });
res.json({
code: 200,
message: '登录成功',
token
});
});前端请求时携带:
http
Authorization: Bearer <token>五、常用实用中间件
1. CORS 跨域
bash
npm install corsjs
const cors = require('cors');
app.use(cors());
// 或更精细控制
app.use(cors({
origin: ['http://localhost:5173'],
credentials: true
}));2. 请求日志
bash
npm install morganjs
const morgan = require('morgan');
app.use(morgan('dev'));3. 安全相关(生产推荐)
bash
npm install helmet express-rate-limitjs
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
app.use(helmet());
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 限制100次请求
});
app.use('/api', limiter);六、环境变量管理
bash
npm install dotenv项目根目录创建 .env:
env
PORT=3000
JWT_SECRET=your_super_secret_key
NODE_ENV=development在入口文件最顶部加载:
js
require('dotenv').config();使用:
js
const PORT = process.env.PORT || 3000;注意:.env 不要提交到 Git,应加入 .gitignore。
七、推荐项目启动方式
server.js
js
require('dotenv').config();
const app = require('./app');
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});app.js 只负责配置中间件和路由,不启动服务,方便测试。