Node.js 实战:从零构建高性能 RESTful API 服务(JWT认证+中间件+错误处理)

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
喜欢就支持一下吧
点赞15 分享