Skip to content

Express 框架

Express 核心概念、路由、中间件与最佳实践

Express 概述

特点

  • Node.js 最流行的 Web 框架
  • 简单、灵活、极简主义
  • 内置路由功能
  • 丰富的中间件生态
  • 学习曲线平缓

核心组件

javascript
import express from 'express'

const app = express()

// 请求对象
app.request  // 扩展了 Node 的 IncomingMessage
app.response // 扩展了 Node 的 ServerResponse

// 应用实例
app.listen() // 创建 HTTP 服务器

基础用法

应用创建

javascript
import express from 'express'
import cors from 'cors'
import helmet from 'helmet'
import morgan from 'morgan'

const app = express()

// 安全中间件
app.use(helmet())
app.use(cors())

// 日志
app.use(morgan('combined'))

// 解析 JSON
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

// 静态文件
app.use(express.static('public'))

// 根路由
app.get('/', (req, res) => {
  res.send('Hello Express')
})

// 启动
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000')
})

请求对象

javascript
// req.app      - 引用 Express 实例
// req.baseUrl  - 基础路径
// req.body     - 解析后的请求体
// req.cookies  - Cookie(需 cookie-parser)
// req.fresh    - 是否缓存新鲜
// req.hostname - 主机名
// req.ip       - 客户端 IP
// req.method   - HTTP 方法
// req.params   - 路由参数
// req.path     - 路径
// req.protocol - 协议
// req.query    - 查询参数
// req.route    - 当前路由
// req.secure   - 是否 HTTPS
// req.xhr      - 是否 AJAX 请求

响应对象

javascript
// res.app        - 引用 Express 实例
// res.append()   - 追加响应头
// res.attachment() - 设置 Content-Disposition
// res.cookie()   - 设置 Cookie
// res.clearCookie() - 清除 Cookie
// res.download() - 文件下载
// res.end()      - 结束响应
// res.format()   - 内容协商
// res.get()      - 获取响应头
// res.json()     - JSON 响应
// res.jsonp()    - JSONP 响应
// res.links()    - 设置 Link 头
// res.location() - 设置 Location 头
// res.redirect() - 重定向
// res.render()   - 渲染视图
// res.send()     - 发送响应
// res.sendFile() - 发送文件
// res.sendStatus() - 设置状态码并发送
// res.set()      - 设置响应头
// res.status()   - 设置状态码
// res.type()     - 设置 Content-Type

路由

基础路由

javascript
// GET
app.get('/users', (req, res) => {
  res.json(users)
})

// POST
app.post('/users', (req, res) => {
  const user = createUser(req.body)
  res.status(201).json(user)
})

// PUT
app.put('/users/:id', (req, res) => {
  const user = updateUser(req.params.id, req.body)
  res.json(user)
})

// PATCH
app.patch('/users/:id', (req, res) => {
  const user = patchUser(req.params.id, req.body)
  res.json(user)
})

// DELETE
app.delete('/users/:id', (req, res) => {
  deleteUser(req.params.id)
  res.status(204).send()
})

// 所有方法
app.all('/api/secret', (req, res) => {
  res.json({ message: 'Access granted' })
})

路由参数

javascript
// 路径参数
app.get('/users/:userId', (req, res) => {
  const { userId } = req.params
  res.json({ userId })
})

// 多参数
app.get('/users/:userId/posts/:postId', (req, res) => {
  const { userId, postId } = req.params
  res.json({ userId, postId })
})

// 可选参数
app.get('/users/:userId?', (req, res) => {
  const { userId } = req.params
  res.json(userId ? { userId } : { users: getAllUsers() })
})

// 正则匹配
app.get(/^\/api\/v(\d+)\//, (req, res) => {
  const version = req.params[0]
  res.json({ version })
})

查询参数

javascript
app.get('/users', (req, res) => {
  const { page = 1, limit = 10, sort = 'createdAt', order = 'desc' } = req.query

  const users = getUsers({
    page: parseInt(page),
    limit: parseInt(limit),
    sort: { [sort]: order === 'desc' ? -1 : 1 }
  })

  res.json({
    data: users,
    pagination: {
      page: parseInt(page),
      limit: parseInt(limit),
      total: users.length
    }
  })
})

Router 实例

javascript
// users.js
import express from 'express'
const router = express.Router()

router.get('/', getUsers)
router.get('/:id', getUser)
router.post('/', createUser)
router.put('/:id', updateUser)
router.delete('/:id', deleteUser)

export default router

// app.js
import usersRouter from './routes/users.js'
import postsRouter from './routes/posts.js'

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

路由分组

