express-production

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Express.js - Production Web Framework

Express.js - 生产级Web框架

Overview

概述

Express is a minimal and flexible Node.js web application framework providing a robust set of features for web and mobile applications. This skill covers production-ready Express development including middleware architecture, structured error handling, security hardening, comprehensive testing, and deployment strategies.
Key Features:
  • Flexible middleware architecture with composition patterns
  • Centralized error handling with async support
  • Security hardening (Helmet, CORS, rate limiting, input validation)
  • Comprehensive testing with Supertest
  • Production deployment with PM2 clustering
  • Environment-based configuration
  • Structured logging and monitoring
  • Graceful shutdown patterns
  • Zero-downtime deployments
Installation:
bash
undefined
Express是一个轻量灵活的Node.js Web应用框架,为Web和移动应用提供了一套强大的功能。本指南涵盖面向生产环境的Express开发内容,包括中间件架构、结构化错误处理、安全加固、全面测试及部署策略。
核心特性:
  • 支持组合模式的灵活中间件架构
  • 支持异步的集中式错误处理
  • 安全加固(Helmet、CORS、速率限制、输入验证)
  • 基于Supertest的全面测试方案
  • 基于PM2集群的生产环境部署
  • 基于环境的配置管理
  • 结构化日志与监控
  • 优雅停机模式
  • 零停机部署
安装:
bash
undefined

Basic Express

基础Express

npm install express
npm install express

Production stack

生产环境依赖栈

npm install express helmet cors express-rate-limit express-validator npm install morgan winston compression npm install dotenv
npm install express helmet cors express-rate-limit express-validator npm install morgan winston compression npm install dotenv

Development tools

开发工具

npm install -D nodemon supertest jest
npm install -D nodemon supertest jest

Optional: Database and auth

可选:数据库与认证

npm install mongoose jsonwebtoken bcrypt
undefined
npm install mongoose jsonwebtoken bcrypt
undefined

When to Use This Skill

何时使用本指南

Use this comprehensive Express skill when:
  • Building production REST APIs
  • Creating microservices architectures
  • Implementing secure web applications
  • Need flexible middleware composition
  • Require comprehensive error handling
  • Building systems requiring extensive testing
  • Deploying high-availability services
  • Need granular control over request/response lifecycle
Express vs Other Frameworks:
  • Express: Maximum flexibility, unopinionated, extensive ecosystem
  • Fastify: Performance-focused, schema-based validation
  • Koa: Modern async/await, minimalist
  • NestJS: TypeScript-first, opinionated, enterprise patterns
在以下场景中使用本全面的Express开发指南:
  • 构建生产级REST API
  • 创建微服务架构
  • 实现安全的Web应用
  • 需要灵活的中间件组合
  • 要求完善的错误处理机制
  • 构建需要全面测试的系统
  • 部署高可用服务
  • 需要对请求/响应生命周期进行精细控制
Express与其他框架对比:
  • Express: 灵活性最高,无约束设计,生态系统丰富
  • Fastify: 性能优先,基于Schema的验证
  • Koa: 现代异步/await设计,极简风格
  • NestJS: TypeScript优先,约束式设计,企业级模式

Quick Start

快速开始

Minimal Express Server

极简Express服务器

javascript
// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes
app.get('/', (req, res) => {
  res.json({ message: 'Hello World' });
});

app.get('/health', (req, res) => {
  res.json({ status: 'ok', uptime: process.uptime() });
});

// Error handler
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Internal server error' });
});

// Start server
const server = app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('SIGTERM received, closing server...');
  server.close(() => {
    console.log('Server closed');
    process.exit(0);
  });
});
Run Development Server:
bash
undefined
javascript
// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

// 中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 路由
app.get('/', (req, res) => {
  res.json({ message: 'Hello World' });
});

app.get('/health', (req, res) => {
  res.json({ status: 'ok', uptime: process.uptime() });
});

// 错误处理器
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Internal server error' });
});

// 启动服务器
const server = app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

// 优雅停机
process.on('SIGTERM', () => {
  console.log('SIGTERM received, closing server...');
  server.close(() => {
    console.log('Server closed');
    process.exit(0);
  });
});
启动开发服务器:
bash
undefined

Install nodemon

安装nodemon

npm install -D nodemon
npm install -D nodemon

Run with nodemon

使用nodemon启动

npx nodemon server.js
npx nodemon server.js

Or add to package.json

或添加到package.json

npm run dev
undefined
npm run dev
undefined

Production-Ready Server Structure

生产级服务器结构

project/
├── src/
│   ├── app.js              # Express app factory
│   ├── server.js           # Server entry point
│   ├── config/
│   │   ├── index.js        # Configuration management
│   │   └── logger.js       # Winston logger setup
│   ├── middleware/
│   │   ├── errorHandler.js # Centralized error handling
│   │   ├── validation.js   # Input validation
│   │   ├── auth.js         # Authentication middleware
│   │   └── rateLimiter.js  # Rate limiting
│   ├── routes/
│   │   ├── index.js        # Route aggregator
│   │   ├── users.js        # User routes
│   │   └── api/            # API versioning
│   ├── controllers/
│   │   ├── userController.js
│   │   └── authController.js
│   ├── models/             # Data models
│   ├── services/           # Business logic
│   ├── utils/
│   │   ├── AppError.js     # Custom error class
│   │   └── catchAsync.js   # Async wrapper
│   └── tests/
│       ├── unit/
│       └── integration/
├── ecosystem.config.js     # PM2 configuration
├── .env.example            # Environment template
├── nodemon.json            # Nodemon config
└── package.json
project/
├── src/
│   ├── app.js              # Express应用工厂
│   ├── server.js           # 服务器入口文件
│   ├── config/
│   │   ├── index.js        # 配置管理
│   │   └── logger.js       # Winston日志配置
│   ├── middleware/
│   │   ├── errorHandler.js # 集中式错误处理
│   │   ├── validation.js   # 输入验证
│   │   ├── auth.js         # 认证中间件
│   │   └── rateLimiter.js  # 速率限制
│   ├── routes/
│   │   ├── index.js        # 路由聚合器
│   │   ├── users.js        # 用户路由
│   │   └── api/            # API版本控制
│   ├── controllers/
│   │   ├── userController.js
│   │   └── authController.js
│   ├── models/             # 数据模型
│   ├── services/           # 业务逻辑
│   ├── utils/
│   │   ├── AppError.js     # 自定义错误类
│   │   └── catchAsync.js   # 异步包装器
│   └── tests/
│       ├── unit/
│       └── integration/
├── ecosystem.config.js     # PM2配置
├── .env.example            # 环境变量模板
├── nodemon.json            # Nodemon配置
└── package.json

Middleware Architecture

中间件架构

Understanding Middleware

理解中间件

Middleware functions are functions that have access to the request object (
req
), response object (
res
), and the next middleware function (
next
).
Middleware Types:
  1. Application-level:
    app.use()
    or
    app.METHOD()
  2. Router-level:
    router.use()
    or
    router.METHOD()
  3. Error-handling: Four parameters
    (err, req, res, next)
  4. Built-in:
    express.json()
    ,
    express.static()
  5. Third-party:
    helmet
    ,
    cors
    ,
    morgan
中间件函数可以访问请求对象(
req
)、响应对象(
res
)以及下一个中间件函数(
next
)。
中间件类型:
  1. 应用级:
    app.use()
    app.METHOD()
  2. 路由级:
    router.use()
    router.METHOD()
  3. 错误处理: 四个参数
    (err, req, res, next)
  4. 内置:
    express.json()
    express.static()
  5. 第三方:
    helmet
    cors
    morgan

Proper Middleware Order

正确的中间件顺序

Correct Order:
javascript
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const compression = require('compression');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');

const app = express();

// 1. Security headers (FIRST)
app.use(helmet());

// 2. CORS configuration
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

// 3. Rate limiting (before parsing)
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP'
});
app.use('/api/', limiter);

// 4. Request parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

// 5. Compression
app.use(compression());

// 6. Logging
if (process.env.NODE_ENV !== 'production') {
  app.use(morgan('dev'));
} else {
  app.use(morgan('combined'));
}

// 7. Static files (if needed)
app.use(express.static('public'));

// 8. Custom middleware
app.use(require('./middleware/requestId'));
app.use(require('./middleware/timing'));

// 9. Routes
app.use('/api/v1/users', require('./routes/users'));
app.use('/api/v1/posts', require('./routes/posts'));

// 10. 404 handler (after all routes)
app.use((req, res) => {
  res.status(404).json({ error: 'Route not found' });
});

// 11. Error handling (LAST)
app.use(require('./middleware/errorHandler'));
Wrong Order:
javascript
// DON'T: Routes before security
app.use('/api/users', userRoutes); // Routes first
app.use(helmet()); // Security too late!

// DON'T: Error handler before routes
app.use(errorHandler); // Error handler first
app.use('/api/users', userRoutes); // Routes won't be caught

// DON'T: Parsing after routes
app.use('/api/users', userRoutes);
app.use(express.json()); // Too late to parse!
正确顺序:
javascript
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const compression = require('compression');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');

const app = express();

// 1. 安全头(优先级最高)
app.use(helmet());

// 2. CORS配置
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

// 3. 速率限制(在解析之前)
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100, // 限制每个IP在窗口时间内最多100次请求
  message: 'Too many requests from this IP'
});
app.use('/api/', limiter);

// 4. 请求解析
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

// 5. 压缩
app.use(compression());

// 6. 日志
if (process.env.NODE_ENV !== 'production') {
  app.use(morgan('dev'));
} else {
  app.use(morgan('combined'));
}

// 7. 静态文件(如果需要)
app.use(express.static('public'));

// 8. 自定义中间件
app.use(require('./middleware/requestId'));
app.use(require('./middleware/timing'));

// 9. 路由
app.use('/api/v1/users', require('./routes/users'));
app.use('/api/v1/posts', require('./routes/posts'));

// 10. 404处理器(所有路由之后)
app.use((req, res) => {
  res.status(404).json({ error: 'Route not found' });
});

