Node.js 凭借其非阻塞 I/O 和事件驱动架构,成为构建高并发 API 服务的热门选择。本文将带你从零搭建一个生产级 RESTful API 服务,涵盖项目结构设计、JWT 鉴权、统一错误处理、请求校验、日志记录和性能优化等核心实践。
一、项目初始化与目录结构
使用 Express 框架搭建项目骨架,配合 TypeScript 提升代码可维护性。
mkdir node-api-demo && cd node-api-demo
npm init -y
npm install express cors helmet morgan dotenv bcryptjs jsonwebtoken express-validator
npm install -D typescript ts-node nodemon @types/express @types/node @types/jsonwebtoken @types/bcryptjs
npx tsc --init
推荐的目录结构如下:
node-api-demo/
├── src/
│ ├── config/ # 配置文件
│ ├── controllers/ # 控制器层
│ ├── middlewares/ # 中间件
│ ├── models/ # 数据模型
│ ├── routes/ # 路由定义
│ ├── services/ # 业务逻辑层
│ ├── utils/ # 工具函数
│ └── app.ts # 应用入口
├── .env
├── tsconfig.json
└── package.json
二、应用入口配置
在 src/app.ts 中配置 Express 应用,集成常用安全和日志中间件:
import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';
import { errorHandler } from './middlewares/errorHandler';
import authRoutes from './routes/auth';
import userRoutes from './routes/users';
dotenv.config();
const app: Application = express();
// 安全头
app.use(helmet());
// 跨域
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || '*' }));
// 日志
app.use(morgan('combined'));
// JSON 解析
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// 路由
app.use('/api/v1/auth', authRoutes);
app.use('/api/v1/users', userRoutes);
// 健康检查
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// 统一错误处理(必须放最后)
app.use(errorHandler);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
});
export default app;
三、JWT 认证中间件
JWT(JSON Web Token)是无状态认证的标准方案,适合前后端分离架构。
3.1 生成与验证 Token
// src/utils/jwt.ts
import jwt from 'jsonwebtoken';
const SECRET = process.env.JWT_SECRET || 'your-secret-key';
const EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
export interface JwtPayload {
userId: string;
email: string;
role: string;
}
// 生成 Token
export const generateToken = (payload: JwtPayload): string => {
return jwt.sign(payload, SECRET, { expiresIn: EXPIRES_IN });
};
// 验证 Token
export const verifyToken = (token: string): JwtPayload => {
return jwt.verify(token, SECRET) as JwtPayload;
};
3.2 认证中间件
// src/middlewares/auth.ts
import { Request, Response, NextFunction } from 'express';
import { verifyToken } from '../utils/jwt';
// 扩展 Request 类型
declare global {
namespace Express {
interface Request {
user?: { userId: string; email: string; role: string };
}
}
}
export const authenticate = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ success: false, message: '未提供认证令牌' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ success: false, message: '令牌无效或已过期' });
}
};
// 角色权限中间件
export const authorize = (...roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user || !roles.includes(req.user.role)) {
return res.status(403).json({ success: false, message: '权限不足' });
}
next();
};
};
四、统一错误处理
良好的错误处理机制能让 API 返回一致的错误格式,便于前端处理。
// src/middlewares/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
export class AppError extends Error {
statusCode: number;
isOperational: boolean;
constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
export const errorHandler = (
err: AppError | Error,
req: Request,
res: Response,
next: NextFunction
) => {
// 开发环境打印堆栈
if (process.env.NODE_ENV === 'development') {
console.error(err.stack);
}
if (err instanceof AppError) {
return res.status(err.statusCode).json({
success: false,
message: err.message,
});
}
// 处理 Mongoose 验证错误
if (err.name === 'ValidationError') {
return res.status(400).json({ success: false, message: '数据验证失败' });
}
// 处理重复键错误
if ((err as any).code === 11000) {
return res.status(409).json({ success: false, message: '数据已存在' });
}
// 未知错误
console.error('未处理的错误:', err);
res.status(500).json({ success: false, message: '服务器内部错误' });
};
五、请求参数校验
使用 express-validator 对请求参数进行严格校验,防止非法数据进入业务层。
// src/middlewares/validate.ts
import { validationResult, ValidationChain } from 'express-validator';
import { Request, Response, NextFunction } from 'express';
export const validate = (validations: ValidationChain[]) => {
return async (req: Request, res: Response, next: NextFunction) => {
// 并行执行所有校验
await Promise.all(validations.map(v => v.run(req)));
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: '参数校验失败',
errors: errors.array().map(e => ({ field: e.type, message: e.msg })),
});
}
next();
};
};
六、用户认证路由实现
// src/routes/auth.ts
import { Router } from 'express';
import { body } from 'express-validator';
import { validate } from '../middlewares/validate';
import { register, login, refreshToken } from '../controllers/authController';
const router = Router();
// 注册校验规则
const registerRules = [
body('email').isEmail().normalizeEmail().withMessage('邮箱格式不正确'),
body('password').isLength({ min: 8 }).withMessage('密码至少8位'),
body('username').trim().isLength({ min: 2, max: 20 }).withMessage('用户名2-20个字符'),
];
// 登录校验规则
const loginRules = [
body('email').isEmail().normalizeEmail(),
body('password').notEmpty().withMessage('密码不能为空'),
];
router.post('/register', validate(registerRules), register);
router.post('/login', validate(loginRules), login);
router.post('/refresh', refreshToken);
export default router;
控制器实现
// src/controllers/authController.ts
import { Request, Response, NextFunction } from 'express';
import bcrypt from 'bcryptjs';
import { generateToken } from '../utils/jwt';
import { AppError } from '../middlewares/errorHandler';
// 模拟用户数据库(实际项目替换为真实 DB 操作)
const users: any[] = [];
export const register = async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password, username } = req.body;
// 检查邮箱是否已注册
const exists = users.find(u => u.email === email);
if (exists) throw new AppError('该邮箱已注册', 409);
// 密码加密
const hashedPassword = await bcrypt.hash(password, 12);
const user = {
id: Date.now().toString(),
email,
username,
password: hashedPassword,
role: 'user',
createdAt: new Date(),
};
users.push(user);
const token = generateToken({ userId: user.id, email: user.email, role: user.role });
res.status(201).json({
success: true,
message: '注册成功',
data: { token, user: { id: user.id, email, username } },
});
} catch (error) {
next(error);
}
};
export const login = async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;
const user = users.find(u => u.email === email);
if (!user) throw new AppError('邮箱或密码错误', 401);
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) throw new AppError('邮箱或密码错误', 401);
const token = generateToken({ userId: user.id, email: user.email, role: user.role });
res.json({
success: true,
message: '登录成功',
data: { token, user: { id: user.id, email, username: user.username } },
});
} catch (error) {
next(error);
}
};
export const refreshToken = async (req: Request, res: Response, next: NextFunction) => {
// 实际项目中应使用 Refresh Token 机制
res.json({ success: true, message: 'Token 刷新功能待实现' });
};
七、限流与防暴力破解
对登录、注册等敏感接口添加请求频率限制,防止暴力破解攻击。
npm install express-rate-limit
// src/middlewares/rateLimiter.ts
import rateLimit from 'express-rate-limit';
// 全局限流:每15分钟最多100次请求
export const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: { success: false, message: '请求过于频繁,请稍后再试' },
standardHeaders: true,
legacyHeaders: false,
});
// 登录接口严格限流:每15分钟最多5次
export const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: { success: false, message: '登录尝试次数过多,请15分钟后再试' },
skipSuccessfulRequests: true, // 成功请求不计入限制
});
八、性能优化技巧
8.1 响应压缩
npm install compression
npm install -D @types/compression
import compression from 'compression';
// 在 app.ts 中添加
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) return false;
return compression.filter(req, res);
},
level: 6, // 压缩级别 1-9,6 是速度与压缩率的平衡点
}));
8.2 使用 cluster 模块充分利用多核 CPU
// src/cluster.ts
import cluster from 'cluster';
import os from 'os';
const numCPUs = os.cpus().length;
if (cluster.isPrimary) {
console.log(`主进程 ${process.pid} 运行中`);
console.log(`启动 ${numCPUs} 个工作进程...`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 退出,正在重启...`);
cluster.fork(); // 自动重启崩溃的进程
});
} else {
// 工作进程运行 Express 应用
import('./app');
console.log(`工作进程 ${process.pid} 已启动`);
}
8.3 异步操作最佳实践
// 使用 Promise.all 并行执行独立的异步操作
const getUserData = async (userId: string) => {
// ❌ 串行执行(慢)
// const profile = await getProfile(userId);
// const orders = await getOrders(userId);
// const notifications = await getNotifications(userId);
// ✅ 并行执行(快)
const [profile, orders, notifications] = await Promise.all([
getProfile(userId),
getOrders(userId),
getNotifications(userId),
]);
return { profile, orders, notifications };
};
九、环境变量配置
# .env
NODE_ENV=development
PORT=3000
JWT_SECRET=your-super-secret-key-change-in-production
JWT_EXPIRES_IN=7d
ALLOWED_ORIGINS=http://localhost:5173,https://yourdomain.com
DB_URI=mongodb://localhost:27017/myapp
十、package.json 脚本配置
{
"scripts": {
"dev": "nodemon --exec ts-node src/app.ts",
"build": "tsc",
"start": "node dist/app.js",
"start:cluster": "node dist/cluster.js",
"lint": "eslint src/**/*.ts"
}
}
总结
本文完整演示了一个生产级 Node.js RESTful API 服务的核心构建要素:
- 项目结构:分层架构(路由→控制器→服务→模型),职责清晰
- JWT 认证:无状态鉴权,支持角色权限控制
- 统一错误处理:自定义 AppError 类,返回一致的错误格式
- 请求校验:express-validator 防止非法数据
- 限流防护:express-rate-limit 防暴力破解
- 性能优化:响应压缩、cluster 多进程、并行异步
💡 生产部署建议:使用 PM2 替代 cluster 模块管理进程(
pm2 start dist/app.js -i max),配合 Nginx 反向代理和 HTTPS,可获得更稳定的生产环境表现。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END








