Express框架

Express 是 Node.js 最流行的 Web 框架,以简洁灵活著称。它提供了丰富的 HTTP 工具,让你快速构建各种 Web 应用。

Express 简介

安装与初始化

# 创建项目
mkdir dongba-express
cd dongba-express
npm init -y

# 安装 Express
npm install express

# 安装开发依赖
npm install -D nodemon

最简应用

const express = require('express')

const app = express()
const PORT = 3000

// 定义路由
app.get('/', (req, res) => {
  res.send('东巴文欢迎你')
})

// 启动服务器
app.listen(PORT, () => {
  console.log(`服务器运行在 http://localhost:${PORT}`)
})

Express 应用结构

dongba-express/
├── app.js              # 应用入口
├── config/             # 配置文件
│   └── index.js
├── routes/             # 路由
│   ├── index.js
│   ├── users.js
│   └── products.js
├── controllers/        # 控制器
│   └── userController.js
├── models/             # 数据模型
│   └── User.js
├── middlewares/        # 中间件
│   └── auth.js
├── utils/              # 工具函数
│   └── helpers.js
└── public/             # 静态文件
    ├── css/
    ├── js/
    └── images/

路由系统

基本路由

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

// GET 请求
app.get('/', (req, res) => {
  res.send('GET 请求')
})

// POST 请求
app.post('/', (req, res) => {
  res.send('POST 请求')
})

// PUT 请求
app.put('/user', (req, res) => {
  res.send('PUT 请求')
})

// DELETE 请求
app.delete('/user', (req, res) => {
  res.send('DELETE 请求')
})

// 匹配所有 HTTP 方法
app.all('/all', (req, res) => {
  res.send(`所有方法: ${req.method}`)
})

路由参数

// 动态路由参数
app.get('/users/:id', (req, res) => {
  res.json({
    userId: req.params.id
  })
})

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

// 可选参数
app.get('/books/:id?', (req, res) => {
  const id = req.params.id || 'default'
  res.json({ bookId: id })
})

// 正则匹配
app.get('/file/:name(\\w+\\.\\w+)', (req, res) => {
  res.json({ filename: req.params.name })
})

查询参数

app.get('/search', (req, res) => {
  const { q, page, limit } = req.query
  
  res.json({
    query: q,
    page: page || 1,
    limit: limit || 10
  })
})

// 访问: /search?q=东巴文&page=2&limit=20
// 返回: { query: "东巴文", page: "2", limit: "20" }

路由模块化

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

// 中间件(仅对当前路由生效)
router.use((req, res, next) => {
  console.log('用户路由中间件')
  next()
})

// 用户列表
router.get('/', (req, res) => {
  res.json({ users: ['东巴文', '用户A', '用户B'] })
})

// 用户详情
router.get('/:id', (req, res) => {
  res.json({ userId: req.params.id })
})

// 创建用户
router.post('/', (req, res) => {
  res.status(201).json({ message: '用户创建成功' })
})

// 更新用户
router.put('/:id', (req, res) => {
  res.json({ message: `用户 ${req.params.id} 更新成功` })
})

// 删除用户
router.delete('/:id', (req, res) => {
  res.json({ message: `用户 ${req.params.id} 删除成功` })
})

module.exports = router

// app.js
const express = require('express')
const usersRouter = require('./routes/users')

const app = express()

app.use('/users', usersRouter)

app.listen(3000)

中间件

中间件是 Express 的核心概念,它是一个函数,可以访问请求对象、响应对象和下一个中间件。

中间件执行顺序

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

// 全局中间件
app.use((req, res, next) => {
  console.log('第一个中间件')
  next()
})

app.use((req, res, next) => {
  console.log('第二个中间件')
  next()
})

// 路由中间件
app.get('/', (req, res) => {
  console.log('路由处理')
  res.send('东巴文')
})

// 执行顺序: 第一个中间件 -> 第二个中间件 -> 路由处理

应用级中间件

// 日志中间件
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()
})