// 11. 错误处理(最后)
app.use(require('./middleware/errorHandler'));
错误顺序:
javascript
// 错误:路由在安全中间件之前
app.use('/api/users', userRoutes); // 路由优先
app.use(helmet()); // 安全中间件太晚!

// 错误:错误处理器在路由之前
app.use(errorHandler); // 错误处理器优先
app.use('/api/users', userRoutes); // 路由不会被捕获

// 错误:解析中间件在路由之后
app.use('/api/users', userRoutes);
app.use(express.json()); // 解析太晚!

Custom Middleware Patterns

自定义中间件模式

Request ID Middleware:
javascript
// middleware/requestId.js
const { v4: uuidv4 } = require('uuid');

module.exports = function requestId(req, res, next) {
  req.id = req.headers['x-request-id'] || uuidv4();
  res.setHeader('X-Request-ID', req.id);
  next();
};
Request Timing Middleware:
javascript
// middleware/timing.js
module.exports = function timing(req, res, next) {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.path} - ${duration}ms`);
  });

  next();
};
Authentication Middleware:
javascript
// middleware/auth.js
const jwt = require('jsonwebtoken');
const AppError = require('../utils/AppError');

exports.authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return next(new AppError('No token provided', 401));
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    next(new AppError('Invalid token', 401));
  }
};

exports.authorize = (...roles) => {
  return (req, res, next) => {
    if (!req.user) {
      return next(new AppError('Not authenticated', 401));
    }

    if (!roles.includes(req.user.role)) {
      return next(new AppError('Insufficient permissions', 403));
    }

    next();
  };
};
Usage:
javascript
const { authenticate, authorize } = require('./middleware/auth');

// Public route
app.get('/api/posts', getPosts);

// Authenticated route
app.get('/api/profile', authenticate, getProfile);

// Role-based authorization
app.delete('/api/users/:id',
  authenticate,
  authorize('admin', 'moderator'),
  deleteUser
);
请求ID中间件:
javascript
// middleware/requestId.js
const { v4: uuidv4 } = require('uuid');

module.exports = function requestId(req, res, next) {
  req.id = req.headers['x-request-id'] || uuidv4();
  res.setHeader('X-Request-ID', req.id);
  next();
};
请求计时中间件:
javascript
// middleware/timing.js
module.exports = function timing(req, res, next) {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.path} - ${duration}ms`);
  });

  next();
};
认证中间件:
javascript
// middleware/auth.js
const jwt = require('jsonwebtoken');
const AppError = require('../utils/AppError');

exports.authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return next(new AppError('No token provided', 401));
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    next(new AppError('Invalid token', 401));
  }
};

exports.authorize = (...roles) => {
  return (req, res, next) => {
    if (!req.user) {
      return next(new AppError('Not authenticated', 401));
    }

    if (!roles.includes(req.user.role)) {
      return next(new AppError('Insufficient permissions', 403));
    }

    next();
  };
};
使用方式:
javascript
const { authenticate, authorize } = require('./middleware/auth');

// 公开路由
app.get('/api/posts', getPosts);

// 需认证的路由
app.get('/api/profile', authenticate, getProfile);

// 基于角色的授权
app.delete('/api/users/:id',
  authenticate,
  authorize('admin', 'moderator'),
  deleteUser
);

Async Middleware

异步中间件

Correct Async Handling:
javascript
// utils/catchAsync.js
module.exports = (fn) => {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
};

// Usage
const catchAsync = require('../utils/catchAsync');

app.get('/users', catchAsync(async (req, res) => {
  const users = await User.find();
  res.json({ users });
}));
Wrong: No Error Handling:
javascript
// DON'T: Async without catch
app.get('/users', async (req, res) => {
  const users = await User.find(); // Unhandled rejection!
  res.json({ users });
});
正确的异步处理:
javascript
// utils/catchAsync.js
module.exports = (fn) => {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
};

// 使用方式
const catchAsync = require('../utils/catchAsync');

app.get('/users', catchAsync(async (req, res) => {
  const users = await User.find();
  res.json({ users });
}));
错误示例:无错误处理:
javascript
// 错误:异步函数无catch
app.get('/users', async (req, res) => {
  const users = await User.find(); // 未处理的拒绝!
  res.json({ users });
});

Middleware Composition

中间件组合

Compose Multiple Middleware:
javascript
// middleware/compose.js
const compose = (...middleware) => {
  return (req, res, next) => {
    let index = 0;

    const dispatch = (i) => {
      if (i >= middleware.length) return next();

      const fn = middleware[i];
      try {
        fn(req, res, () => dispatch(i + 1));
      } catch (err) {
        next(err);
      }
    };

    dispatch(0);
  };
};

// Usage
const adminOnly = compose(
  authenticate,
  authorize('admin'),
  validateRequest
);

app.delete('/api/users/:id', adminOnly, deleteUser);
Conditional Middleware:
javascript
// Apply middleware conditionally
const conditionalMiddleware = (condition, middleware) => {
  return (req, res, next) => {
    if (condition(req)) {
      return middleware(req, res, next);
    }
    next();
  };
};

// Only log in development
app.use(conditionalMiddleware(
  (req) => process.env.NODE_ENV === 'development',
  morgan('dev')
));
组合多个中间件:
javascript
// middleware/compose.js
const compose = (...middleware) => {
  return (req, res, next) => {
    let index = 0;

    const dispatch = (i) => {
      if (i >= middleware.length) return next();

      const fn = middleware[i];
      try {
        fn(req, res, () => dispatch(i + 1));
      } catch (err) {
        next(err);
      }
    };

    dispatch(0);
  };
};

// 使用方式
const adminOnly = compose(
  authenticate,
  authorize('admin'),
  validateRequest
);

app.delete('/api/users/:id', adminOnly, deleteUser);
条件中间件:
javascript
// 条件式应用中间件
const conditionalMiddleware = (condition, middleware) => {
  return (req, res, next) => {
    if (condition(req)) {
      return middleware(req, res, next);
    }
    next();
  };
};

// 仅在开发环境中记录日志
app.use(conditionalMiddleware(
  (req) => process.env.NODE_ENV === 'development',
  morgan('dev')
));

Structured Error Handling

结构化错误处理

Custom Error Classes

自定义错误类

javascript
// utils/AppError.js
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);

    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true;

    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = AppError;
Error Hierarchy:
javascript
// utils/errors.js
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
  }
}

class ValidationError extends AppError {
  constructor(message, errors = []) {
    super(message, 400);
    this.errors = errors;
  }
}

class AuthenticationError extends AppError {
  constructor(message = 'Authentication required') {
    super(message, 401);
  }
}

class AuthorizationError extends AppError {
  constructor(message = 'Insufficient permissions') {
    super(message, 403);
  }
}

class NotFoundError extends AppError {
  constructor(resource = 'Resource') {
    super(`${resource} not found`, 404);
  }
}

class ConflictError extends AppError {
  constructor(message = 'Resource conflict') {
    super(message, 409);
  }
}

module.exports = {
  AppError,
  ValidationError,
  AuthenticationError,
  AuthorizationError,
  NotFoundError,
  ConflictError
};
javascript
// utils/AppError.js
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);

    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true;

    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = AppError;
错误层级:
javascript
// utils/errors.js
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
  }
}

class ValidationError extends AppError {
  constructor(message, errors = []) {
    super(message, 400);
    this.errors = errors;
  }
}

class AuthenticationError extends AppError {
  constructor(message = 'Authentication required') {
    super(message, 401);
  }
}

class AuthorizationError extends AppError {
  constructor(message = 'Insufficient permissions') {
    super(message, 403);
  }
}

class NotFoundError extends AppError {
  constructor(resource = 'Resource') {
    super(`${resource} not found`, 404);
  }
}

class ConflictError extends AppError {
  constructor(message = 'Resource conflict') {
    super(message, 409);
  }
}

module.exports = {
  AppError,
  ValidationError,
  AuthenticationError,
  AuthorizationError,
  NotFoundError,
  ConflictError
};

Centralized Error Handler

集中式错误处理器

javascript
// middleware/errorHandler.js
const logger = require('../config/logger');

function errorHandler(err, req, res, next) {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  // Log error
  logger.error({
    message: err.message,
    statusCode: err.statusCode,
    stack: err.stack,
    path: req.path,
    method: req.method,
    ip: req.ip,
    userId: req.user?.id
  });

  // Development: send full error
  if (process.env.NODE_ENV === 'development') {
    return res.status(err.statusCode).json({
      status: err.status,
      error: err,
      message: err.message,
      stack: err.stack
    });
  }

  // Production: sanitize errors
  if (err.isOperational) {
    // Operational, trusted error: send to client
    return res.status(err.statusCode).json({
      status: err.status,
      message: err.message,
      ...(err.errors && { errors: err.errors })
    });
  }

  // Programming or unknown error: don't leak details
  console.error('ERROR 💥', err);
  return res.status(500).json({
    status: 'error',
    message: 'Something went wrong'
  });
}

module.exports = errorHandler;
javascript
// middleware/errorHandler.js
const logger = require('../config/logger');

function errorHandler(err, req, res, next) {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  // 记录错误
  logger.error({
    message: err.message,
    statusCode: err.statusCode,
    stack: err.stack,
    path: req.path,
    method: req.method,
    ip: req.ip,
    userId: req.user?.id
  });

  // 开发环境:返回完整错误
  if (process.env.NODE_ENV === 'development') {
    return res.status(err.statusCode).json({
      status: err.status,
      error: err,
      message: err.message,
      stack: err.stack
    });
  }

  // 生产环境:清理错误信息
  if (err.isOperational) {
    // 可预期的业务错误:返回给客户端
    return res.status(err.statusCode).json({
      status: err.status,
      message: err.message,
      ...(err.errors && { errors: err.errors })
    });
  }

  // 编程错误或未知错误:不泄露细节
  console.error('ERROR 💥', err);
  return res.status(500).json({
    status: 'error',
    message: 'Something went wrong'
  });
}

module.exports = errorHandler;

Handling Specific Error Types

处理特定错误类型

