Express 教程
Express 是一个基于 Node.js 平台的极简、灵活的 web 应用开发框架,它提供了一系列强大的特性来帮助创建各种 Web 和移动设备应用。本教程将详细介绍 Express 框架的核心概念和使用方法。
目录
- Express 简介
- 安装与设置
- 基本应用结构
- 路由
- 中间件
- 请求与响应
- 模板引擎
- 错误处理
- 数据库集成
- RESTful API 开发
- 用户认证
- 文件上传
- 项目结构最佳实践
- 部署
- 安全最佳实践
Express 简介
Express 是一个快速、开放、极简的 Node.js web 应用框架,它具有以下特点:
- 简单易学
- 轻量级且灵活
- 丰富的中间件生态系统
- 强大的路由系统
- 支持多种模板引擎
- 易于与数据库集成
安装与设置
安装 Node.js
首先确保已安装 Node.js(推荐 LTS 版本),可以从 Node.js 官网 下载。
创建 Express 项目
- 创建项目文件夹并初始化:
mkdir my-express-app
cd my-express-app
npm init -y
- 安装 Express:
npm install express
- 创建基本应用文件
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}`)
})
- 启动应用:
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
- 解析 Cookieexpress-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()
- 设置 Cookieres.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
- 安装 EJS:
npm install ejs
- 配置 Express 使用 EJS:
app.set('view engine', 'ejs')
app.set('views', path.join(__dirname, 'views'))
- 创建视图文件
views/index.ejs
:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= message %></h1>
</body>
</html>
运行 HTML
- 渲染视图:
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
- 安装 Mongoose:
npm install mongoose
- 连接数据库:
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')
})
- 定义模型:
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)
- 使用模型:
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
- 安装 MySQL 驱动:
npm install mysql2
- 连接数据库:
const mysql = require('mysql2/promise')
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'myapp',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
})
- 执行查询:
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)
- 安装依赖:
npm install jsonwebtoken bcryptjs
- 用户模型:
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
}
- 注册路由:
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)
}
})
- 登录路由:
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)
}
})
- 认证中间件:
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
- 安装 multer:
npm install multer
- 配置 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!')
}
}
})
- 文件上传路由:
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 部署
- 安装 PM2:
npm install pm2 -g
- 启动应用:
pm2 start app.js --name my-app
- 常用命令:
pm2 list # 列出所有应用
pm2 restart my-app # 重启应用
pm2 stop my-app # 停止应用
pm2 delete my-app # 删除应用
pm2 logs # 查看日志
环境变量
使用 .env
文件管理环境变量:
- 安装 dotenv:
npm install dotenv
- 创建
.env
文件:
NODE_ENV=development
PORT=3000
JWT_SECRET=your_jwt_secret
DB_URI=mongodb://localhost:27017/myapp
- 在
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}`)
})
安全最佳实践
- 使用 helmet - 设置安全相关的 HTTP 头
- 防止 XSS - 对用户输入进行转义
- 防止 CSRF - 使用 csurf 中间件
- 限制请求大小 - 防止拒绝服务攻击
- 参数验证 - 使用 express-validator
- 速率限制 - 使用 express-rate-limit
- HTTP 安全头 - 设置适当的头信息
- 会话安全 - 使用安全 Cookie 和 HTTPS
- 依赖更新 - 定期更新依赖项
- 错误处理 - 不要暴露堆栈跟踪
示例安全配置:
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 开发的理想选择。