// 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') {
    res.sendStatus(200)
  } else {
    next()
  }
})

// 请求体解析
app.use(express.json())                    // JSON 格式
app.use(express.urlencoded({ extended: true }))  // URL 编码
app.use(express.raw())                     // 原始格式
app.use(express.text())                    // 文本格式

路由级中间件

// 单个路由的中间件
const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization
  
  if (!token) {
    return res.status(401).json({ error: '未授权' })
  }
  
  // 验证 token
  if (token === 'dongba-secret') {
    req.user = { id: 1, name: '东巴文' }
    next()
  } else {
    res.status(403).json({ error: '无效的 token' })
  }
}

// 应用到单个路由
app.get('/profile', authMiddleware, (req, res) => {
  res.json({ user: req.user })
})

// 应用到多个路由
app.get('/admin', authMiddleware, (req, res) => {
  res.json({ message: '管理页面' })
})

// 多个中间件
app.get('/secure', 
  authMiddleware,
  (req, res, next) => {
    console.log('第二个中间件')
    next()
  },
  (req, res) => {
    res.json({ message: '安全页面' })
  }
)

错误处理中间件

// 同步错误
app.get('/error', (req, res) => {
  throw new Error('同步错误')
})

// 异步错误需要传递给 next
app.get('/async-error', async (req, res, next) => {
  try {
    await someAsyncOperation()
    res.json({ success: true })
  } catch (err) {
    next(err)
  }
})

// 错误处理中间件(4个参数)
app.use((err, req, res, next) => {
  console.error('错误:', err.message)
  
  res.status(err.status || 500).json({
    error: {
      message: err.message,
      stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
    }
  })
})

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

// 使用自定义错误
app.get('/custom-error', (req, res, next) => {
  next(new AppError('资源未找到', 404))
})

第三方中间件

const express = require('express')
const morgan = require('morgan')           // 日志
const helmet = require('helmet')           // 安全头
const compression = require('compression') // 压缩
const rateLimit = require('express-rate-limit') // 限流

const app = express()

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

// 日志中间件
app.use(morgan('combined'))

// 压缩响应
app.use(compression())

// 限流
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 分钟
  max: 100,                   // 最多 100 次请求
  message: '请求过于频繁,请稍后再试'
})
app.use('/api', limiter)

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

请求与响应

请求对象(req)

app.get('/request-info', (req, res) => {
  res.json({
    method: req.method,           // 请求方法
    url: req.url,                 // 请求 URL
    path: req.path,               // 路径
    query: req.query,             // 查询参数
    params: req.params,           // 路由参数
    headers: req.headers,         // 请求头
    body: req.body,               // 请求体
    ip: req.ip,                   // 客户端 IP
    protocol: req.protocol,       // 协议
    hostname: req.hostname,       // 主机名
    cookies: req.cookies,         // Cookie(需要 cookie-parser)
    signedCookies: req.signedCookies  // 签名 Cookie
  })
})

// 获取特定请求头
app.get('/headers', (req, res) => {
  const userAgent = req.get('User-Agent')
  const contentType = req.get('Content-Type')
  
  res.json({ userAgent, contentType })
})

// 检查请求类型
app.post('/check', (req, res) => {
  if (req.is('json')) {
    res.json({ type: 'JSON 请求' })
  } else if (req.is('urlencoded')) {
    res.json({ type: '表单请求' })
  } else {
    res.json({ type: '其他请求' })
  }
})

// 接受的内容类型
app.get('/accepts', (req, res) => {
  res.json({
    accepts: req.accepts(),           // ['html', 'json', ...]
    acceptsEncoding: req.acceptsEncodings(),
    acceptsCharset: req.acceptsCharsets(),
    acceptsLanguage: req.acceptsLanguages()
  })
})

响应对象(res)

// 发送文本
app.get('/text', (req, res) => {
  res.send('东巴文文本响应')
})

// 发送 JSON
app.get('/json', (req, res) => {
  res.json({ name: '东巴文', version: '1.0.0' })
})