javascript
// middleware/errorHandler.js (extended)
function handleCastError(err) {
  const message = `Invalid ${err.path}: ${err.value}`;
  return new AppError(message, 400);
}

function handleDuplicateFields(err) {
  const field = Object.keys(err.keyValue)[0];
  const message = `Duplicate field value: ${field}. Please use another value`;
  return new AppError(message, 400);
}

function handleValidationError(err) {
  const errors = Object.values(err.errors).map(el => el.message);
  const message = `Invalid input data. ${errors.join('. ')}`;
  return new AppError(message, 400);
}

function handleJWTError() {
  return new AppError('Invalid token. Please log in again', 401);
}

function handleJWTExpiredError() {
  return new AppError('Your token has expired. Please log in again', 401);
}

module.exports = (err, req, res, next) => {
  let error = { ...err };
  error.message = err.message;

  // Mongoose bad ObjectId
  if (err.name === 'CastError') error = handleCastError(error);

  // Mongoose duplicate key
  if (err.code === 11000) error = handleDuplicateFields(error);

  // Mongoose validation error
  if (err.name === 'ValidationError') error = handleValidationError(error);

  // JWT errors
  if (err.name === 'JsonWebTokenError') error = handleJWTError();
  if (err.name === 'TokenExpiredError') error = handleJWTExpiredError();

  // Send response
  sendErrorResponse(error, req, res);
};
javascript
// middleware/errorHandler.js (扩展版)
function handleCastError(err) {
  const message = `Invalid ${err.path}: ${err.value}`;
  return new AppError(message, 400);
}

function handleDuplicateFields(err) {
  const field = Object.keys(err.keyValue)[0];
  const message = `Duplicate field value: ${field}. Please use another value`;
  return new AppError(message, 400);
}

function handleValidationError(err) {
  const errors = Object.values(err.errors).map(el => el.message);
  const message = `Invalid input data. ${errors.join('. ')}`;
  return new AppError(message, 400);
}

function handleJWTError() {
  return new AppError('Invalid token. Please log in again', 401);
}

function handleJWTExpiredError() {
  return new AppError('Your token has expired. Please log in again', 401);
}

module.exports = (err, req, res, next) => {
  let error = { ...err };
  error.message = err.message;

  // Mongoose无效ObjectId错误
  if (err.name === 'CastError') error = handleCastError(error);

  // Mongoose重复键错误
  if (err.code === 11000) error = handleDuplicateFields(error);

  // Mongoose验证错误
  if (err.name === 'ValidationError') error = handleValidationError(error);

  // JWT错误
  if (err.name === 'JsonWebTokenError') error = handleJWTError();
  if (err.name === 'TokenExpiredError') error = handleJWTExpiredError();

  // 返回响应
  sendErrorResponse(error, req, res);
};

Async Error Handling

异步错误处理

javascript
// utils/catchAsync.js
const catchAsync = (fn) => {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
};

module.exports = catchAsync;

// Usage in controllers
const catchAsync = require('../utils/catchAsync');
const User = require('../models/User');
const { NotFoundError } = require('../utils/errors');

exports.getUser = catchAsync(async (req, res, next) => {
  const user = await User.findById(req.params.id);

  if (!user) {
    return next(new NotFoundError('User'));
  }

  res.json({ user });
});

exports.createUser = catchAsync(async (req, res, next) => {
  const user = await User.create(req.body);
  res.status(201).json({ user });
});
javascript
// utils/catchAsync.js
const catchAsync = (fn) => {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
};

module.exports = catchAsync;

// 在控制器中使用
const catchAsync = require('../utils/catchAsync');
const User = require('../models/User');
const { NotFoundError } = require('../utils/errors');

exports.getUser = catchAsync(async (req, res, next) => {
  const user = await User.findById(req.params.id);

  if (!user) {
    return next(new NotFoundError('User'));
  }

  res.json({ user });
});

exports.createUser = catchAsync(async (req, res, next) => {
  const user = await User.create(req.body);
  res.status(201).json({ user });
});

Unhandled Rejections

未处理的Promise拒绝

javascript
// server.js
const app = require('./app');

const PORT = process.env.PORT || 3000;

const server = app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

// Handle unhandled promise rejections
process.on('unhandledRejection', (err) => {
  console.error('UNHANDLED REJECTION! 💥 Shutting down...');
  console.error(err.name, err.message);

  server.close(() => {
    process.exit(1);
  });
});

// Handle uncaught exceptions
process.on('uncaughtException', (err) => {
  console.error('UNCAUGHT EXCEPTION! 💥 Shutting down...');
  console.error(err.name, err.message);
  process.exit(1);
});

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('👋 SIGTERM RECEIVED. Shutting down gracefully');
  server.close(() => {
    console.log('💥 Process terminated!');
  });
});
javascript
// server.js
const app = require('./app');

const PORT = process.env.PORT || 3000;

const server = app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

// 处理未处理的Promise拒绝
process.on('unhandledRejection', (err) => {
  console.error('UNHANDLED REJECTION! 💥 Shutting down...');
  console.error(err.name, err.message);

  server.close(() => {
    process.exit(1);
  });
});

// 处理未捕获的异常
process.on('uncaughtException', (err) => {
  console.error('UNCAUGHT EXCEPTION! 💥 Shutting down...');
  console.error(err.name, err.message);
  process.exit(1);
});

// 优雅停机
process.on('SIGTERM', () => {
  console.log('👋 SIGTERM RECEIVED. Shutting down gracefully');
  server.close(() => {
    console.log('💥 Process terminated!');
  });
});

Security Hardening

安全加固

Helmet.js Configuration

Helmet.js配置

javascript
// config/security.js
const helmet = require('helmet');

const securityConfig = helmet({
  // Content Security Policy
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
    },
  },

  // Strict Transport Security
  hsts: {
    maxAge: 31536000, // 1 year
    includeSubDomains: true,
    preload: true
  },

  // X-Frame-Options
  frameguard: {
    action: 'deny'
  },

  // X-Content-Type-Options
  noSniff: true,

  // X-XSS-Protection
  xssFilter: true,

  // Referrer-Policy
  referrerPolicy: {
    policy: 'strict-origin-when-cross-origin'
  }
});

module.exports = securityConfig;
Usage:
javascript
// app.js
const securityConfig = require('./config/security');

app.use(securityConfig);
javascript
// config/security.js
const helmet = require('helmet');

const securityConfig = helmet({
  // 内容安全策略
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
    },
  },

  // 严格传输安全
  hsts: {
    maxAge: 31536000, // 1年
    includeSubDomains: true,
    preload: true
  },

  // X-Frame-Options
  frameguard: {
    action: 'deny'
  },

  // X-Content-Type-Options
  noSniff: true,

  // X-XSS-Protection
  xssFilter: true,

  // Referrer-Policy
  referrerPolicy: {
    policy: 'strict-origin-when-cross-origin'
  }
});

module.exports = securityConfig;
使用方式:
javascript
// app.js
const securityConfig = require('./config/security');

app.use(securityConfig);

CORS Configuration

CORS配置

javascript
// config/cors.js
const cors = require('cors');

const whitelist = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];

const corsOptions = {
  origin: function (origin, callback) {
    // Allow requests with no origin (mobile apps, Postman)
    if (!origin) return callback(null, true);

    if (whitelist.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
  exposedHeaders: ['X-Total-Count', 'X-Page-Number'],
  maxAge: 86400 // 24 hours
};

module.exports = cors(corsOptions);
javascript
// config/cors.js
const cors = require('cors');

const whitelist = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];

const corsOptions = {
  origin: function (origin, callback) {
    // 允许无origin的请求(移动应用、Postman)
    if (!origin) return callback(null, true);

    if (whitelist.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
  exposedHeaders: ['X-Total-Count', 'X-Page-Number'],
  maxAge: 86400 // 24小时
};

module.exports = cors(corsOptions);

Rate Limiting

速率限制

javascript
// middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');

// Redis client for distributed rate limiting
const redisClient = redis.createClient({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT
});

// General rate limiter
exports.generalLimiter = rateLimit({
  store: new RedisStore({
    client: redisClient,
    prefix: 'rl:general:'
  }),
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP, please try again later',
  standardHeaders: true, // Return rate limit info in RateLimit-* headers
  legacyHeaders: false // Disable X-RateLimit-* headers
});

// Strict rate limiter for auth endpoints
exports.authLimiter = rateLimit({
  store: new RedisStore({
    client: redisClient,
    prefix: 'rl:auth:'
  }),
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // limit each IP to 5 login attempts per windowMs
  message: 'Too many login attempts, please try again later',
  skipSuccessfulRequests: true // Don't count successful requests
});

// API key limiter (higher limits for authenticated users)
exports.apiKeyLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 1000,
  keyGenerator: (req) => req.headers['x-api-key'] || req.ip,
  skip: (req) => !req.headers['x-api-key']
});
Usage:
javascript
const { generalLimiter, authLimiter } = require('./middleware/rateLimiter');

// Apply to all routes
app.use('/api/', generalLimiter);

// Strict limiting for auth
app.use('/api/auth/login', authLimiter);
app.use('/api/auth/register', authLimiter);
javascript
// middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');

// Redis客户端用于分布式速率限制
const redisClient = redis.createClient({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT
});

// 通用速率限制器
exports.generalLimiter = rateLimit({
  store: new RedisStore({
    client: redisClient,
    prefix: 'rl:general:'
  }),
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100, // 限制每个IP在窗口时间内最多100次请求
  message: 'Too many requests from this IP, please try again later',
  standardHeaders: true, // 在RateLimit-*头中返回速率限制信息
  legacyHeaders: false // 禁用X-RateLimit-*头
});

// 认证端点的严格速率限制器
exports.authLimiter = rateLimit({
  store: new RedisStore({
    client: redisClient,
    prefix: 'rl:auth:'
  }),
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 5, // 限制每个IP在窗口时间内最多5次登录尝试
  message: 'Too many login attempts, please try again later',
  skipSuccessfulRequests: true // 不统计成功的请求
});

// API密钥速率限制器(认证用户拥有更高限制)
exports.apiKeyLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1小时
  max: 1000,
  keyGenerator: (req) => req.headers['x-api-key'] || req.ip,
  skip: (req) => !req.headers['x-api-key']
});
使用方式:
javascript
const { generalLimiter, authLimiter } = require('./middleware/rateLimiter');

