Express 是 Node.js 最流行的 Web 框架,以简洁灵活著称。它提供了丰富的 HTTP 工具,让你快速构建各种 Web 应用。
# 创建项目
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}`)
})
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'))
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()
})
})
// 发送文本
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()
})
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>
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
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 开发更高效