// 发送 JSONP
app.get('/jsonp', (req, res) => {
  res.jsonp({ name: '东巴文' })
})

// 设置状态码
app.get('/status', (req, res) => {
  res.status(201).json({ created: true })
})

// 设置响应头
app.get('/headers', (req, res) => {
  res.set('X-Custom-Header', '东巴文')
  res.set({
    'X-App-Version': '1.0.0',
    'X-Author': 'Dongba'
  })
  res.send('响应头已设置')
})

// 重定向
app.get('/redirect', (req, res) => {
  res.redirect('/target')
})

app.get('/redirect-external', (req, res) => {
  res.redirect(301, 'https://db-w.cn')
})

// 发送文件
app.get('/download', (req, res) => {
  res.download('./files/document.pdf')
})

app.get('/attachment', (req, res) => {
  res.attachment('data.json')
  res.send(JSON.stringify({ data: '东巴文' }))
})

// 发送文件内容
app.get('/sendfile', (req, res) => {
  res.sendFile('/path/to/file.html', { root: __dirname })
})

// Cookie 操作
app.get('/cookie', (req, res) => {
  res.cookie('name', '东巴文', {
    maxAge: 900000,
    httpOnly: true,
    secure: true,
    signed: true
  })
  res.clearCookie('old-cookie')
  res.send('Cookie 已设置')
})

// 链式调用
app.get('/chain', (req, res) => {
  res
    .status(200)
    .set('X-Custom', 'Dongba')
    .json({ message: '链式响应' })
})

// 结束响应
app.get('/end', (req, res) => {
  res.write('第一部分\n')
  res.write('第二部分\n')
  res.end()
})

模板引擎

使用 EJS

npm install ejs
const express = require('express')
const app = express()

// 设置模板引擎
app.set('view engine', 'ejs')
app.set('views', './views')

// 渲染模板
app.get('/', (req, res) => {
  res.render('index', {
    title: '东巴文',
    message: '欢迎来到东巴文'
  })
})

// 渲染带布局的页面
app.get('/page', (req, res) => {
  res.render('page', {
    title: '页面标题',
    layout: 'layouts/main'
  })
})
<!-- views/index.ejs -->
<!DOCTYPE html>
<html>
<head>
  <title><%= title %></title>
</head>
<body>
  <h1><%= message %></h1>
  <ul>
    <% items.forEach(item => { %>
      <li><%= item %></li>
    <% }) %>
  </ul>
</body>
</html>

使用 Pug

npm install pug
app.set('view engine', 'pug')
app.set('views', './views')

app.get('/pug', (req, res) => {
  res.render('index', {
    title: '东巴文',
    message: 'Pug 模板'
  })
})
//- views/index.pug
doctype html
html
  head
    title= title
  body
    h1= message
    ul
      each item in items
        li= item

RESTful API 实战

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

app.use(express.json())

// 模拟数据库
let users = [
  { id: 1, name: '东巴文', email: 'dongba@example.com' },
  { id: 2, name: '用户A', email: 'userA@example.com' }
]

let nextId = 3

// 获取所有用户
app.get('/api/users', (req, res) => {
  const { page = 1, limit = 10, name } = req.query
  
  let result = users
  
  // 过滤
  if (name) {
    result = result.filter(u => 
      u.name.includes(name)
    )
  }
  
  // 分页
  const start = (page - 1) * limit
  const end = start + parseInt(limit)
  const paginated = result.slice(start, end)
  
  res.json({
    data: paginated,
    total: result.length,
    page: parseInt(page),
    limit: parseInt(limit)
  })
})

// 获取单个用户
app.get('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id))
  
  if (!user) {
    return res.status(404).json({ error: '用户不存在' })
  }
  
  res.json(user)
})

