跳转至

第 3 章:Express.js 框架详解

Express 是 Node.js 最流行的 Web 框架。理解 Express 的核心机制,是构建 Node.js 后端服务的关键。


3.1 Express 是什么?

Express 是一个 最小化、灵活的 Node.js Web 应用框架,提供:

  • HTTP 服务器封装
  • 路由系统
  • 中间件机制
  • 模板引擎支持

原生 Node.js vs Express

// ❌ 原生 Node.js:每增加一个功能,代码复杂度指数增长
const http = require('http');
const url = require('url');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  const parsed = url.parse(req.url, true);
  const { pathname, query } = parsed;

  // 手动处理路由
  if (pathname === '/api/users' && req.method === 'GET') {
    // 手动解析 JSON body
    let body = '';
    req.on('data', chunk => body += chunk);
    req.on('end', () => {
      // 手动设置响应头
      res.writeHead(200, {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      });
      res.end(JSON.stringify({ users: [] }));
    });
  } else if (pathname === '/api/users' && req.method === 'POST') {
    // 重复的路由判断逻辑...
  }
  // ... 更多路由需要更多 if-else
});

// ✅ Express:同样的功能,代码量减少 80%
const express = require('express');
const app = express();
app.use(express.json());
app.use(cors());

app.get('/api/users', (req, res) => {
  res.json({ users: [] });
});

app.post('/api/users', (req, res) => {
  res.status(201).json({ user: req.body });
});

app.listen(3000);

3.2 中间件(Middleware)—— Express 的灵魂

什么是中间件?

中间件是一个 函数,可以访问请求对象(req)、响应对象(res)和下一个中间件(next)。

function myMiddleware(req, res, next) {
  // 1. 执行某些操作
  console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);

  // 2. 修改 req 或 res
  req.startTime = Date.now();

  // 3. 调用 next() 传递给下一个中间件
  next();

  // 或者结束请求(不调用 next)
  // res.json({ message: '提前返回' });
}

中间件执行流程

1
2
3
4
5
6
7
请求 → [中间件1] → [中间件2] → [路由处理] → [响应]
          ↓             ↓           ↓
       日志记录      身份验证      业务逻辑
          ↓             ↓           ↓
       修改 req      检查 token    查询数据库
          ↓             ↓           ↓
       next()        next()       res.json()

中间件类型

1. 应用级中间件

// 对所有路由生效
app.use((req, res, next) => {
  req.requestId = Math.random().toString(36).substr(2, 9);
  next();
});

// 对特定路径生效
app.use('/api/', (req, res, next) => {
  console.log('API 请求:', req.url);
  next();
});

2. 路由级中间件

// 只对特定路由生效
const authenticate = (req, res, next) => {
  const token = req.headers.authorization;
  if (!token) return res.status(401).json({ error: '未认证' });
  req.user = decodeToken(token);
  next();
};

app.get('/api/profile', authenticate, (req, res) => {
  res.json({ user: req.user });
});

3. 错误处理中间件

// 错误处理中间件有 4 个参数(必须有 err)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    error: {
      message: err.message || '服务器内部错误',
      ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
    }
  });
});

4. 内置中间件

1
2
3
4
// Express 4.16+ 内置
app.use(express.json());           // 解析 JSON 请求体
app.use(express.urlencoded());     // 解析 URL-encoded 请求体
app.use(express.static('public')); // 提供静态文件

实际案例:完整的中间件链

const express = require('express');
const app = express();

