【Trae+AI】Express框架03:RESTful API设计
Express教程 - 第三部分:RESTful API设计
RESTful API 是一种基于 HTTP 协议设计的 API 规范,其核心是通过统一的接口风格实现客户端与服务器之间的资源交互。它并非强制标准,而是一种 “设计哲学”,旨在让 API 更简洁、可扩展、易于理解和维护。
RESTful API 是一种 “设计规范”,而 Express 是实现这种规范的 “高效工具”。Express 凭借其路由系统、中间件机制和对 HTTP 特性的封装,为开发者提供了简洁的方式来构建符合 RESTful 规范的 API。
一、核心关系:规范与实现工具
- RESTful API:是一套设计接口的 “思想和规则”(如以资源为中心、用 HTTP 方法表达操作、用状态码表示结果等),本身不依赖任何技术栈。
- Express:是 Node.js 生态中的 Web 框架,它通过封装 HTTP 模块、提供路由和中间件等功能,降低了遵循 RESTful 规范的开发成本,让开发者能更专注于业务逻辑而非底层细节。
简单说:RESTful 定义了 “API 应该长什么样”,Express 提供了 “快速造出这种 API 的工具”。
二、Express 如何支撑 RESTful API 的实现?
Express 的核心特性与 RESTful 规范高度契合,具体体现在以下几个方面:
1. 路由系统:精准匹配 “资源 URI + HTTP 方法”
RESTful 要求用 URI 标识资源,用 HTTP 方法表达对资源的操作(如 GET /users 表示获取用户列表)。Express 的路由系统天然支持这种映射:
-
HTTP 方法绑定:Express 提供了
app.get()、app.post()、app.put()、app.delete()等方法,直接对应 RESTful 规范中 GET(查)、POST(增)、PUT(全量改)、DELETE(删)等操作,语义清晰。示例:
// 符合 RESTful 的路由设计 app.get('/users', (req, res) => { /* 获取所有用户(查)*/ }); app.get('/users/:id', (req, res) => { /* 获取单个用户(查)*/ }); app.post('/users', (req, res) => { /* 创建用户(增)*/ }); app.put('/users/:id', (req, res) => { /* 全量更新用户(改)*/ }); app.delete('/users/:id', (req, res) => { /* 删除用户(删)*/ }); -
动态路由参数:RESTful 中用 URI 路径标识单个资源(如
/users/123),Express 通过:param语法支持动态参数(req.params.id),轻松获取资源标识。
2. 中间件:解决 RESTful 所需的共性问题
RESTful API 开发中需要处理的认证、权限校验、请求体解析、错误处理等共性逻辑,可通过 Express 中间件高效实现:
-
请求体解析:RESTful 中创建 / 更新资源时需传递数据(如 POST 请求体),Express 内置的
express.json()或第三方中间件(如body-parser)可直接解析 JSON 或表单数据到req.body,简化参数获取。示例:
const express = require('express'); const app = express(); app.use(express.json()); // 解析 JSON 格式的请求体 app.post('/users', (req, res) => { const newUser = req.body; // 直接获取请求体数据(符合 RESTful 新增资源的参数传递方式) // 保存用户逻辑... res.status(201).json(newUser); // 用 201 状态码表示创建成功(符合 RESTful 状态码规范) }); -
认证与权限:通过自定义中间件验证用户身份(如 JWT 验证),确保只有授权用户能操作资源,符合 RESTful 对资源访问控制的需求。
示例:
// 认证中间件 const auth = (req, res, next) => { const token = req.headers.authorization; if (!token) return res.status(401).json({ error: '未认证' }); // 401 符合 RESTful 状态码 // 验证 token 逻辑... next(); // 验证通过,进入下一个中间件/路由处理函数 }; // 保护路由:只有认证用户能访问 app.get('/users', auth, (req, res) => { /* 获取用户列表 */ }); -
错误处理:Express 的错误处理中间件(参数为
(err, req, res, next))可统一捕获异常,并返回符合 RESTful 规范的错误响应(如 404、500 状态码 + 错误信息)。
3. 响应处理:贴合 RESTful 对 “资源表示” 的要求
RESTful 要求服务器返回资源的 “表示”(如 JSON),并通过 HTTP 状态码和头信息传递元数据。Express 对 res 对象的扩展完美支持这一点:
- 资源表示:
res.json()方法可直接返回 JSON 格式的资源数据(RESTful 最常用的表示格式)。 - 状态码控制:
res.status(code)可灵活设置状态码(如 201 表示创建成功、404 表示资源不存在),符合 RESTful 对状态码的规范。 - 资源定位:创建资源后,可通过
res.location(uri)设置响应头Location,返回新资源的 URI(如res.location('/users/123').status(201).json(...))。
4. 模块化路由:适配 RESTful 的资源分类
RESTful 中不同资源(如用户、订单、商品)的接口应分离管理,Express的 express.Router() 支持路由模块化,可按资源拆分代码,保持项目结构清晰:
示例:
// routes/users.js(用户资源路由模块)
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => { /* 获取所有用户 */ });
router.post('/', (req, res) => { /* 创建用户 */ });
// ...其他用户相关接口
module.exports = router;
// app.js(主应用)
const userRouter = require('./routes/users');
app.use('/users', userRouter); // 挂载用户路由,所有接口自动带上 /users 前缀
三、RESTful API示例教程
1. 创建RESTful API基础教程
advanced\01-restful-api-basics.js
// 高级教程1: RESTful API设计基础
const express = require('express');
const app = express();
const PORT = 3007;
// 中间件配置
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
console.log('🌐 RESTful API设计教程');
console.log('================================');
// 模拟数据库(内存存储)
let users = [
{ id: 1, name: '张三', email: 'zhangsan@example.com', age: 25 },
{ id: 2, name: '李四', email: 'lisi@example.com', age: 30 },
{ id: 3, name: '王五', email: 'wangwu@example.com', age: 28 }
];
let nextId = 4;
// 🎯 第一部分:什么是RESTful API?
app.get('/api/intro', (req, res) => {
res.json({
title: 'RESTful API设计原则',
principles: [
{
principle: '统一接口 (Uniform Interface)',
description: '使用标准的HTTP方法和状态码',
example: 'GET /users, POST /users, PUT /users/1'
},
{
principle: '无状态 (Stateless)',
description: '每个请求都包含所有必要信息',
example: '不使用session,使用token认证'
},
{
principle: '资源导向 (Resource-Based)',
description: '一切皆资源,使用名词而不是动词',
example: '/users 而不是 /getUsers'
},
{
principle: '可缓存 (Cacheable)',
description: '响应应该标明是否可缓存',
example: '使用Cache-Control头'
}
]
});
});
// 🎯 第二部分:HTTP方法详解
app.get('/api/http-methods', (req, res) => {
res.json({
methods: [
{
method: 'GET',
purpose: '获取资源',
example: 'GET /api/users - 获取用户列表',
idempotent: true,
safe: true
},
{
method: 'POST',
purpose: '创建新资源',
example: 'POST /api/users - 创建新用户',
idempotent: false,
safe: false
},
{
method: 'PUT',
purpose: '更新整个资源',
example: 'PUT /api/users/1 - 更新用户1的所有信息',
idempotent: true,
safe: false
},
{
method: 'PATCH',
purpose: '部分更新资源',
example: 'PATCH /api/users/1 - 只更新用户1的部分信息',
idempotent: false,
safe: false
},
{
method: 'DELETE',
purpose: '删除资源',
example: 'DELETE /api/users/1 - 删除用户1',
idempotent: true,
safe: false
}
]
});
});
// 🎯 第三部分:完整的用户API实现
// 1. 获取所有用户 (GET /api/users)
app.get('/api/users', (req, res) => {
console.log('📋 获取用户列表');
// 分页参数
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
// 过滤参数
const nameFilter = req.query.name;
let filteredUsers = users;
if (nameFilter) {
filteredUsers = users.filter(user =>
user.name.toLowerCase().includes(nameFilter.toLowerCase())
);
}
// 分页结果
const paginatedUsers = filteredUsers.slice(startIndex, endIndex);
res.json({
success: true,
data: paginatedUsers,
pagination: {
currentPage: page,
totalPages: Math.ceil(filteredUsers.length / limit),
totalItems: filteredUsers.length,
hasNext: endIndex < filteredUsers.length,
hasPrev: page > 1
},
filters: {
name: nameFilter || '无'
}
});
});
// 2. 获取单个用户 (GET /api/users/:id)
app.get('/api/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
const user = users.find(u => u.id === userId);
if (!user) {
return res.status(404).json({
success: false,
message: '用户不存在',
errorCode: 'USER_NOT_FOUND'
});
}
console.log(`👤 获取用户详情: ${user.name}`);
res.json({
success: true,
data: user
});
});
// 3. 创建新用户 (POST /api/users)
app.post('/api/users', (req, res) => {
console.log('➕ 创建新用户');
const { name, email, age } = req.body;
// 数据验证
if (!name || !email) {
return res.status(400).json({
success: false,
message: '姓名和邮箱是必填项',
errorCode: 'VALIDATION_ERROR'
});
}
// 检查邮箱是否已存在
const existingUser = users.find(u => u.email === email);
if (existingUser) {
return res.status(409).json({
success: false,
message: '邮箱已存在',
errorCode: 'EMAIL_EXISTS'
});
}
const newUser = {
id: nextId++,
name,
email,
age: age || null,
createdAt: new Date().toISOString()
};
users.push(newUser);
res.status(201).json({
success: true,
message: '用户创建成功',
data: newUser
});
});
// 4. 更新用户信息 (PUT /api/users/:id)
app.put('/api/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
const userIndex = users.findIndex(u => u.id === userId);
if (userIndex === -1) {
return res.status(404).json({
success: false,
message: '用户不存在',
errorCode: 'USER_NOT_FOUND'
});
}
const { name, email, age } = req.body;
// 数据验证
if (!name || !email) {
return res.status(400).json({
success: false,
message: '姓名和邮箱是必填项',
errorCode: 'VALIDATION_ERROR'
});
}
// 检查邮箱是否被其他用户使用
const emailUser = users.find(u => u.email === email && u.id !== userId);
if (emailUser) {
return res.status(409).json({
success: false,
message: '邮箱已被其他用户使用',
errorCode: 'EMAIL_IN_USE'
});
}
users[userIndex] = {
...users[userIndex],
name,
email,
age: age || null,
updatedAt: new Date().toISOString()
};
console.log(`✏️ 更新用户: ${users[userIndex].name}`);
res.json({
success: true,
message: '用户更新成功',
data: users[userIndex]
});
});
// 5. 删除用户 (DELETE /api/users/:id)
app.delete('/api/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
const userIndex = users.findIndex(u => u.id === userId);
if (userIndex === -1) {
return res.status(404).json({
success: false,
message: '用户不存在',
errorCode: 'USER_NOT_FOUND'
});
}
const deletedUser = users.splice(userIndex, 1)[0];
console.log(`🗑️ 删除用户: ${deletedUser.name}`);
res.json({
success: true,
message: '用户删除成功',
data: deletedUser
});
});
// 🎯 第四部分:高级功能 - 搜索和统计
app.get('/api/users/search/:keyword', (req, res) => {
const keyword = req.params.keyword.toLowerCase();
const results = users.filter(user =>
user.name.toLowerCase().includes(keyword) ||
user.email.toLowerCase().includes(keyword)
);
res.json({
success: true,
keyword: keyword,
results: results,
count: results.length
});
});
app.get('/api/stats', (req, res) => {
const stats = {
totalUsers: users.length,
averageAge: users.reduce((sum, user) => sum + (user.age || 0), 0) / users.filter(u => u.age).length,
createdToday: users.filter(u => {
const today = new Date().toDateString();
const userDate = new Date(u.createdAt || new Date()).toDateString();
return today === userDate;
}).length
};
res.json({
success: true,
data: stats
});
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error('❌ API错误:', err);
res.status(500).json({
success: false,
message: '服务器内部错误',
errorCode: 'INTERNAL_ERROR'
});
});
// 404处理
app.use('/api/*', (req, res) => {
res.status(404).json({
success: false,
message: 'API接口不存在',
errorCode: 'ENDPOINT_NOT_FOUND'
});
});
app.listen(PORT, () => {
console.log(`🚀 RESTful API教程运行在 http://localhost:${PORT}`);
console.log('');
console.log('📚 学习端点:');
console.log(' GET /api/intro - RESTful原则介绍');
console.log(' GET /api/http-methods - HTTP方法详解');
console.log('');
console.log('🔧 实践端点:');
console.log(' GET /api/users - 获取用户列表');
console.log(' GET /api/users/1 - 获取用户详情');
console.log(' POST /api/users - 创建新用户');
console.log(' PUT /api/users/1 - 更新用户信息');
console.log(' DELETE /api/users/1 - 删除用户');
console.log(' GET /api/stats - 统计信息');
console.log('');
console.log('💡 使用Postman或curl测试这些API端点');
});
2. 创建API测试指南
API_TEST_GUIDE.md
# RESTful API测试指南
## 🧪 测试工具推荐
### 1. Postman(图形界面,推荐新手)
- 下载地址:https://www.postman.com/downloads/
- 优点:可视化操作,容易上手
### 2. curl(命令行工具)
- 系统自带,无需安装
- 优点:快速测试,适合自动化
## 🔍 API端点测试示例
### 获取所有用户
```bash
# curl命令
curl http://localhost:3007/api/users
# 带分页参数
curl "http://localhost:3007/api/users?page=1&limit=5"
# 带过滤参数
curl "http://localhost:3007/api/users?name=张三"
```
### 获取单个用户
```bash
curl http://localhost:3007/api/users/1
```
### 创建新用户
```bash
curl -X POST http://localhost:3007/api/users \
-H "Content-Type: application/json" \
-d '{"name":"赵六","email":"zhaoliu@example.com","age":35}'
```
### 更新用户信息
```bash
curl -X PUT http://localhost:3007/api/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"张三丰","email":"zhangsanfeng@example.com","age":40}'
```
### 删除用户
```bash
curl -X DELETE http://localhost:3007/api/users/1
```
## 📊 HTTP状态码说明
| 状态码 | 含义 | 示例 |
|--------|------|------|
| 200 | 成功 | GET、PUT、DELETE操作成功 |
| 201 | 创建成功 | POST创建新资源 |
| 400 | 请求错误 | 数据验证失败 |
| 404 | 资源不存在 | 用户ID不存在 |
| 409 | 冲突 | 邮箱已存在 |
| 500 | 服务器错误 | 代码bug |
## 🎯 最佳实践
### 请求头设置
```bash
# 设置Content-Type
-H "Content-Type: application/json"
# 设置认证令牌
-H "Authorization: Bearer your-token-here"
```
### 响应格式统一
所有API返回统一格式:
```json
{
"success": true/false,
"message": "操作结果描述",
"data": { /* 实际数据 */ },
"errorCode": "错误代码(可选)"
}
```
### 错误处理
- 客户端错误(4xx):用户输入问题
- 服务器错误(5xx):代码bug或系统问题
3. 创建RESTful设计原则详解
advanced\02-restful-principles.js
// 高级教程2: RESTful设计原则详解
const express = require('express');
const app = express();
const PORT = 3008;
app.use(express.json());
console.log('📖 RESTful设计原则详解');
console.log('================================');
// 🎯 原则1:资源命名规范
app.get('/api/principles/naming', (req, res) => {
res.json({
title: '资源命名规范',
rules: [
{
rule: '使用名词而不是动词',
good: '/users',
bad: '/getUsers'
},
{
rule: '使用复数形式',
good: '/users',
bad: '/user'
},
{
rule: '使用连字符而不是下划线',
good: '/user-profiles',
bad: '/user_profiles'
},
{
rule: '避免大写字母',
good: '/user-profiles',
bad: '/UserProfiles'
}
]
});
});
// 🎯 原则2:HTTP方法正确使用
app.get('/api/principles/methods', (req, res) => {
res.json({
title: 'HTTP方法使用规范',
examples: [
{
operation: '获取资源列表',
method: 'GET',
url: '/api/users',
description: '获取所有用户'
},
{
operation: '获取单个资源',
method: 'GET',
url: '/api/users/123',
description: '获取ID为123的用户'
},
{
operation: '创建新资源',
method: 'POST',
url: '/api/users',
description: '创建新用户'
},
{
operation: '更新整个资源',
method: 'PUT',
url: '/api/users/123',
description: '完全更新用户123的信息'
},
{
operation: '部分更新资源',
method: 'PATCH',
url: '/api/users/123',
description: '只更新用户123的部分信息'
},
{
operation: '删除资源',
method: 'DELETE',
url: '/api/users/123',
description: '删除用户123'
}
]
});
});
// 🎯 原则3:状态码正确使用
app.get('/api/principles/status-codes', (req, res) => {
res.json({
title: 'HTTP状态码使用规范',
codes: [
{
code: 200,
meaning: 'OK - 请求成功',
usage: 'GET、PUT、DELETE操作成功'
},
{
code: 201,
meaning: 'Created - 创建成功',
usage: 'POST创建新资源成功'
},
{
code: 204,
meaning: 'No Content - 无内容',
usage: 'DELETE成功,但不需要返回内容'
},
{
code: 400,
meaning: 'Bad Request - 请求错误',
usage: '客户端数据验证失败'
},
{
code: 401,
meaning: 'Unauthorized - 未授权',
usage: '需要登录认证'
},
{
code: 403,
meaning: 'Forbidden - 禁止访问',
usage: '有权限但被拒绝'
},
{
code: 404,
meaning: 'Not Found - 资源不存在',
usage: '请求的资源不存在'
},
{
code: 409,
meaning: 'Conflict - 冲突',
usage: '资源状态冲突(如邮箱已存在)'
},
{
code: 500,
meaning: 'Internal Server Error - 服务器错误',
usage: '服务器端代码错误'
}
]
});
});
app.listen(PORT, () => {
console.log(`📚 RESTful原则教程运行在 http://localhost:${PORT}`);
console.log('访问以下端点学习设计原则:');
console.log(' /api/principles/naming - 资源命名规范');
console.log(' /api/principles/methods - HTTP方法规范');
console.log(' /api/principles/status-codes - 状态码规范');
});
4. 创建学习实践项目
examples\02-todo-api.js
// 实践案例2: Todo列表API
const express = require('express');
const app = express();
const PORT = 3009;
app.use(express.json());
console.log('✅ Todo列表API实践项目');
console.log('================================');
// 模拟数据库
let todos = [
{ id: 1, title: '学习Express', completed: false, createdAt: new Date() },
{ id: 2, title: '练习RESTful API', completed: true, createdAt: new Date() },
{ id: 3, title: '构建Todo应用', completed: false, createdAt: new Date() }
];
let nextId = 4;
// 🎯 完整的Todo API实现
// GET /api/todos - 获取所有待办事项
app.get('/api/todos', (req, res) => {
const { completed, search } = req.query;
let filteredTodos = todos;
// 过滤完成状态
if (completed !== undefined) {
const isCompleted = completed === 'true';
filteredTodos = filteredTodos.filter(todo => todo.completed === isCompleted);
}
// 搜索功能
if (search) {
filteredTodos = filteredTodos.filter(todo =>
todo.title.toLowerCase().includes(search.toLowerCase())
);
}
res.json({
success: true,
data: filteredTodos,
total: filteredTodos.length,
completed: filteredTodos.filter(t => t.completed).length,
pending: filteredTodos.filter(t => !t.completed).length
});
});
// GET /api/todos/:id - 获取单个待办事项
app.get('/api/todos/:id', (req, res) => {
const todo = todos.find(t => t.id === parseInt(req.params.id));
if (!todo) {
return res.status(404).json({
success: false,
message: '待办事项不存在'
});
}
res.json({ success: true, data: todo });
});
// POST /api/todos - 创建新待办事项
app.post('/api/todos', (req, res) => {
const { title } = req.body;
if (!title || title.trim() === '') {
return res.status(400).json({
success: false,
message: '标题不能为空'
});
}
const newTodo = {
id: nextId++,
title: title.trim(),
completed: false,
createdAt: new Date()
};
todos.push(newTodo);
res.status(201).json({
success: true,
message: '待办事项创建成功',
data: newTodo
});
});
// PUT /api/todos/:id - 更新待办事项
app.put('/api/todos/:id', (req, res) => {
const todoIndex = todos.findIndex(t => t.id === parseInt(req.params.id));
if (todoIndex === -1) {
return res.status(404).json({
success: false,
message: '待办事项不存在'
});
}
const { title, completed } = req.body;
if (title !== undefined && title.trim() === '') {
return res.status(400).json({
success: false,
message: '标题不能为空'
});
}
todos[todoIndex] = {
...todos[todoIndex],
title: title !== undefined ? title.trim() : todos[todoIndex].title,
completed: completed !== undefined ? completed : todos[todoIndex].completed,
updatedAt: new Date()
};
res.json({
success: true,
message: '待办事项更新成功',
data: todos[todoIndex]
});
});
// DELETE /api/todos/:id - 删除待办事项
app.delete('/api/todos/:id', (req, res) => {
const todoIndex = todos.findIndex(t => t.id === parseInt(req.params.id));
if (todoIndex === -1) {
return res.status(404).json({
success: false,
message: '待办事项不存在'
});
}
const deletedTodo = todos.splice(todoIndex, 1)[0];
res.json({
success: true,
message: '待办事项删除成功',
data: deletedTodo
});
});
// 🎯 批量操作
// PATCH /api/todos/batch-complete - 批量完成
app.patch('/api/todos/batch-complete', (req, res) => {
const { ids } = req.body;
if (!Array.isArray(ids)) {
return res.status(400).json({
success: false,
message: 'ids必须是数组'
});
}
let updatedCount = 0;
todos.forEach(todo => {
if (ids.includes(todo.id)) {
todo.completed = true;
todo.updatedAt = new Date();
updatedCount++;
}
});
res.json({
success: true,
message: `批量完成了 ${updatedCount} 个待办事项`
});
});
// GET /api/todos/stats - 统计信息
app.get('/api/todos/stats', (req, res) => {
const stats = {
total: todos.length,
completed: todos.filter(t => t.completed).length,
pending: todos.filter(t => !t.completed).length,
completionRate: (todos.filter(t => t.completed).length / todos.length * 100).toFixed(1)
};
res.json({ success: true, data: stats });
});
app.listen(PORT, () => {
console.log(`🚀 Todo API运行在 http://localhost:${PORT}`);
console.log('');
console.log('📋 可用端点:');
console.log(' GET /api/todos - 获取所有待办事项');
console.log(' GET /api/todos/:id - 获取单个待办事项');
console.log(' POST /api/todos - 创建新待办事项');
console.log(' PUT /api/todos/:id - 更新待办事项');
console.log(' DELETE /api/todos/:id - 删除待办事项');
console.log(' PATCH /api/todos/batch-complete - 批量完成');
console.log(' GET /api/todos/stats - 统计信息');
console.log('');
console.log('💡 使用Postman测试这些API,构建完整的前后端应用!');
});
🚀 现在开始学习RESTful API!
第一步:运行基础教程
node advanced/01-restful-api-basics.js
第二步:学习设计原则
node advanced/02-restful-principles.js
第三步:实践项目
node examples/02-todo-api.js
第四步:使用Postman测试
- 下载并安装Postman
- 测试所有API端点
- 观察请求和响应格式
学习重点:
- 理解RESTful原则:资源导向、统一接口
- 掌握HTTP方法:GET、POST、PUT、DELETE的正确使用
- 学会状态码:正确返回HTTP状态码
- 实践API设计:从需求到实现的完整流程
完成这部分后,你将能够设计出专业的Web API!
🐳Express框架就先整理这么多,就到这里,就到这里🐳
更多推荐
所有评论(0)