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 的主要区别?
| 特性 | Express | Koa |
|---|---|---|
| 中间件 | 线性模型 | 洋葱模型 |
| 异步 | 回调/Promise | async/await |
| 路由 | 内置 | 需安装 koa-router |
| 体积 | 较大 | 轻量 |
| 错误处理 | 回调最后一个参数 | try/catch |
Q5: 如何保证 Express 服务稳定性?
答案:
- 异步操作使用 try/catch 或 Promise 捕获
- 设置请求超时
- 实施速率限制
- 正确处理未捕获异常
- 实现优雅退出机制