javascript
// 认证中间件
const authenticate = (req, res, next) => {
  const token = req.headers.authorization
  if (!token) {
    return res.status(401).json({ error: 'Unauthorized' })
  }
  req.user = verifyToken(token)
  next()
}

// Admin 路由组
const adminRouter = express.Router()
adminRouter.use(authenticate)

adminRouter.get('/dashboard', (req, res) => {
  res.json({ message: 'Admin dashboard' })
})

adminRouter.post('/users', createUser)

// API v1 路由组
const apiV1 = express.Router({ prefix: '/v1' })

apiV1.use('/users', usersRouter)
apiV1.use('/posts', postsRouter)

app.use('/api', apiV1)

中间件

中间件类型

javascript
// 1. 应用级中间件
app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`)
  next()
})

// 2. 路由级中间件
app.get('/admin', authenticate, (req, res) => {
  res.json({ message: 'Admin area' })
})

// 3. 错误处理中间件(必须 4 个参数)
app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).json({ error: 'Something went wrong!' })
})

// 4. 内置中间件
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use(express.static('public'))

// 5. 第三方中间件
import cors from 'cors'
import helmet from 'helmet'
import rateLimit from 'express-rate-limit'

app.use(cors())
app.use(helmet())

中间件顺序

javascript
// 顺序很重要!
app.use(cors())                    // 1. CORS(所有请求)
app.use(express.json())            // 2. Body 解析
app.use('/api', router)            // 3. 路由

// 路由内的中间件
router.get('/users', logger, getUsers)

// 错误处理(最后)
app.use(errorHandler)

常见中间件

javascript
import express from 'express'
import cors from 'cors'
import helmet from 'helmet'
import rateLimit from 'express-rate-limit'
import compression from 'compression'
import cookieParser from 'cookie-parser'
import session from 'express-session'

// CORS
app.use(cors({
  origin: 'https://example.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}))

// 安全头
app.use(helmet())

// 速率限制
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 分钟
  max: 100,                   // 限制 100 次
  message: { error: 'Too many requests' }
})
app.use('/api/', limiter)

// Gzip 压缩
app.use(compression())

// Cookie 解析
app.use(cookieParser())

// Session
app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000  // 24 小时
  }
}))

错误处理

同步错误

javascript
app.get('/users/:id', (req, res) => {
  const user = database.get(req.params.id)
  if (!user) {
    return res.status(404).json({ error: 'User not found' })
  }
  res.json(user)
})

异步错误

javascript
app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await database.get(req.params.id)
    if (!user) {
      return res.status(404).json({ error: 'User not found' })
    }
    res.json(user)
  } catch (err) {
    next(err)  // 传递给错误处理中间件
  }
})

// Promise 自动捕获(Express 5+)
app.get('/users/:id', async (req, res) => {
  const user = await database.get(req.params.id)
  res.json(user)
})

错误处理中间件

javascript
// 404 处理(放在所有路由之后)
app.use((req, res) => {
  res.status(404).json({ error: 'Not found' })
})

// 错误处理
app.use((err, req, res, next) => {
  // 记录错误
  console.error(err.stack)

  // 根据环境决定错误详情
  const message = process.env.NODE_ENV === 'production'
    ? 'Internal server error'
    : err.message

  res.status(err.status || 500).json({
    error: message,
    ...(process.env.NODE_ENV !== 'production' && { stack: err.stack })
  })
})

// 自定义错误类
class AppError extends Error {
  constructor(message, status = 500) {
    super(message)
    this.status = status
    this.isOperational = true
    Error.captureStackTrace(this, this.constructor)
  }
}

throw new AppError('Custom error', 400)

文件处理

文件上传

javascript
import express from 'express'
import multer from 'multer'
import path from 'path'

const router = express.Router()

// 配置 multer
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/')
  },
  filename: (req, file, cb) => {
    const unique = Date.now() + '-' + Math.round(Math.random() * 1E9)
    cb(null, unique + path.extname(file.originalname))
  }
})

const upload = multer({
  storage,
  limits: { fileSize: 10 * 1024 * 1024 },  // 10MB
  fileFilter: (req, file, cb) => {
    const allowed = ['image/jpeg', 'image/png', 'image/gif']
    if (allowed.includes(file.mimetype)) {
      cb(null, true)
    } else {
      cb(new Error('Invalid file type'))
    }
  }
})

// 单文件上传
router.post('/upload', upload.single('file'), (req, res) => {
  res.json({
    filename: req.file.filename,
    url: `/uploads/${req.file.filename}`
  })
})

// 多文件上传
router.post('/upload/multiple', upload.array('files', 5), (req, res) => {
  res.json({
    files: req.files.map(f => f.filename)
  })
})

文件下载

javascript
import path from 'path'

router.get('/download/:filename', (req, res) => {
  const { filename } = req.params
  const filePath = path.join(process.cwd(), 'uploads', filename)

  res.download(filePath, filename, (err) => {
    if (err) {
      res.status(404).json({ error: 'File not found' })
    }
  })
})

// 发送 JSON 文件
router.get('/export', (req, res) => {
  const data = getExportData()
  res.setHeader('Content-Type', 'application/json')
  res.setHeader('Content-Disposition', 'attachment; filename="export.json"')
  res.send(JSON.stringify(data, null, 2))
})

最佳实践

项目结构

src/
├── app.js              # Express 应用配置
├── server.js           # 启动入口
├── routes/             # 路由
│   ├── index.js
│   ├── users.js
│   └── posts.js
├── controllers/        # 控制器
│   ├── users.js
│   └── posts.js
├── services/           # 业务逻辑
│   ├── users.js
│   └── posts.js
├── models/             # 数据模型
│   └── user.js
├── middleware/         # 中间件
│   ├── auth.js
│   ├── error.js
│   └── validation.js
├── utils/              # 工具
│   └── helper.js
├── config/             # 配置
│   └── index.js
└── routes.js           # 路由汇总

完整应用示例

javascript
// src/app.js
import express from 'express'
import cors from 'cors'
import helmet from 'helmet'
import { errorHandler, notFoundHandler } from './middleware/error.js'
import routes from './routes.js'

const app = express()

// 安全
app.use(helmet())
app.use(cors({
  origin: process.env.CORS_ORIGIN || '*'
}))

// Body 解析
app.use(express.json({ limit: '10mb' }))
app.use(express.urlencoded({ extended: true }))

// 路由
app.use(routes)

// 404
app.use(notFoundHandler)

// 错误处理
app.use(errorHandler)

export default app

// src/server.js
import app from './app.js'

const PORT = process.env.PORT || 3000

const server = app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`)
})

