Express 教程

Express 是一个基于 Node.js 平台的极简、灵活的 web 应用开发框架,它提供了一系列强大的特性来帮助创建各种 Web 和移动设备应用。本教程将详细介绍 Express 框架的核心概念和使用方法。

目录

  1. Express 简介
  2. 安装与设置
  3. 基本应用结构
  4. 路由
  5. 中间件
  6. 请求与响应
  7. 模板引擎
  8. 错误处理
  9. 数据库集成
  10. RESTful API 开发
  11. 用户认证
  12. 文件上传
  13. 项目结构最佳实践
  14. 部署
  15. 安全最佳实践

Express 简介

Express 是一个快速、开放、极简的 Node.js web 应用框架,它具有以下特点:

  • 简单易学
  • 轻量级且灵活
  • 丰富的中间件生态系统
  • 强大的路由系统
  • 支持多种模板引擎
  • 易于与数据库集成

安装与设置

安装 Node.js

首先确保已安装 Node.js(推荐 LTS 版本),可以从 Node.js 官网 下载。

创建 Express 项目

  1. 创建项目文件夹并初始化:
mkdir my-express-app
cd my-express-app
npm init -y
  1. 安装 Express:
npm install express
  1. 创建基本应用文件 app.js
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
  1. 启动应用:
node app.js

访问 http://localhost:3000 即可看到 "Hello World!"。

使用 nodemon 自动重启

开发时推荐使用 nodemon 自动重启应用:

npm install --save-dev nodemon

然后在 package.json 中添加脚本:

"scripts": {
  "start": "node app.js",
  "dev": "nodemon app.js"
}

之后可以使用 npm run dev 启动开发服务器。

基本应用结构

一个典型的 Express 应用结构如下:

my-express-app/
├── node_modules/     # 依赖模块
├── public/           # 静态文件
│   ├── css/
│   ├── js/
│   └── images/
├── views/            # 视图/模板文件
├── routes/           # 路由文件
│   ├── index.js
│   └── users.js
├── models/           # 数据库模型
├── controllers/      # 控制器
├── app.js            # 主应用文件
├── package.json
└── .env              # 环境变量

路由

路由是指确定应用如何响应客户端对特定端点的请求。

基本路由

// GET 方法路由
app.get('/', (req, res) => {
  res.send('GET request to the homepage')
})

// POST 方法路由
app.post('/', (req, res) => {
  res.send('POST request to the homepage')
})

// 所有 HTTP 方法的路由
app.all('/secret', (req, res, next) => {
  console.log('Accessing the secret section...')
  next() // 传递给下一个处理程序
})

路由路径

路由路径可以是字符串、字符串模式或正则表达式:

// 字符串路径
app.get('/about', (req, res) => {
  res.send('about')
})

// 字符串模式路径
app.get('/ab?cd', (req, res) => {  // 匹配 acd 和 abcd
  res.send('ab?cd')
})

// 正则表达式路径
app.get(/.*fly$/, (req, res) => {  // 匹配 butterfly, dragonfly 等
  res.send('/.*fly$/')
})

路由参数

app.get('/users/:userId/books/:bookId', (req, res) => {
  res.send(req.params)  // { userId: '34', bookId: '8989' }
})

路由模块化

将路由分离到单独的文件中:

routes/users.js:

const express = require('express')
const router = express.Router()

// 用户主页路由
router.get('/', (req, res) => {
  res.send('用户主页')
})

// 用户详情路由
router.get('/:userId', (req, res) => {
  res.send(`用户ID: ${req.params.userId}`)
})

module.exports = router

app.js:

const usersRouter = require('./routes/users')

// ...

app.use('/users', usersRouter)

中间件

中间件函数能够访问请求对象 (req)、响应对象 (res) 以及应用程序的请求/响应循环中的下一个中间件函数。

应用级中间件

// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use((req, res, next) => {
  console.log('Time:', Date.now())
  next()
})

// 挂载至 /user/:id 的中间件
app.use('/user/:id', (req, res, next) => {
  console.log('Request Type:', req.method)
  next()
})

// 多个中间件函数
app.use('/user/:id', (req, res, next) => {
  console.log('Request URL:', req.originalUrl)
  next()
}, (req, res, next) => {
  console.log('Request Type:', req.method)
  next()
})

路由级中间件

const express = require('express')
const app = express()
const router = express.Router()

// 没有挂载路径的中间件,通过该路由的每个请求都会执行该中间件
router.use((req, res, next) => {
  console.log('Time:', Date.now())
  next()
})

// 验证用户中间件
router.use('/user/:id', (req, res, next) => {
  console.log('Request URL:', req.originalUrl)
  next()
}, (req, res, next) => {
  console.log('Request Type:', req.method)
  next()
})