// 应用到所有路由
app.use('/api/', generalLimiter);

// 对认证端点应用严格限制
app.use('/api/auth/login', authLimiter);
app.use('/api/auth/register', authLimiter);

Input Validation and Sanitization

输入验证与清理

javascript
// middleware/validation.js
const { body, param, query, validationResult } = require('express-validator');
const { ValidationError } = require('../utils/errors');

// Validation middleware
exports.validate = (req, res, next) => {
  const errors = validationResult(req);

  if (!errors.isEmpty()) {
    const extractedErrors = errors.array().map(err => ({
      field: err.param,
      message: err.msg,
      value: err.value
    }));

    return next(new ValidationError('Validation failed', extractedErrors));
  }

  next();
};

// User validation rules
exports.createUserRules = [
  body('email')
    .isEmail()
    .normalizeEmail()
    .withMessage('Must be a valid email'),
  body('password')
    .isLength({ min: 8 })
    .withMessage('Password must be at least 8 characters')
    .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
    .withMessage('Password must contain uppercase, lowercase, and number'),
  body('name')
    .trim()
    .notEmpty()
    .withMessage('Name is required')
    .isLength({ max: 100 })
    .withMessage('Name too long')
    .escape(), // XSS protection
  body('age')
    .optional()
    .isInt({ min: 0, max: 150 })
    .withMessage('Age must be between 0 and 150')
];

exports.updateUserRules = [
  param('id')
    .isMongoId()
    .withMessage('Invalid user ID'),
  body('email')
    .optional()
    .isEmail()
    .normalizeEmail(),
  body('name')
    .optional()
    .trim()
    .notEmpty()
    .escape()
];

// Usage
const { createUserRules, validate } = require('./middleware/validation');

app.post('/api/users', createUserRules, validate, createUser);
javascript
// middleware/validation.js
const { body, param, query, validationResult } = require('express-validator');
const { ValidationError } = require('../utils/errors');

// 验证中间件
exports.validate = (req, res, next) => {
  const errors = validationResult(req);

  if (!errors.isEmpty()) {
    const extractedErrors = errors.array().map(err => ({
      field: err.param,
      message: err.msg,
      value: err.value
    }));

    return next(new ValidationError('Validation failed', extractedErrors));
  }

  next();
};

// 用户验证规则
exports.createUserRules = [
  body('email')
    .isEmail()
    .normalizeEmail()
    .withMessage('Must be a valid email'),
  body('password')
    .isLength({ min: 8 })
    .withMessage('Password must be at least 8 characters')
    .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
    .withMessage('Password must contain uppercase, lowercase, and number'),
  body('name')
    .trim()
    .notEmpty()
    .withMessage('Name is required')
    .isLength({ max: 100 })
    .withMessage('Name too long')
    .escape(), // XSS防护
  body('age')
    .optional()
    .isInt({ min: 0, max: 150 })
    .withMessage('Age must be between 0 and 150')
];

exports.updateUserRules = [
  param('id')
    .isMongoId()
    .withMessage('Invalid user ID'),
  body('email')
    .optional()
    .isEmail()
    .normalizeEmail(),
  body('name')
    .optional()
    .trim()
    .notEmpty()
    .escape()
];

// 使用方式
const { createUserRules, validate } = require('./middleware/validation');

app.post('/api/users', createUserRules, validate, createUser);

SQL Injection Prevention

SQL注入防护

javascript
// DON'T: String concatenation
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`; // Vulnerable!

// DO: Parameterized queries
const query = 'SELECT * FROM users WHERE email = ?';
connection.query(query, [req.body.email], (err, results) => {
  // Safe from SQL injection
});

// DO: ORM/Query Builder
const user = await User.findOne({ email: req.body.email }); // Mongoose
const user = await db('users').where('email', req.body.email).first(); // Knex
javascript
// 错误:字符串拼接
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`; // 易受攻击!

// 正确:参数化查询
const query = 'SELECT * FROM users WHERE email = ?';
connection.query(query, [req.body.email], (err, results) => {
  // 免受SQL注入
});

// 正确:ORM/查询构建器
const user = await User.findOne({ email: req.body.email }); // Mongoose
const user = await db('users').where('email', req.body.email).first(); // Knex

XSS Protection

XSS防护

javascript
// Install: npm install xss-clean
const xss = require('xss-clean');

// Apply XSS sanitization
app.use(xss());

// Additional: HTML escaping in templates
const escapeHtml = (unsafe) => {
  return unsafe
    .replace(/&/g, "&")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
};
javascript
// 安装:npm install xss-clean
const xss = require('xss-clean');

// 应用XSS清理
app.use(xss());

// 额外:模板中的HTML转义
const escapeHtml = (unsafe) => {
  return unsafe
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
};

Environment Variable Security

环境变量安全

javascript
// config/index.js
require('dotenv').config();

const requiredEnvVars = [
  'NODE_ENV',
  'PORT',
  'DATABASE_URL',
  'JWT_SECRET',
  'REDIS_HOST'
];

// Validate required environment variables
requiredEnvVars.forEach((envVar) => {
  if (!process.env[envVar]) {
    throw new Error(`Missing required environment variable: ${envVar}`);
  }
});

// Validate JWT_SECRET strength
if (process.env.JWT_SECRET.length < 32) {
  throw new Error('JWT_SECRET must be at least 32 characters');
}

module.exports = {
  env: process.env.NODE_ENV,
  port: parseInt(process.env.PORT, 10),
  database: {
    url: process.env.DATABASE_URL
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '7d'
  },
  redis: {
    host: process.env.REDIS_HOST,
    port: parseInt(process.env.REDIS_PORT, 10) || 6379
  }
};
javascript
// config/index.js
require('dotenv').config();

const requiredEnvVars = [
  'NODE_ENV',
  'PORT',
  'DATABASE_URL',
  'JWT_SECRET',
  'REDIS_HOST'
];

// 验证必填环境变量
requiredEnvVars.forEach((envVar) => {
  if (!process.env[envVar]) {
    throw new Error(`Missing required environment variable: ${envVar}`);
  }
});

// 验证JWT_SECRET强度
if (process.env.JWT_SECRET.length < 32) {
  throw new Error('JWT_SECRET must be at least 32 characters');
}

module.exports = {
  env: process.env.NODE_ENV,
  port: parseInt(process.env.PORT, 10),
  database: {
    url: process.env.DATABASE_URL
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '7d'
  },
  redis: {
    host: process.env.REDIS_HOST,
    port: parseInt(process.env.REDIS_PORT, 10) || 6379
  }
};

Testing with Supertest

测试(使用Supertest)

Test Setup

测试配置

javascript
// tests/setup.js
const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');

let mongoServer;

// Setup before all tests
beforeAll(async () => {
  mongoServer = await MongoMemoryServer.create();
  const mongoUri = mongoServer.getUri();

  await mongoose.connect(mongoUri);
});

// Cleanup after each test
afterEach(async () => {
  const collections = mongoose.connection.collections;

  for (const key in collections) {
    await collections[key].deleteMany();
  }
});

// Teardown after all tests
afterAll(async () => {
  await mongoose.disconnect();
  await mongoServer.stop();
});
javascript
// tests/setup.js
const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');

let mongoServer;

// 所有测试前的配置
beforeAll(async () => {
  mongoServer = await MongoMemoryServer.create();
  const mongoUri = mongoServer.getUri();

  await mongoose.connect(mongoUri);
});

// 每个测试后的清理
afterEach(async () => {
  const collections = mongoose.connection.collections;

  for (const key in collections) {
    await collections[key].deleteMany();
  }
});

// 所有测试后的清理
afterAll(async () => {
  await mongoose.disconnect();
  await mongoServer.stop();
});

Integration Testing

集成测试

javascript
// tests/integration/users.test.js
const request = require('supertest');
const app = require('../../src/app');
const User = require('../../src/models/User');

describe('User API', () => {
  describe('POST /api/users', () => {
    it('should create a new user', async () => {
      const userData = {
        email: 'test@example.com',
        name: 'Test User',
        password: 'Password123'
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect('Content-Type', /json/)
        .expect(201);

      expect(response.body).toHaveProperty('user');
      expect(response.body.user.email).toBe(userData.email);
      expect(response.body.user).not.toHaveProperty('password');
    });

    it('should return 400 for invalid email', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'invalid-email',
          name: 'Test User',
          password: 'Password123'
        })
        .expect(400);

      expect(response.body).toHaveProperty('errors');
    });

    it('should return 409 for duplicate email', async () => {
      const userData = {
        email: 'duplicate@example.com',
        name: 'Test User',
        password: 'Password123'
      };

      // Create first user
      await User.create(userData);

      // Try to create duplicate
      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(409);

      expect(response.body.message).toMatch(/duplicate/i);
    });
  });

  describe('GET /api/users/:id', () => {
    it('should get user by ID', async () => {
      const user = await User.create({
        email: 'get@example.com',
        name: 'Get User',
        password: 'Password123'
      });

      const response = await request(app)
        .get(`/api/users/${user._id}`)
        .expect(200);

      expect(response.body.user._id).toBe(user._id.toString());
    });

    it('should return 404 for non-existent user', async () => {
      const fakeId = '507f1f77bcf86cd799439011';

      await request(app)
        .get(`/api/users/${fakeId}`)
        .expect(404);
    });
  });

  describe('PUT /api/users/:id', () => {
    it('should update user', async () => {
      const user = await User.create({
        email: 'update@example.com',
        name: 'Update User',
        password: 'Password123'
      });

      const response = await request(app)
        .put(`/api/users/${user._id}`)
        .send({ name: 'Updated Name' })
        .expect(200);

      expect(response.body.user.name).toBe('Updated Name');
    });
  });

  describe('DELETE /api/users/:id', () => {
    it('should delete user', async () => {
      const user = await User.create({
        email: 'delete@example.com',
        name: 'Delete User',
        password: 'Password123'
      });

      await request(app)
        .delete(`/api/users/${user._id}`)
        .expect(204);

      const deletedUser = await User.findById(user._id);
      expect(deletedUser).toBeNull();
    });
  });
});
javascript
// tests/integration/users.test.js
const request = require('supertest');
const app = require('../../src/app');
const User = require('../../src/models/User');