// 创建用户
app.post('/api/users', (req, res) => {
  const { name, email } = req.body
  
  if (!name || !email) {
    return res.status(400).json({ error: '姓名和邮箱不能为空' })
  }
  
  const newUser = {
    id: nextId++,
    name,
    email,
    createdAt: new Date()
  }
  
  users.push(newUser)
  
  res.status(201).json(newUser)
})

// 更新用户
app.put('/api/users/:id', (req, res) => {
  const index = users.findIndex(u => u.id === parseInt(req.params.id))
  
  if (index === -1) {
    return res.status(404).json({ error: '用户不存在' })
  }
  
  const { name, email } = req.body
  
  users[index] = {
    ...users[index],
    name: name || users[index].name,
    email: email || users[index].email,
    updatedAt: new Date()
  }
  
  res.json(users[index])
})

// 删除用户
app.delete('/api/users/:id', (req, res) => {
  const index = users.findIndex(u => u.id === parseInt(req.params.id))
  
  if (index === -1) {
    return res.status(404).json({ error: '用户不存在' })
  }
  
  users.splice(index, 1)
  
  res.status(204).send()
})

app.listen(3000, () => {
  console.log('API 服务器运行在 http://localhost:3000')
})

文件上传

const express = require('express')
const multer = require('multer')
const path = require('path')
const fs = require('fs')

const app = express()

// 确保上传目录存在
const uploadDir = './uploads'
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir, { recursive: true })
}

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

// 文件过滤
const fileFilter = (req, file, cb) => {
  const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
  
  if (allowedTypes.includes(file.mimetype)) {
    cb(null, true)
  } else {
    cb(new Error('不支持的文件类型'), false)
  }
}

const upload = multer({
  storage,
  fileFilter,
  limits: {
    fileSize: 5 * 1024 * 1024  // 5MB
  }
})

// 单文件上传
app.post('/upload/single', upload.single('file'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: '请选择文件' })
  }
  
  res.json({
    message: '上传成功',
    file: {
      fieldname: req.file.fieldname,
      originalname: req.file.originalname,
      size: req.file.size,
      mimetype: req.file.mimetype,
      path: req.file.path
    }
  })
})

// 多文件上传
app.post('/upload/multiple', upload.array('files', 5), (req, res) => {
  res.json({
    message: '上传成功',
    count: req.files.length,
    files: req.files.map(f => ({
      originalname: f.originalname,
      size: f.size
    }))
  })
})

// 多字段上传
app.post('/upload/fields', upload.fields([
  { name: 'avatar', maxCount: 1 },
  { name: 'documents', maxCount: 5 }
]), (req, res) => {
  res.json({
    avatar: req.files.avatar?.[0],
    documents: req.files.documents
  })
})

// 错误处理
app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    if (err.code === 'LIMIT_FILE_SIZE') {
      return res.status(400).json({ error: '文件太大' })
    }
    if (err.code === 'LIMIT_FILE_COUNT') {
      return res.status(400).json({ error: '文件数量超限' })
    }
  }
  next(err)
})

app.listen(3000)

安全最佳实践

const express = require('express')
const helmet = require('helmet')
const rateLimit = require('express-rate-limit')
const mongoSanitize = require('express-mongo-sanitize')
const xss = require('xss-clean')
const hpp = require('hpp')
const cors = require('cors')

const app = express()

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

// CORS 配置
app.use(cors({
  origin: ['https://db-w.cn'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true
}))

// 限流
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: { error: '请求过于频繁' }
})
app.use('/api', limiter)

// 数据清洗
app.use(mongoSanitize())
app.use(xss())
app.use(hpp())

// 禁用敏感信息
app.disable('x-powered-by')

// 输入验证
const { body, validationResult } = require('express-validator')

app.post('/api/users', 
  body('email').isEmail().normalizeEmail(),
  body('password').isLength({ min: 8 }),
  body('name').trim().escape(),
  (req, res) => {
    const errors = validationResult(req)
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() })
    }
    // 处理请求
  }
)

app.listen(3000)

下一步

掌握 Express 后,你可以继续学习:


东巴文(db-w.cn)—— 让 Express 开发更高效