// 路由处理
router.get('/user/:id', (req, res, next) => {
  res.send('USER')
})

app.use('/', router)

错误处理中间件

app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

内置中间件

Express 内置了一些常用中间件:

  • express.static - 托管静态文件
  • express.json - 解析 JSON 请求体
  • express.urlencoded - 解析 URL 编码的请求体
app.use(express.static('public'))
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

第三方中间件

常用第三方中间件:

  • morgan - HTTP 请求日志记录
  • helmet - 安全相关 HTTP 头设置
  • cors - 跨域资源共享
  • cookie-parser - 解析 Cookie
  • express-session - 会话管理

安装并使用:

npm install morgan helmet cors cookie-parser express-session
const morgan = require('morgan')
const helmet = require('helmet')
const cors = require('cors')
const cookieParser = require('cookie-parser')
const session = require('express-session')

app.use(morgan('dev'))
app.use(helmet())
app.use(cors())
app.use(cookieParser())
app.use(session({
  secret: 'your secret key',
  resave: false,
  saveUninitialized: true
}))

请求与响应

请求对象 (req)

常用属性:

  • req.params - 路由参数
  • req.query - URL 查询参数
  • req.body - 请求体(需要 body-parser 中间件)
  • req.cookies - Cookies(需要 cookie-parser 中间件)
  • req.headers - HTTP 头
  • req.ip - 客户端 IP 地址
  • req.path - 请求路径
  • req.protocol - 请求协议
  • req.method - 请求方法

响应对象 (res)

常用方法:

  • res.send() - 发送各种类型的响应
  • res.json() - 发送 JSON 响应
  • res.render() - 渲染视图模板
  • res.redirect() - 重定向请求
  • res.status() - 设置 HTTP 状态码
  • res.sendFile() - 发送文件作为响应
  • res.download() - 提示下载文件
  • res.set() - 设置响应头
  • res.cookie() - 设置 Cookie
  • res.clearCookie() - 清除 Cookie

示例:

app.get('/example', (req, res) => {
  // 设置状态码和内容类型
  res.status(200).type('text/plain')

  // 设置 Cookie
  res.cookie('username', 'john', { maxAge: 900000, httpOnly: true })

  // 发送响应
  res.send('Hello World')
})

app.get('/download', (req, res) => {
  res.download('/path/to/file.pdf')
})

app.get('/redirect', (req, res) => {
  res.redirect('/new-location')
})

模板引擎

Express 支持多种模板引擎,如 EJS、Pug、Handlebars 等。

使用 EJS

  1. 安装 EJS:
npm install ejs
  1. 配置 Express 使用 EJS:
app.set('view engine', 'ejs')
app.set('views', path.join(__dirname, 'views'))
  1. 创建视图文件 views/index.ejs
<!DOCTYPE html>
<html>
<head>
    <title><%= title %></title>
</head>
<body>
    <h1><%= message %></h1>
</body>
</html>

运行 HTML

  1. 渲染视图:
app.get('/', (req, res) => {
  res.render('index', { title: 'Express', message: 'Hello World!' })
})

EJS 常用语法

  • <%= value %> - 输出转义后的值
  • <%- html %> - 输出原始 HTML
  • <% code %> - 执行 JavaScript 代码
  • <% include('partial') %> - 包含其他模板

错误处理

基本错误处理

app.get('/error', (req, res, next) => {
  try {
    throw new Error('Something went wrong!')
  } catch (err) {
    next(err)
  }
})

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

异步错误处理

对于异步代码,必须将错误传递给 next()

app.get('/async-error', (req, res, next) => {
  fs.readFile('/file-does-not-exist', (err, data) => {
    if (err) {
      next(err) // 将错误传递给 Express
    } else {
      res.send(data)
    }
  })
})

使用 Promise 和 async/await

app.get('/promise-error', (req, res, next) => {
  someAsyncFunction()
    .then(result => res.send(result))
    .catch(next) // 错误将被传递给错误处理中间件
})

app.get('/async-await-error', async (req, res, next) => {
  try {
    const result = await someAsyncFunction()
    res.send(result)
  } catch (err) {
    next(err)
  }
})

数据库集成

使用 MongoDB 和 Mongoose

  1. 安装 Mongoose:
npm install mongoose
  1. 连接数据库:
const mongoose = require('mongoose')

mongoose.connect('mongodb://localhost:27017/myapp', {
  useNewUrlParser: true,
  useUnifiedTopology: true
})

const db = mongoose.connection
db.on('error', console.error.bind(console, 'connection error:'))
db.once('open', () => {
  console.log('Connected to MongoDB')
})
  1. 定义模型:
const userSchema = new mongoose.Schema({
  name: String,
  email: { type: String, required: true, unique: true },
  age: { type: Number, min: 18 }
})

const User = mongoose.model('User', userSchema)
  1. 使用模型:
app.post('/users', async (req, res) => {
  try {
    const user = new User(req.body)
    await user.save()
    res.status(201).send(user)
  } catch (err) {
    res.status(400).send(err)
  }
})

app.get('/users', async (req, res) => {
  try {
    const users = await User.find()
    res.send(users)
  } catch (err) {
    res.status(500).send()
  }
})

使用 MySQL

  1. 安装 MySQL 驱动:
npm install mysql2
  1. 连接数据库:
const mysql = require('mysql2/promise')

const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'myapp',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
})
  1. 执行查询:
app.get('/users', async (req, res) => {
  try {
    const [rows] = await pool.query('SELECT * FROM users')
    res.json(rows)
  } catch (err) {
    console.error(err)
    res.status(500).send('Database error')
  }
})

RESTful API 开发

基本 RESTful API

const express = require('express')
const router = express.Router()
const User = require('../models/user')

// 获取所有用户
router.get('/', async (req, res) => {
  try {
    const users = await User.find()
    res.json(users)
  } catch (err) {
    res.status(500).json({ message: err.message })
  }
})

// 获取单个用户
router.get('/:id', getUser, (req, res) => {
  res.json(res.user)
})

// 创建用户
router.post('/', async (req, res) => {
  const user = new User({
    name: req.body.name,
    email: req.body.email
  })

  try {
    const newUser = await user.save()
    res.status(201).json(newUser)
  } catch (err) {
    res.status(400).json({ message: err.message })
  }
})

// 更新用户
router.patch('/:id', getUser, async (req, res) => {
  if (req.body.name != null) {
    res.user.name = req.body.name
  }
  if (req.body.email != null) {
    res.user.email = req.body.email
  }

  try {
    const updatedUser = await res.user.save()
    res.json(updatedUser)
  } catch (err) {
    res.status(400).json({ message: err.message })
  }
})

// 删除用户
router.delete('/:id', getUser, async (req, res) => {
  try {
    await res.user.remove()
    res.json({ message: 'Deleted User' })
  } catch (err) {
    res.status(500).json({ message: err.message })
  }
})

// 中间件 - 获取用户
async function getUser(req, res, next) {
  let user
  try {
    user = await User.findById(req.params.id)
    if (user == null) {
      return res.status(404).json({ message: 'Cannot find user' })
    }
  } catch (err) {
    return res.status(500).json({ message: err.message })
  }

  res.user = user
  next()
}

module.exports = router

API 版本控制

// app.js
app.use('/api/v1', require('./routes/api/v1/users'))
app.use('/api/v2', require('./routes/api/v2/users'))

用户认证

使用 JWT (JSON Web Token)

  1. 安装依赖:
npm install jsonwebtoken bcryptjs
  1. 用户模型:
const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true }
})

// 密码哈希
userSchema.pre('save', async function(next) {
  if (this.isModified('password')) {
    this.password = await bcrypt.hash(this.password, 8)
  }
  next()
})

// 生成 JWT
userSchema.methods.generateAuthToken = function() {
  const token = jwt.sign({ _id: this._id }, process.env.JWT_SECRET, { expiresIn: '1h' })
  return token
}
  1. 注册路由:
router.post('/register', async (req, res) => {
  try {
    const user = new User(req.body)
    await user.save()
    const token = user.generateAuthToken()
    res.status(201).send({ user, token })
  } catch (err) {
    res.status(400).send(err)
  }
})
  1. 登录路由:
router.post('/login', async (req, res) => {
  try {
    const user = await User.findOne({ username: req.body.username })
    if (!user) {
      return res.status(401).send({ error: 'Login failed' })
    }

    const isMatch = await bcrypt.compare(req.body.password, user.password)
    if (!isMatch) {
      return res.status(401).send({ error: 'Login failed' })
    }

    const token = user.generateAuthToken()
    res.send({ user, token })
  } catch (err) {
    res.status(400).send(err)
  }
})
  1. 认证中间件:
const auth = async (req, res, next) => {
  try {
    const token = req.header('Authorization').replace('Bearer ', '')
    const decoded = jwt.verify(token, process.env.JWT_SECRET)
    const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })

    if (!user) {
      throw new Error()
    }

    req.token = token
    req.user = user
    next()
  } catch (err) {
    res.status(401).send({ error: 'Please authenticate' })
  }
}

// 使用中间件保护路由
router.get('/profile', auth, (req, res) => {
  res.send(req.user)
})

文件上传

使用 multer

  1. 安装 multer:
npm install multer
  1. 配置 multer:
const multer = require('multer')
const path = require('path')

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/')
  },
  filename: (req, file, cb) => {
    cb(null, `${Date.now()}-${file.originalname}`)
  }
})

const upload = multer({ 
  storage: storage,
  limits: { fileSize: 1024 * 1024 * 5 }, // 5MB
  fileFilter: (req, file, cb) => {
    const filetypes = /jpeg|jpg|png|gif/
    const extname = filetypes.test(path.extname(file.originalname).toLowerCase())
    const mimetype = filetypes.test(file.mimetype)

    if (mimetype && extname) {
      return cb(null, true)
    } else {
      cb('Error: Images Only!')
    }
  }
})
  1. 文件上传路由:
app.post('/upload', upload.single('avatar'), (req, res) => {
  if (!req.file) {
    return res.status(400).send('No files were uploaded.')
  }
  res.send({
    message: 'File uploaded successfully',
    file: req.file
  })
})

// 多文件上传
app.post('/upload-multiple', upload.array('photos', 5), (req, res) => {
  if (!req.files) {
    return res.status(400).send('No files were uploaded.')
  }
  res.send({
    message: 'Files uploaded successfully',
    files: req.files
  })
})

项目结构最佳实践

一个良好的 Express 项目结构:

my-express-app/
├── config/            # 配置文件
│   ├── db.js          # 数据库配置
│   └── passport.js    # 认证配置
├── controllers/       # 控制器
│   ├── auth.js
│   └── users.js
├── models/            # 数据模型
│   └── user.js
├── routes/            # 路由
│   ├── auth.js
│   └── users.js
├── middlewares/       # 自定义中间件
│   └── auth.js
├── services/          # 业务逻辑
│   └── userService.js
├── utils/             # 工具函数
│   ├── logger.js
│   └── errorHandler.js
├── public/            # 静态文件
├── views/             # 视图文件
├── tests/             # 测试文件
├── app.js             # 应用入口
└── package.json

部署

使用 PM2 部署

  1. 安装 PM2:
npm install pm2 -g
  1. 启动应用:
pm2 start app.js --name my-app
  1. 常用命令:
pm2 list             # 列出所有应用
pm2 restart my-app   # 重启应用
pm2 stop my-app      # 停止应用
pm2 delete my-app    # 删除应用
pm2 logs             # 查看日志

环境变量

使用 .env 文件管理环境变量:

  1. 安装 dotenv:
npm install dotenv
  1. 创建 .env 文件:
NODE_ENV=development
PORT=3000
JWT_SECRET=your_jwt_secret
DB_URI=mongodb://localhost:27017/myapp
  1. app.js 中加载:
require('dotenv').config()

const port = process.env.PORT || 3000
// ...
app.listen(port, () => {
  console.log(`Server running in ${process.env.NODE_ENV} mode on port ${port}`)
})

安全最佳实践

  1. 使用 helmet - 设置安全相关的 HTTP 头
  2. 防止 XSS - 对用户输入进行转义
  3. 防止 CSRF - 使用 csurf 中间件
  4. 限制请求大小 - 防止拒绝服务攻击
  5. 参数验证 - 使用 express-validator
  6. 速率限制 - 使用 express-rate-limit
  7. HTTP 安全头 - 设置适当的头信息
  8. 会话安全 - 使用安全 Cookie 和 HTTPS
  9. 依赖更新 - 定期更新依赖项
  10. 错误处理 - 不要暴露堆栈跟踪

示例安全配置:

const helmet = require('helmet')
const rateLimit = require('express-rate-limit')
const { body, validationResult } = require('express-validator')

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

// 速率限制
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100 // 每个IP限制100个请求
})
app.use(limiter)

// 请求体大小限制
app.use(express.json({ limit: '10kb' }))

// 参数验证
app.post('/user', 
  body('email').isEmail(),
  body('password').isLength({ min: 5 }),
  (req, res) => {
    const errors = validationResult(req)
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() })
    }
    // 处理请求
  }
)

总结

Express.js 是一个灵活、轻量级的 Node.js web 应用框架,它提供了构建 web 应用和 API 所需的核心功能,同时保持简单和可扩展。通过本教程,你已经学习了 Express 的核心概念,包括路由、中间件、模板引擎、数据库集成、用户认证和部署等。

要构建更复杂的应用,可以考虑:

  • 使用 TypeScript 增强代码质量
  • 采用更高级的架构模式(如 MVC、DDD)
  • 实现单元测试和集成测试
  • 使用 Docker 容器化应用
  • 探索 Express 生态系统中的其他中间件和工具

Express 的灵活性和丰富的生态系统使其成为 Node.js web 开发的理想选择。









results matching ""

    No results matching ""