describe('User API', () => {
  describe('POST /api/users', () => {
    it('should create a new user', async () => {
      const userData = {
        email: 'test@example.com',
        name: 'Test User',
        password: 'Password123'
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect('Content-Type', /json/)
        .expect(201);

      expect(response.body).toHaveProperty('user');
      expect(response.body.user.email).toBe(userData.email);
      expect(response.body.user).not.toHaveProperty('password');
    });

    it('should return 400 for invalid email', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'invalid-email',
          name: 'Test User',
          password: 'Password123'
        })
        .expect(400);

      expect(response.body).toHaveProperty('errors');
    });

    it('should return 409 for duplicate email', async () => {
      const userData = {
        email: 'duplicate@example.com',
        name: 'Test User',
        password: 'Password123'
      };

      // 创建第一个用户
      await User.create(userData);

      // 尝试创建重复用户
      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(409);

      expect(response.body.message).toMatch(/duplicate/i);
    });
  });

  describe('GET /api/users/:id', () => {
    it('should get user by ID', async () => {
      const user = await User.create({
        email: 'get@example.com',
        name: 'Get User',
        password: 'Password123'
      });

      const response = await request(app)
        .get(`/api/users/${user._id}`)
        .expect(200);

      expect(response.body.user._id).toBe(user._id.toString());
    });

    it('should return 404 for non-existent user', async () => {
      const fakeId = '507f1f77bcf86cd799439011';

      await request(app)
        .get(`/api/users/${fakeId}`)
        .expect(404);
    });
  });

  describe('PUT /api/users/:id', () => {
    it('should update user', async () => {
      const user = await User.create({
        email: 'update@example.com',
        name: 'Update User',
        password: 'Password123'
      });

      const response = await request(app)
        .put(`/api/users/${user._id}`)
        .send({ name: 'Updated Name' })
        .expect(200);

      expect(response.body.user.name).toBe('Updated Name');
    });
  });

  describe('DELETE /api/users/:id', () => {
    it('should delete user', async () => {
      const user = await User.create({
        email: 'delete@example.com',
        name: 'Delete User',
        password: 'Password123'
      });

      await request(app)
        .delete(`/api/users/${user._id}`)
        .expect(204);

      const deletedUser = await User.findById(user._id);
      expect(deletedUser).toBeNull();
    });
  });
});

Authentication Testing

认证测试

javascript
// tests/integration/auth.test.js
const request = require('supertest');
const app = require('../../src/app');
const User = require('../../src/models/User');

describe('Authentication', () => {
  let authToken;
  let testUser;

  beforeEach(async () => {
    // Create test user
    testUser = await User.create({
      email: 'auth@example.com',
      name: 'Auth User',
      password: 'Password123'
    });

    // Login to get token
    const response = await request(app)
      .post('/api/auth/login')
      .send({
        email: 'auth@example.com',
        password: 'Password123'
      });

    authToken = response.body.token;
  });

  describe('POST /api/auth/login', () => {
    it('should login with valid credentials', async () => {
      const response = await request(app)
        .post('/api/auth/login')
        .send({
          email: 'auth@example.com',
          password: 'Password123'
        })
        .expect(200);

      expect(response.body).toHaveProperty('token');
      expect(response.body).toHaveProperty('user');
    });

    it('should reject invalid credentials', async () => {
      await request(app)
        .post('/api/auth/login')
        .send({
          email: 'auth@example.com',
          password: 'WrongPassword'
        })
        .expect(401);
    });
  });

  describe('GET /api/auth/me', () => {
    it('should get current user with valid token', async () => {
      const response = await request(app)
        .get('/api/auth/me')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(200);

      expect(response.body.user.email).toBe('auth@example.com');
    });

    it('should reject request without token', async () => {
      await request(app)
        .get('/api/auth/me')
        .expect(401);
    });

    it('should reject request with invalid token', async () => {
      await request(app)
        .get('/api/auth/me')
        .set('Authorization', 'Bearer invalid-token')
        .expect(401);
    });
  });
});
javascript
// tests/integration/auth.test.js
const request = require('supertest');
const app = require('../../src/app');
const User = require('../../src/models/User');

describe('Authentication', () => {
  let authToken;
  let testUser;

  beforeEach(async () => {
    // 创建测试用户
    testUser = await User.create({
      email: 'auth@example.com',
      name: 'Auth User',
      password: 'Password123'
    });

    // 登录获取token
    const response = await request(app)
      .post('/api/auth/login')
      .send({
        email: 'auth@example.com',
        password: 'Password123'
      });

    authToken = response.body.token;
  });

  describe('POST /api/auth/login', () => {
    it('should login with valid credentials', async () => {
      const response = await request(app)
        .post('/api/auth/login')
        .send({
          email: 'auth@example.com',
          password: 'Password123'
        })
        .expect(200);

      expect(response.body).toHaveProperty('token');
      expect(response.body).toHaveProperty('user');
    });

    it('should reject invalid credentials', async () => {
      await request(app)
        .post('/api/auth/login')
        .send({
          email: 'auth@example.com',
          password: 'WrongPassword'
        })
        .expect(401);
    });
  });

  describe('GET /api/auth/me', () => {
    it('should get current user with valid token', async () => {
      const response = await request(app)
        .get('/api/auth/me')
        .set('Authorization', `Bearer ${authToken}`)
        .expect(200);

      expect(response.body.user.email).toBe('auth@example.com');
    });

    it('should reject request without token', async () => {
      await request(app)
        .get('/api/auth/me')
        .expect(401);
    });

    it('should reject request with invalid token', async () => {
      await request(app)
        .get('/api/auth/me')
        .set('Authorization', 'Bearer invalid-token')
        .expect(401);
    });
  });
});

Test Factories and Fixtures

测试工厂与测试数据

javascript
// tests/factories/userFactory.js
const User = require('../../src/models/User');

let userCount = 0;

exports.createUser = async (overrides = {}) => {
  userCount++;

  const defaultData = {
    email: `user${userCount}@example.com`,
    name: `User ${userCount}`,
    password: 'Password123'
  };

  return User.create({ ...defaultData, ...overrides });
};

exports.createUsers = async (count, overrides = {}) => {
  const users = [];
  for (let i = 0; i < count; i++) {
    users.push(await exports.createUser(overrides));
  }
  return users;
};
Usage:
javascript
const { createUser, createUsers } = require('../factories/userFactory');

describe('User operations', () => {
  it('should list all users', async () => {
    await createUsers(5);

    const response = await request(app)
      .get('/api/users')
      .expect(200);

    expect(response.body.users).toHaveLength(5);
  });

  it('should create admin user', async () => {
    const admin = await createUser({ role: 'admin' });
    expect(admin.role).toBe('admin');
  });
});
javascript
// tests/factories/userFactory.js
const User = require('../../src/models/User');

let userCount = 0;

exports.createUser = async (overrides = {}) => {
  userCount++;

  const defaultData = {
    email: `user${userCount}@example.com`,
    name: `User ${userCount}`,
    password: 'Password123'
  };

  return User.create({ ...defaultData, ...overrides });
};

exports.createUsers = async (count, overrides = {}) => {
  const users = [];
  for (let i = 0; i < count; i++) {
    users.push(await exports.createUser(overrides));
  }
  return users;
};
使用方式:
javascript
const { createUser, createUsers } = require('../factories/userFactory');

describe('User operations', () => {
  it('should list all users', async () => {
    await createUsers(5);

    const response = await request(app)
      .get('/api/users')
      .expect(200);

    expect(response.body.users).toHaveLength(5);
  });

  it('should create admin user', async () => {
    const admin = await createUser({ role: 'admin' });
    expect(admin.role).toBe('admin');
  });
});

Test Coverage

测试覆盖率

javascript
// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:unit": "jest tests/unit",
    "test:integration": "jest tests/integration"
  },
  "jest": {
    "testEnvironment": "node",
    "coveragePathIgnorePatterns": ["/node_modules/"],
    "collectCoverageFrom": [
      "src/**/*.js",
      "!src/tests/**"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}
javascript
// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:unit": "jest tests/unit",
    "test:integration": "jest tests/integration"
  },
  "jest": {
    "testEnvironment": "node",
    "coveragePathIgnorePatterns": ["/node_modules/"],
    "collectCoverageFrom": [
      "src/**/*.js",
      "!src/tests/**"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

Production Operations

生产环境运维

Environment Configuration

环境配置

javascript
// config/index.js
require('dotenv').config();

const config = {
  // Environment
  env: process.env.NODE_ENV || 'development',
  port: parseInt(process.env.PORT, 10) || 3000,

  // Database
  database: {
    url: process.env.DATABASE_URL,
    poolMin: parseInt(process.env.DB_POOL_MIN, 10) || 2,
    poolMax: parseInt(process.env.DB_POOL_MAX, 10) || 10
  },

  // Redis
  redis: {
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT, 10) || 6379,
    password: process.env.REDIS_PASSWORD
  },

  // JWT
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '7d',
    refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '30d'
  },

  // CORS
  cors: {
    origins: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000']
  },

  // Rate Limiting
  rateLimit: {
    windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 900000,
    max: parseInt(process.env.RATE_LIMIT_MAX, 10) || 100
  },

  // Logging
  logging: {
    level: process.env.LOG_LEVEL || 'info',
    file: process.env.LOG_FILE || 'logs/app.log'
  }
};

// Validate required configuration
const requiredConfig = [
  'database.url',
  'jwt.secret'
];

requiredConfig.forEach(key => {
  const value = key.split('.').reduce((obj, k) => obj?.[k], config);
  if (!value) {
    throw new Error(`Missing required configuration: ${key}`);
  }
});