// 优雅退出
process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down gracefully')
  server.close(() => {
    console.log('Server closed')
    process.exit(0)
  })
})

// src/routes.js
import express from 'express'
import usersRouter from './routes/users.js'
import postsRouter from './routes/posts.js'
import { authenticate } from './middleware/auth.js'

const router = express.Router()

// 公开路由
router.get('/health', (req, res) => {
  res.json({ status: 'ok' })
})

// 认证路由
router.post('/auth/login', authenticateToken, (req, res) => {
  res.json({ token: generateToken(req.user) })
})

// API 路由
router.use('/users', authenticate, usersRouter)
router.use('/posts', authenticate, postsRouter)

export default router

// src/middleware/error.js
export const errorHandler = (err, req, res, next) => {
  console.error(err.stack)
  res.status(err.status || 500).json({
    success: false,
    message: process.env.NODE_ENV === 'production'
      ? 'Internal server error'
      : err.message
  })
}

export const notFoundHandler = (req, res) => {
  res.status(404).json({
    success: false,
    message: `Route ${req.method} ${req.path} not found`
  })
}

性能优化

javascript
import express from 'express'
import compression from 'compression'

const app = express()

// Gzip 压缩
app.use(compression())

// 缓存静态资源
app.use(express.static('public', {
  maxAge: '1d',
  etag: true,
  lastModified: true
}))

// 关闭 x-powered-by
app.disable('x-powered-by')

// 解析 JSON 限制大小
app.use(express.json({ limit: '10mb' }))

// 路由路径末尾斜杠重定向
app.set('strict routing', true)

// 使用 Fastify 或其他高性能框架作为替代

面试高频题

Q1: Express 中间件的执行顺序?

答案: 按注册顺序执行,先注册先执行。同步中间件从上到下依次执行,异步中间件通过 next() 控制流程。

Q2: next() 的作用和调用规则?

答案:

  • next() 传递控制权给下一个中间件
  • 不调用 next() 请求会挂起
  • 传递参数 next('route') 跳过当前路由的剩余中间件
  • 传递参数 next(err) 进入错误处理中间件

Q3: 如何处理 404 和全局错误?

答案:

javascript
// 404 - 放在所有路由之后
app.use((req, res) => {
  res.status(404).json({ error: 'Not found' })
})

// 错误处理 - 放在最后
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ error: err.message })
})

Q4: Express 和 Koa 的主要区别?

特性ExpressKoa
中间件线性模型洋葱模型
异步回调/Promiseasync/await
路由内置需安装 koa-router
体积较大轻量
错误处理回调最后一个参数try/catch

Q5: 如何保证 Express 服务稳定性?

答案:

  • 异步操作使用 try/catch 或 Promise 捕获
  • 设置请求超时
  • 实施速率限制
  • 正确处理未捕获异常
  • 实现优雅退出机制

前端面试知识库