// 1. 日志中间件(所有请求)
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.url} ${res.statusCode} ${duration}ms`);
  });
  next();
});

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

// 3. CORS
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  if (req.method === 'OPTIONS') return res.sendStatus(204);
  next();
});

// 4. 认证中间件
const auth = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: '需要认证' });
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch (e) {
    res.status(401).json({ error: 'Token 无效' });
  }
};

// 5. 限流中间件
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 分钟
  max: 100                    // 最多 100 次请求
});
app.use('/api/', limiter);

// 6. 路由
app.get('/api/public', (req, res) => {
  res.json({ message: '公开接口' });
});

app.get('/api/protected', auth, (req, res) => {
  res.json({ message: '受保护接口', user: req.user });
});

// 7. 404 处理
app.use((req, res) => {
  res.status(404).json({ error: '接口不存在' });
});

// 8. 错误处理(必须放在最后)
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: '服务器错误' });
});

3.3 路由系统

基本路由

1
2
3
4
app.get('/', (req, res) => res.send('首页'));
app.post('/users', (req, res) => res.json({ created: true }));
app.put('/users/:id', (req, res) => res.json({ updated: true }));
app.delete('/users/:id', (req, res) => res.status(204).send());

路由参数

// 路径参数
app.get('/users/:id/posts/:postId', (req, res) => {
  console.log(req.params); // { id: '123', postId: '456' }
});

// 查询参数
app.get('/search', (req, res) => {
  console.log(req.query);  // { q: 'nodejs', page: '2' }
});

// 可选参数
app.get('/api/v:version?', (req, res) => {
  const version = req.params.version || '1'; // 默认 v1
});

路由正则

1
2
3
4
5
6
7
8
9
// 匹配 .jpg 或 .png
app.get(/\.jpg$/, (req, res) => {
  res.send('这是一个 JPG 文件');
});

// 更精确的正则
app.get(/^\/users\/(\d+)$/, (req, res) => {
  const id = req.params[0]; // 正则捕获组
});

路由模块化(Router)

// routes/users.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => res.json({ users: [] }));
router.get('/:id', (req, res) => res.json({ id: req.params.id }));
router.post('/', (req, res) => res.status(201).json(req.body));

module.exports = router;

// routes/posts.js
const router = express.Router();
router.get('/', (req, res) => res.json({ posts: [] }));
router.post('/', (req, res) => res.status(201).json(req.body));
module.exports = router;

// app.js
const usersRouter = require('./routes/users');
const postsRouter = require('./routes/posts');

app.use('/api/users', usersRouter);
app.use('/api/posts', postsRouter);

3.4 错误处理最佳实践

自定义错误类

// errors/AppError.js
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true; // 可预期的错误
    Error.captureStackTrace(this, this.constructor);
  }
}

class NotFoundError extends AppError {
  constructor(resource) {
    super(`${resource} 不存在`, 404);
  }
}

class ValidationError extends AppError {
  constructor(message) {
    super(message, 422);
  }
}

module.exports = { AppError, NotFoundError, ValidationError };

统一错误处理

// 在路由中抛出错误
app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await db.findUser(req.params.id);
    if (!user) return next(new NotFoundError('用户'));
    res.json(user);
  } catch (err) {
    next(err); // 传递给错误处理中间件
  }
});

// 或者用 async 包装器
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await db.findUser(req.params.id);
  if (!user) throw new NotFoundError('用户');
  res.json(user);
}));

3.5 完整项目结构

my-api/
├── src/
│   ├── app.js              # Express 应用配置
│   ├── server.js           # 启动入口
│   ├── config/
│   │   └── index.js        # 配置文件
│   ├── routes/
│   │   ├── index.js        # 路由汇总
│   │   ├── users.js        # 用户路由
│   │   └── posts.js        # 文章路由
│   ├── controllers/
│   │   ├── userController.js
│   │   └── postController.js
│   ├── middleware/
│   │   ├── auth.js         # 认证中间件
│   │   ├── validate.js     # 验证中间件
│   │   ├── errorHandler.js # 错误处理
│   │   └── logger.js       # 日志中间件
│   ├── models/
│   │   ├── User.js
│   │   └── Post.js
│   ├── services/
│   │   ├── userService.js  # 业务逻辑
│   │   └── emailService.js # 邮件服务
│   ├── utils/
│   │   ├── AppError.js
│   │   └── catchAsync.js
│   └── tests/
│       └── users.test.js
├── .env
├── package.json
└── README.md

3.6 实际案例:完整的用户管理 API

// src/app.js
const express = require('express');
const { AppError, NotFoundError } = require('./utils/AppError');
const errorHandler = require('./middleware/errorHandler');
const logger = require('./middleware/logger');
const auth = require('./middleware/auth');

const app = express();

// 中间件
app.use(logger);
app.use(express.json({ limit: '10mb' }));

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

// 用户路由
app.post('/api/users', async (req, res, next) => {
  try {
    const { name, email, password } = req.body;
    if (!name || !email) {
      return next(new AppError('name 和 email 是必填项', 400));
    }
    const user = await db.createUser({ name, email, password });
    res.status(201).json(user);
  } catch (err) {
    next(err);
  }
});

app.get('/api/users', auth, async (req, res, next) => {
  try {
    const { page = 1, limit = 20, role } = req.query;
    const users = await db.findUsers({
      page: parseInt(page),
      limit: parseInt(limit),
      role
    });
    res.json(users);
  } catch (err) {
    next(err);
  }
});

app.get('/api/users/:id', auth, async (req, res, next) => {
  try {
    const user = await db.findUserById(req.params.id);
    if (!user) return next(new NotFoundError('用户'));
    res.json(user);
  } catch (err) {
    next(err);
  }
});

app.patch('/api/users/:id', auth, async (req, res, next) => {
  try {
    const user = await db.updateUser(req.params.id, req.body);
    res.json(user);
  } catch (err) {
    next(err);
  }
});

app.delete('/api/users/:id', auth, async (req, res, next) => {
  try {
    await db.deleteUser(req.params.id);
    res.status(204).send();
  } catch (err) {
    next(err);
  }
});

// 错误处理(必须放在最后)
app.use(errorHandler);

module.exports = app;

3.7 常用中间件推荐

中间件 用途 安装
cors 跨域支持 npm i cors
helmet 安全头 npm i helmet
morgan HTTP 日志 npm i morgan
express-rate-limit 限流 npm i express-rate-limit
compression Gzip 压缩 npm i compression
cookie-parser Cookie 解析 npm i cookie-parser
multer 文件上传 npm i multer
express-validator 请求验证 npm i express-validator
swagger-ui-express API 文档 npm i swagger-ui-express
1
2
3
4
5
6
7
const helmet = require('helmet');
const compression = require('compression');
const morgan = require('morgan');

app.use(helmet());              // 安全头
app.use(compression());         // Gzip 压缩
app.use(morgan('combined'));    // 日志

3.8 总结

核心概念 一句话
中间件 Express 的灵魂,请求处理的管道
路由 URL 到处理函数的映射
Router 路由模块化,按功能拆分
错误处理 4 参数中间件,统一捕获
项目结构 routes → controllers → services → models

Express 的优势: 简单、灵活、生态丰富(npm 上最多的 Web 框架) Express 的不足: 缺少内置的 ORM、验证、类型系统(需要第三方库)


上一章:REST API 设计规范 | 下一章:FastAPI 框架