module.exports = config;
.env.example:
bash
undefined
javascript
// config/index.js
require('dotenv').config();

const config = {
  // 环境
  env: process.env.NODE_ENV || 'development',
  port: parseInt(process.env.PORT, 10) || 3000,

  // 数据库
  database: {
    url: process.env.DATABASE_URL,
    poolMin: parseInt(process.env.DB_POOL_MIN, 10) || 2,
    poolMax: parseInt(process.env.DB_POOL_MAX, 10) || 10
  },

  // Redis
  redis: {
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT, 10) || 6379,
    password: process.env.REDIS_PASSWORD
  },

  // JWT
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '7d',
    refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '30d'
  },

  // CORS
  cors: {
    origins: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000']
  },

  // 速率限制
  rateLimit: {
    windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 900000,
    max: parseInt(process.env.RATE_LIMIT_MAX, 10) || 100
  },

  // 日志
  logging: {
    level: process.env.LOG_LEVEL || 'info',
    file: process.env.LOG_FILE || 'logs/app.log'
  }
};

// 验证必填配置
const requiredConfig = [
  'database.url',
  'jwt.secret'
];

requiredConfig.forEach(key => {
  const value = key.split('.').reduce((obj, k) => obj?.[k], config);
  if (!value) {
    throw new Error(`Missing required configuration: ${key}`);
  }
});

module.exports = config;
.env.example:
bash
undefined

Environment

环境

NODE_ENV=production PORT=3000
NODE_ENV=production PORT=3000

Database

数据库

DATABASE_URL=mongodb://localhost:27017/myapp DB_POOL_MIN=2 DB_POOL_MAX=10
DATABASE_URL=mongodb://localhost:27017/myapp DB_POOL_MIN=2 DB_POOL_MAX=10

Redis

Redis

REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD=
REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD=

JWT

JWT

JWT_SECRET=your-super-secret-jwt-key-min-32-chars JWT_EXPIRES_IN=7d JWT_REFRESH_EXPIRES_IN=30d
JWT_SECRET=your-super-secret-jwt-key-min-32-chars JWT_EXPIRES_IN=7d JWT_REFRESH_EXPIRES_IN=30d

CORS

CORS

Rate Limiting

速率限制

RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX=100
RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX=100

Logging

日志

LOG_LEVEL=info LOG_FILE=logs/app.log
undefined
LOG_LEVEL=info LOG_FILE=logs/app.log
undefined

Structured Logging

结构化日志

javascript
// config/logger.js
const winston = require('winston');
const path = require('path');

const logLevels = {
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  debug: 4
};

const logColors = {
  error: 'red',
  warn: 'yellow',
  info: 'green',
  http: 'magenta',
  debug: 'blue'
};

winston.addColors(logColors);

const format = winston.format.combine(
  winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
  winston.format.errors({ stack: true }),
  winston.format.splat(),
  winston.format.json()
);

const transports = [
  // Error logs
  new winston.transports.File({
    filename: path.join('logs', 'error.log'),
    level: 'error',
    maxsize: 5242880, // 5MB
    maxFiles: 5
  }),

  // Combined logs
  new winston.transports.File({
    filename: path.join('logs', 'combined.log'),
    maxsize: 5242880,
    maxFiles: 5
  })
];

// Console transport in development
if (process.env.NODE_ENV !== 'production') {
  transports.push(
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize({ all: true }),
        winston.format.printf(
          (info) => `${info.timestamp} ${info.level}: ${info.message}`
        )
      )
    })
  );
}

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  levels: logLevels,
  format,
  transports
});

module.exports = logger;
Usage:
javascript
const logger = require('./config/logger');

logger.info('Server started', { port: 3000 });
logger.error('Database connection failed', { error: err.message });
logger.debug('User data', { userId: user.id, email: user.email });
Request Logging Middleware:
javascript
// middleware/requestLogger.js
const logger = require('../config/logger');

module.exports = (req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;

    logger.http('Request completed', {
      method: req.method,
      url: req.url,
      statusCode: res.statusCode,
      duration: `${duration}ms`,
      ip: req.ip,
      userAgent: req.get('user-agent'),
      userId: req.user?.id
    });
  });

  next();
};
javascript
// config/logger.js
const winston = require('winston');
const path = require('path');

const logLevels = {
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  debug: 4
};

const logColors = {
  error: 'red',
  warn: 'yellow',
  info: 'green',
  http: 'magenta',
  debug: 'blue'
};

winston.addColors(logColors);

const format = winston.format.combine(
  winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
  winston.format.errors({ stack: true }),
  winston.format.splat(),
  winston.format.json()
);

const transports = [
  // 错误日志
  new winston.transports.File({
    filename: path.join('logs', 'error.log'),
    level: 'error',
    maxsize: 5242880, // 5MB
    maxFiles: 5
  }),

  // 组合日志
  new winston.transports.File({
    filename: path.join('logs', 'combined.log'),
    maxsize: 5242880,
    maxFiles: 5
  })
];

// 开发环境添加控制台输出
if (process.env.NODE_ENV !== 'production') {
  transports.push(
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize({ all: true }),
        winston.format.printf(
          (info) => `${info.timestamp} ${info.level}: ${info.message}`
        )
      )
    })
  );
}

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  levels: logLevels,
  format,
  transports
});

module.exports = logger;
使用方式:
javascript
const logger = require('./config/logger');

logger.info('Server started', { port: 3000 });
logger.error('Database connection failed', { error: err.message });
logger.debug('User data', { userId: user.id, email: user.email });
请求日志中间件:
javascript
// middleware/requestLogger.js
const logger = require('../config/logger');

module.exports = (req, res, next) => {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;

    logger.http('Request completed', {
      method: req.method,
      url: req.url,
      statusCode: res.statusCode,
      duration: `${duration}ms`,
      ip: req.ip,
      userAgent: req.get('user-agent'),
      userId: req.user?.id
    });
  });

  next();
};

Health Check Endpoints

健康检查端点

javascript
// routes/health.js
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const redis = require('redis');

const redisClient = redis.createClient();

// Basic health check
router.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    uptime: process.uptime(),
    timestamp: new Date().toISOString()
  });
});

// Detailed health check
router.get('/health/detailed', async (req, res) => {
  const health = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    services: {}
  };

  // Check MongoDB
  try {
    const mongoState = mongoose.connection.readyState;
    health.services.mongodb = {
      status: mongoState === 1 ? 'connected' : 'disconnected',
      state: mongoState
    };
  } catch (error) {
    health.services.mongodb = {
      status: 'error',
      error: error.message
    };
    health.status = 'degraded';
  }

  // Check Redis
  try {
    await redisClient.ping();
    health.services.redis = {
      status: 'connected'
    };
  } catch (error) {
    health.services.redis = {
      status: 'error',
      error: error.message
    };
    health.status = 'degraded';
  }

  // Memory usage
  const memUsage = process.memoryUsage();
  health.memory = {
    rss: `${Math.round(memUsage.rss / 1024 / 1024)}MB`,
    heapUsed: `${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(memUsage.heapTotal / 1024 / 1024)}MB`
  };

  const statusCode = health.status === 'ok' ? 200 : 503;
  res.status(statusCode).json(health);
});

// Readiness check (Kubernetes)
router.get('/ready', async (req, res) => {
  try {
    // Check if app can serve requests
    await mongoose.connection.db.admin().ping();
    res.status(200).json({ status: 'ready' });
  } catch (error) {
    res.status(503).json({ status: 'not ready', error: error.message });
  }
});

// Liveness check (Kubernetes)
router.get('/live', (req, res) => {
  res.status(200).json({ status: 'alive' });
});

module.exports = router;
javascript
// routes/health.js
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const redis = require('redis');

const redisClient = redis.createClient();

// 基础健康检查
router.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    uptime: process.uptime(),
    timestamp: new Date().toISOString()
  });
});

// 详细健康检查
router.get('/health/detailed', async (req, res) => {
  const health = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    services: {}
  };

  // 检查MongoDB
  try {
    const mongoState = mongoose.connection.readyState;
    health.services.mongodb = {
      status: mongoState === 1 ? 'connected' : 'disconnected',
      state: mongoState
    };
  } catch (error) {
    health.services.mongodb = {
      status: 'error',
      error: error.message
    };
    health.status = 'degraded';
  }

  // 检查Redis
  try {
    await redisClient.ping();
    health.services.redis = {
      status: 'connected'
    };
  } catch (error) {
    health.services.redis = {
      status: 'error',
      error: error.message
    };
    health.status = 'degraded';
  }

  // 内存使用情况
  const memUsage = process.memoryUsage();
  health.memory = {
    rss: `${Math.round(memUsage.rss / 1024 / 1024)}MB`,
    heapUsed: `${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(memUsage.heapTotal / 1024 / 1024)}MB`
  };

  const statusCode = health.status === 'ok' ? 200 : 503;
  res.status(statusCode).json(health);
});

// 就绪检查(Kubernetes)
router.get('/ready', async (req, res) => {
  try {
    // 检查应用是否可以处理请求
    await mongoose.connection.db.admin().ping();
    res.status(200).json({ status: 'ready' });
  } catch (error) {
    res.status(503).json({ status: 'not ready', error: error.message });
  }
});

// 存活检查(Kubernetes)
router.get('/live', (req, res) => {
  res.status(200).json({ status: 'alive' });
});

module.exports = router;

Graceful Shutdown

优雅停机

javascript
// server.js
const app = require('./app');
const logger = require('./config/logger');
const mongoose = require('./config/database');
const redis = require('./config/redis');

const PORT = process.env.PORT || 3000;

const server = app.listen(PORT, () => {
  logger.info(`Server running on port ${PORT}`);
});

// Graceful shutdown function
async function gracefulShutdown(signal) {
  logger.info(`${signal} received, starting graceful shutdown`);

  // Stop accepting new connections
  server.close(async () => {
    logger.info('HTTP server closed');

    try {
      // Close database connections
      await mongoose.connection.close(false);
      logger.info('MongoDB connection closed');

      // Close Redis connection
      await redis.quit();
      logger.info('Redis connection closed');

      // Close any other resources
      // await closeOtherResources();

      logger.info('Graceful shutdown completed');
      process.exit(0);
    } catch (error) {
      logger.error('Error during shutdown', { error: error.message });
      process.exit(1);
    }
  });

  // Force shutdown after timeout
  setTimeout(() => {
    logger.error('Forcing shutdown after timeout');
    process.exit(1);
  }, 30000); // 30 seconds
}

// Handle termination signals
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

// Handle uncaught errors
process.on('uncaughtException', (error) => {
  logger.error('Uncaught exception', { error: error.message, stack: error.stack });
  gracefulShutdown('uncaughtException');
});

process.on('unhandledRejection', (reason, promise) => {
  logger.error('Unhandled rejection', { reason, promise });
  gracefulShutdown('unhandledRejection');
});

module.exports = server;
javascript
// server.js
const app = require('./app');
const logger = require('./config/logger');
const mongoose = require('./config/database');
const redis = require('./config/redis');

const PORT = process.env.PORT || 3000;

const server = app.listen(PORT, () => {
  logger.info(`Server running on port ${PORT}`);
});

// 优雅停机函数
async function gracefulShutdown(signal) {
  logger.info(`${signal} received, starting graceful shutdown`);

  // 停止接受新连接
  server.close(async () => {
    logger.info('HTTP server closed');

    try {
      // 关闭数据库连接
      await mongoose.connection.close(false);
      logger.info('MongoDB connection closed');

      // 关闭Redis连接
      await redis.quit();
      logger.info('Redis connection closed');

      // 关闭其他资源
      // await closeOtherResources();

      logger.info('Graceful shutdown completed');
      process.exit(0);
    } catch (error) {
      logger.error('Error during shutdown', { error: error.message });
      process.exit(1);
    }
  });

  // 超时后强制停机
  setTimeout(() => {
    logger.error('Forcing shutdown after timeout');
    process.exit(1);
  }, 30000); // 30秒
}

// 处理终止信号
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

// 处理未捕获的错误
process.on('uncaughtException', (error) => {
  logger.error('Uncaught exception', { error: error.message, stack: error.stack });
  gracefulShutdown('uncaughtException');
});

process.on('unhandledRejection', (reason, promise) => {
  logger.error('Unhandled rejection', { reason, promise });
  gracefulShutdown('unhandledRejection');
});

module.exports = server;

PM2 Clustering

PM2集群模式

javascript
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'express-api',
    script: './src/server.js',

    // Clustering
    instances: 'max', // Use all CPU cores
    exec_mode: 'cluster',

    // Environment variables
    env: {
      NODE_ENV: 'development',
      PORT: 3000
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 8080
    },

    // Restart policies
    autorestart: true,
    max_restarts: 10,
    min_uptime: '10s',
    max_memory_restart: '500M',

    // Graceful shutdown
    kill_timeout: 5000,
    wait_ready: true,
    listen_timeout: 10000,

    // Logging
    error_file: './logs/pm2-error.log',
    out_file: './logs/pm2-out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    merge_logs: true,

    // Monitoring
    instance_var: 'INSTANCE_ID',

    // Watch (development only)
    watch: false
  }],

  // Deploy configuration
  deploy: {
    production: {
      user: 'deploy',
      host: 'production.example.com',
      ref: 'origin/main',
      repo: 'git@github.com:username/repo.git',
      path: '/var/www/production',
      'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env production'
    }
  }
};
PM2 Commands:
bash
undefined
javascript
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'express-api',
    script: './src/server.js',

    // 集群配置
    instances: 'max', // 使用所有CPU核心
    exec_mode: 'cluster',

    // 环境变量
    env: {
      NODE_ENV: 'development',
      PORT: 3000
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 8080
    },

    // 重启策略
    autorestart: true,
    max_restarts: 10,
    min_uptime: '10s',
    max_memory_restart: '500M',

    // 优雅停机
    kill_timeout: 5000,
    wait_ready: true,
    listen_timeout: 10000,

    // 日志
    error_file: './logs/pm2-error.log',
    out_file: './logs/pm2-out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    merge_logs: true,

    // 监控
    instance_var: 'INSTANCE_ID',

    // 监听文件变化(仅开发环境)
    watch: false
  }],

  // 部署配置
  deploy: {
    production: {
      user: 'deploy',
      host: 'production.example.com',
      ref: 'origin/main',
      repo: 'git@github.com:username/repo.git',
      path: '/var/www/production',
      'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env production'
    }
  }
};
PM2命令:
bash
undefined

Start cluster

启动集群

pm2 start ecosystem.config.js --env production
pm2 start ecosystem.config.js --env production

Zero-downtime reload

零停机重载

pm2 reload express-api
pm2 reload express-api

Monitor

监控

pm2 monit
pm2 monit

View logs

查看日志

pm2 logs express-api
pm2 logs express-api

Scale instances

扩展实例数

pm2 scale express-api 4
pm2 scale express-api 4

Stop

停止

pm2 stop express-api
pm2 stop express-api

Restart

重启

pm2 restart express-api
pm2 restart express-api

Delete

删除

pm2 delete express-api
pm2 delete express-api

Save process list

保存进程列表

pm2 save
pm2 save

Startup script

设置开机自启

pm2 startup
pm2 startup

Deploy

部署

pm2 deploy production
undefined
pm2 deploy production
undefined

Development Workflow

开发工作流

Nodemon Configuration

Nodemon配置

json
{
  "watch": ["src"],
  "ext": "js,json",
  "ignore": [
    "src/**/*.test.js",
    "src/**/*.spec.js",
    "node_modules/**/*",
    "logs/**/*"
  ],
  "exec": "node src/server.js",
  "env": {
    "NODE_ENV": "development",
    "PORT": "3000"
  },
  "delay": 1000,
  "verbose": false,
  "restartable": "rs",
  "signal": "SIGTERM"
}
json
{
  "watch": ["src"],
  "ext": "js,json",
  "ignore": [
    "src/**/*.test.js",
    "src/**/*.spec.js",
    "node_modules/**/*",
    "logs/**/*"
  ],
  "exec": "node src/server.js",
  "env": {
    "NODE_ENV": "development",
    "PORT": "3000"
  },
  "delay": 1000,
  "verbose": false,
  "restartable": "rs",
  "signal": "SIGTERM"
}

Package.json Scripts

Package.json脚本

json
{
  "scripts": {
    "dev": "nodemon src/server.js",
    "dev:debug": "nodemon --inspect src/server.js",
    "start": "node src/server.js",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "lint": "eslint src/**/*.js",
    "lint:fix": "eslint src/**/*.js --fix",
    "format": "prettier --write \"src/**/*.js\"",
    "prod": "pm2 start ecosystem.config.js --env production",
    "reload": "pm2 reload express-api",
    "stop": "pm2 stop express-api",
    "logs": "pm2 logs express-api"
  }
}
json
{
  "scripts": {
    "dev": "nodemon src/server.js",
    "dev:debug": "nodemon --inspect src/server.js",
    "start": "node src/server.js",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "lint": "eslint src/**/*.js",
    "lint:fix": "eslint src/**/*.js --fix",
    "format": "prettier --write \"src/**/*.js\"",
    "prod": "pm2 start ecosystem.config.js --env production",
    "reload": "pm2 reload express-api",
    "stop": "pm2 stop express-api",
    "logs": "pm2 logs express-api"
  }
}

Decision Trees

决策树

Middleware Selection

中间件选择

Need middleware?
├─ Security?
│  ├─ Headers → helmet
│  ├─ CORS → cors
│  ├─ Rate limiting → express-rate-limit
│  └─ Input validation → express-validator
├─ Parsing?
│  ├─ JSON → express.json()
│  ├─ Form data → express.urlencoded()
│  └─ Multipart → multer
├─ Logging?
│  ├─ Development → morgan('dev')
│  └─ Production → winston + morgan('combined')
├─ Compression?
│  └─ Response compression → compression()
└─ Authentication?
   ├─ Session-based → express-session + connect-redis
   └─ Token-based → jsonwebtoken
需要中间件?
├─ 安全相关?
│  ├─ 安全头 → helmet
│  ├─ 跨域 → cors
│  ├─ 速率限制 → express-rate-limit
│  └─ 输入验证 → express-validator
├─ 解析相关?
│  ├─ JSON → express.json()
│  ├─ 表单数据 → express.urlencoded()
│  └─ 多部分数据 → multer
├─ 日志相关?
│  ├─ 开发环境 → morgan('dev')
│  └─ 生产环境 → winston + morgan('combined')
├─ 压缩相关?
│  └─ 响应压缩 → compression()
└─ 认证相关?
   ├─ 会话式 → express-session + connect-redis
   └─ 令牌式 → jsonwebtoken

Error Handling Strategy

错误处理策略

Error occurred?
├─ Operational error? (Known error)
│  ├─ Validation error → 400 with details
│  ├─ Authentication error → 401
│  ├─ Authorization error → 403
│  ├─ Not found error → 404
│  └─ Conflict error → 409
├─ Programming error? (Bug)
│  ├─ Development → Send full error + stack
│  └─ Production → Log error, send generic message
└─ External service error?
   ├─ Retry → Exponential backoff
   └─ Circuit breaker → Fail fast
发生错误?
├─ 业务错误?(已知错误)
│  ├─ 验证错误 → 400 + 详细信息
│  ├─ 认证错误 → 401
│  ├─ 授权错误 → 403
│  ├─ 资源不存在 → 404
│  └─ 资源冲突 → 409
├─ 编程错误?(Bug)
│  ├─ 开发环境 → 返回完整错误 + 堆栈
│  └─ 生产环境 → 记录错误,返回通用消息
└─ 外部服务错误?
   ├─ 重试 → 指数退避
   └─ 熔断 → 快速失败

Testing Approach

测试方法

What to test?
├─ API endpoints?
│  └─ Integration tests → Supertest
├─ Business logic?
│  └─ Unit tests → Jest
├─ Database operations?
│  └─ Integration tests → MongoMemoryServer
├─ Authentication?
│  └─ Integration tests → Test token flow
└─ Error handling?
   └─ Unit + Integration tests → Test error cases
需要测试什么?
├─ API端点?
│  └─ 集成测试 → Supertest
├─ 业务逻辑?
│  └─ 单元测试 → Jest
├─ 数据库操作?
│  └─ 集成测试 → MongoMemoryServer
├─ 认证流程?
│  └─ 集成测试 → 测试令牌流程
└─ 错误处理?
   └─ 单元+集成测试 → 测试错误场景

Deployment Pattern

部署模式

Deployment target?
├─ Local development?
│  └─ Nodemon
├─ Single server?
│  ├─ Small app → node server.js
│  └─ Production → PM2 (single instance)
├─ Multi-core server?
│  └─ PM2 cluster mode
├─ Container?
│  ├─ Single container → Docker + node
│  └─ Orchestrated → Docker + Kubernetes
└─ Serverless?
   └─ AWS Lambda + API Gateway
部署目标?
├─ 本地开发?
│  └─ Nodemon
├─ 单服务器?
│  ├─ 小型应用 → node server.js
│  └─ 生产环境 → PM2(单实例)
├─ 多核服务器?
│  └─ PM2集群模式
├─ 容器化?
│  ├─ 单容器 → Docker + node
│  └─ 编排部署 → Docker + Kubernetes
└─ 无服务器?
   └─ AWS Lambda + API Gateway

Common Problems & Solutions

常见问题与解决方案

Problem 1: Port Already in Use

问题1:端口已被占用

Symptoms:
Error: listen EADDRINUSE: address already in use :::3000
Solution:
bash
undefined
症状:
Error: listen EADDRINUSE: address already in use :::3000
解决方案:
bash
undefined

Find and kill process on port

查找并杀死占用端口的进程

lsof -ti:3000 | xargs kill -9
lsof -ti:3000 | xargs kill -9

Or use different port

或使用其他端口

PORT=3001 npm run dev
PORT=3001 npm run dev

Or add cleanup script

或添加清理脚本

{ "scripts": { "predev": "kill-port 3000 || true", "dev": "nodemon server.js" } }
undefined
{ "scripts": { "predev": "kill-port 3000 || true", "dev": "nodemon server.js" } }
undefined

Problem 2: Middleware Order Issues

问题2:中间件顺序错误

Symptom: Routes not working, errors not caught, CORS failures
Solution: Follow correct middleware order:
  1. Security (helmet, cors)
  2. Rate limiting
  3. Parsing (json, urlencoded)
  4. Compression
  5. Logging
  6. Custom middleware
  7. Routes
  8. 404 handler
  9. Error handler (last!)
症状:路由无法工作、错误未被捕获、CORS失败
解决方案:遵循正确的中间件顺序:
  1. 安全中间件(helmet、cors)
  2. 速率限制
  3. 解析中间件(json、urlencoded)
  4. 压缩
  5. 日志
  6. 自定义中间件
  7. 路由
  8. 404处理器
  9. 错误处理器(最后!)

Problem 3: Unhandled Promise Rejections

问题3:未处理的Promise拒绝

Symptom:
UnhandledPromiseRejectionWarning
Solution:
javascript
// Use catchAsync wrapper
const catchAsync = require('./utils/catchAsync');

app.get('/users', catchAsync(async (req, res) => {
  const users = await User.find();
  res.json({ users });
}));

// Or handle at process level
process.on('unhandledRejection', (err) => {
  console.error('UNHANDLED REJECTION!', err);
  server.close(() => process.exit(1));
});
症状
UnhandledPromiseRejectionWarning
解决方案:
javascript
// 使用catchAsync包装器
const catchAsync = require('./utils/catchAsync');

app.get('/users', catchAsync(async (req, res) => {
  const users = await User.find();
  res.json({ users });
}));

// 或在进程级别处理
process.on('unhandledRejection', (err) => {
  console.error('UNHANDLED REJECTION!', err);
  server.close(() => process.exit(1));
});

Problem 4: Sessions Not Working in Cluster Mode

问题4:集群模式下会话不工作

Symptom: User logged in but subsequent requests show logged out
Solution: Use Redis session store
javascript
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const redis = require('redis');

const redisClient = redis.createClient();

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false
}));
症状:用户已登录,但后续请求显示未登录
解决方案:使用Redis会话存储
javascript
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const redis = require('redis');

const redisClient = redis.createClient();

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false
}));

Problem 5: Memory Leaks

问题5:内存泄漏

Symptoms: Memory usage grows over time, server crashes
Solution:
bash
undefined
症状:内存使用随时间增长,服务器崩溃
解决方案:
bash
undefined

Monitor memory with PM2

使用PM2监控内存

pm2 start server.js --max-memory-restart 500M
pm2 start server.js --max-memory-restart 500M

Profile with Node

使用Node.js调试

node --inspect server.js
node --inspect server.js

Then use Chrome DevTools

然后使用Chrome开发者工具

Use clinic.js

使用clinic.js

npm install -g clinic clinic doctor -- node server.js
undefined
npm install -g clinic clinic doctor -- node server.js
undefined

Anti-Patterns

反模式

❌ Don't: Mix Concerns

❌ 错误:混合关注点

javascript
// WRONG: Business logic in routes
app.post('/users', async (req, res) => {
  const user = new User(req.body);
  user.password = await bcrypt.hash(req.body.password, 10);
  await user.save();
  const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET);
  res.json({ user, token });
});
Do: Separate Concerns:
javascript
// CORRECT: Use controllers and services
app.post('/users',
  validate(createUserRules),
  userController.create
);

// controller
exports.create = catchAsync(async (req, res) => {
  const user = await userService.createUser(req.body);
  const token = authService.generateToken(user);
  res.status(201).json({ user, token });
});
javascript
// 错误:业务逻辑写在路由中
app.post('/users', async (req, res) => {
  const user = new User(req.body);
  user.password = await bcrypt.hash(req.body.password, 10);
  await user.save();
  const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET);
  res.json({ user, token });
});
正确:分离关注点:
javascript
// 正确:使用控制器和服务
app.post('/users',
  validate(createUserRules),
  userController.create
);

// 控制器
exports.create = catchAsync(async (req, res) => {
  const user = await userService.createUser(req.body);
  const token = authService.generateToken(user);
  res.status(201).json({ user, token });
});

❌ Don't: Sync Operations

❌ 错误:同步操作

javascript
// WRONG
const data = fs.readFileSync('./data.json');
Do: Async Operations:
javascript
// CORRECT
const data = await fs.promises.readFile('./data.json');
javascript
// 错误
const data = fs.readFileSync('./data.json');
正确:异步操作:
javascript
// 正确
const data = await fs.promises.readFile('./data.json');

❌ Don't: Trust User Input

❌ 错误:信任用户输入

javascript
// WRONG
app.post('/users', (req, res) => {
  User.create(req.body); // Dangerous!
});
Do: Validate and Sanitize:
javascript
// CORRECT
app.post('/users',
  validate(createUserRules),
  userController.create
);
javascript
// 错误
app.post('/users', (req, res) => {
  User.create(req.body); // 危险!
});
正确:验证与清理:
javascript
// 正确
app.post('/users',
  validate(createUserRules),
  userController.create
);

Quick Reference

快速参考

Essential Middleware Stack

必备中间件栈

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

const app = express();

// Minimal production stack
app.use(helmet());
app.use(cors());
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(compression());
app.use(morgan('combined'));

// Routes
app.use('/api/v1', require('./routes'));

// Error handler
app.use(require('./middleware/errorHandler'));
javascript
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const compression = require('compression');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');

const app = express();

// 最小生产环境栈
app.use(helmet());
app.use(cors());
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(compression());
app.use(morgan('combined'));

// 路由
app.use('/api/v1', require('./routes'));

// 错误处理器
app.use(require('./middleware/errorHandler'));

Essential Commands

必备命令

bash
undefined
bash
undefined

Development

开发

npm run dev # Start with nodemon npm test # Run tests npm run test:watch # Watch mode npm run lint # Lint code
npm run dev # 使用nodemon启动 npm test # 运行测试 npm run test:watch # 监听模式 npm run lint # 代码检查

Production

生产

npm start # Start production pm2 start ecosystem.config.js # Start with PM2 pm2 reload app # Zero-downtime reload pm2 logs app # View logs pm2 monit # Monitor
npm start # 启动生产环境 pm2 start ecosystem.config.js # 使用PM2启动 pm2 reload app # 零停机重载 pm2 logs app # 查看日志 pm2 monit # 监控

Testing

测试

npm test # All tests npm run test:unit # Unit tests npm run test:integration # Integration tests npm run test:coverage # Coverage report
undefined
npm test # 所有测试 npm run test:unit # 单元测试 npm run test:integration # 集成测试 npm run test:coverage # 覆盖率报告
undefined

Related Skills

相关技能

  • nodejs-backend - Node.js backend development patterns
  • fastify-production - Fastify framework (performance-focused alternative)
  • typescript-core - TypeScript with Express
  • docker-containerization - Containerized Express deployment
  • systematic-debugging - Advanced debugging techniques
  • nodejs-backend - Node.js后端开发模式
  • fastify-production - Fastify框架(性能优先的替代方案)
  • typescript-core - TypeScript与Express结合使用
  • docker-containerization - Express应用的容器化部署
  • systematic-debugging - 高级调试技巧

Progressive Disclosure

进阶参考资料

For detailed implementation guides, see:
  • Middleware Patterns - Advanced middleware composition and patterns
  • Security Hardening - Comprehensive security checklist
  • Testing Strategies - Complete testing guide
  • Production Deployment - Deployment architectures and strategies

Version: Express 4.x, PM2 5.x, Node.js 18+ Last Updated: December 2025 License: MIT
如需详细实现指南,请查看:
  • 中间件模式 - 高级中间件组合与模式
  • 安全加固 - 全面安全检查清单
  • 测试策略 - 完整测试指南
  • 生产部署 - 部署架构与策略

版本: Express 4.x, PM2 5.x, Node.js 18+ 最后更新: 2025年12月 许可证: MIT