您现在的位置是:网站首页 > RESTful API设计文章详情
RESTful API设计
陈川
【
Node.js
】
13094人已围观
7234字
RESTful API设计概述
RESTful API是一种基于HTTP协议的API设计风格,它利用HTTP方法(GET、POST、PUT、DELETE等)来操作资源。这种设计风格强调资源的唯一标识(URI)和统一接口,使得API更加简洁、可扩展和易于理解。
RESTful API的核心原则
资源导向
RESTful API的核心是资源,每个资源都有一个唯一的URI标识。资源可以是任何事物,如用户、订单、商品等。URI应该使用名词而不是动词,并且采用复数形式。
// 不好的设计
GET /getUsers
POST /createUser
// 好的设计
GET /users
POST /users
统一接口
RESTful API使用标准的HTTP方法来表示对资源的操作:
- GET:获取资源
- POST:创建资源
- PUT:更新整个资源
- PATCH:部分更新资源
- DELETE:删除资源
// 获取所有用户
GET /users
// 创建新用户
POST /users
{
"name": "张三",
"email": "zhangsan@example.com"
}
// 更新用户信息
PUT /users/1
{
"name": "张三",
"email": "newemail@example.com"
}
// 删除用户
DELETE /users/1
无状态性
每个请求都应该包含处理该请求所需的所有信息,服务器不应该保存客户端的状态。这使得API更容易扩展和维护。
URI设计规范
资源命名
- 使用名词而不是动词
- 使用复数形式
- 使用小写字母
- 单词间用连字符(-)连接
// 不好的设计
GET /getUserOrders
POST /createNewProduct
// 好的设计
GET /users/1/orders
POST /products
层级关系
使用URI路径表示资源之间的关系:
// 获取用户1的所有订单
GET /users/1/orders
// 获取用户1的订单2的详细信息
GET /users/1/orders/2
查询参数
使用查询参数进行过滤、排序和分页:
// 过滤
GET /products?category=electronics
// 排序
GET /products?sort=price&order=desc
// 分页
GET /products?page=2&limit=10
HTTP状态码
使用合适的HTTP状态码表示请求结果:
- 200 OK - 成功请求
- 201 Created - 资源创建成功
- 204 No Content - 成功但无返回内容
- 400 Bad Request - 请求错误
- 401 Unauthorized - 未授权
- 403 Forbidden - 禁止访问
- 404 Not Found - 资源不存在
- 500 Internal Server Error - 服务器错误
// 成功创建资源
POST /users
Response: 201 Created
// 资源不存在
GET /users/999
Response: 404 Not Found
版本控制
API应该包含版本信息,可以通过以下方式实现:
- URI路径:
GET /v1/users
- 请求头:
GET /users
Headers: Accept: application/vnd.myapi.v1+json
- 查询参数:
GET /users?version=1
数据格式
请求和响应格式
通常使用JSON格式:
// 请求示例
POST /users
{
"name": "李四",
"email": "lisi@example.com"
}
// 响应示例
{
"id": 2,
"name": "李四",
"email": "lisi@example.com",
"createdAt": "2023-05-20T10:00:00Z"
}
错误响应
错误响应应该包含错误详情:
{
"error": {
"code": "invalid_email",
"message": "邮箱格式不正确",
"details": {
"email": "不是有效的邮箱地址"
}
}
}
认证和授权
基本认证
Authorization: Basic base64(username:password)
Bearer Token
Authorization: Bearer <token>
JWT
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
缓存控制
使用HTTP缓存头提高性能:
Cache-Control: max-age=3600
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
限流
防止API被滥用:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1684567890
HATEOAS
超媒体作为应用状态引擎,提供资源间的导航:
{
"id": 1,
"name": "张三",
"links": [
{
"rel": "self",
"href": "/users/1"
},
{
"rel": "orders",
"href": "/users/1/orders"
}
]
}
Node.js实现示例
使用Express实现RESTful API:
const express = require('express');
const app = express();
app.use(express.json());
let users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
];
// 获取所有用户
app.get('/users', (req, res) => {
res.json(users);
});
// 获取单个用户
app.get('/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('/users', (req, res) => {
const user = {
id: users.length + 1,
name: req.body.name
};
users.push(user);
res.status(201).json(user);
});
// 更新用户
app.put('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: '用户不存在' });
user.name = req.body.name;
res.json(user);
});
// 删除用户
app.delete('/users/:id', (req, res) => {
users = users.filter(u => u.id !== parseInt(req.params.id));
res.status(204).end();
});
app.listen(3000, () => console.log('服务器运行在3000端口'));
测试RESTful API
使用Postman或curl测试API:
# 获取所有用户
curl http://localhost:3000/users
# 创建用户
curl -X POST -H "Content-Type: application/json" -d '{"name":"王五"}' http://localhost:3000/users
# 更新用户
curl -X PUT -H "Content-Type: application/json" -d '{"name":"赵六"}' http://localhost:3000/users/1
# 删除用户
curl -X DELETE http://localhost:3000/users/1
文档化API
使用Swagger/OpenAPI规范文档化API:
openapi: 3.0.0
info:
title: 用户API
version: 1.0.0
paths:
/users:
get:
summary: 获取所有用户
responses:
'200':
description: 成功获取用户列表
post:
summary: 创建新用户
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
'201':
description: 用户创建成功
components:
schemas:
User:
type: object
properties:
name:
type: string
required:
- name
性能优化
压缩响应
const compression = require('compression');
app.use(compression());
数据库查询优化
// 不好的做法
app.get('/users', async (req, res) => {
const users = await User.find();
res.json(users);
});
// 好的做法 - 只返回需要的字段
app.get('/users', async (req, res) => {
const users = await User.find().select('name email');
res.json(users);
});
安全性考虑
输入验证
const { body, validationResult } = require('express-validator');
app.post('/users',
body('email').isEmail(),
body('password').isLength({ min: 6 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// 处理请求
}
);
CORS配置
const cors = require('cors');
app.use(cors({
origin: ['https://example.com', 'https://another-domain.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE']
}));
监控和日志
const morgan = require('morgan');
app.use(morgan('combined'));
// 自定义中间件记录请求和响应
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
微服务架构中的RESTful API
在微服务架构中,每个服务通常提供一组RESTful API:
// 用户服务
GET /api/users
// 订单服务
GET /api/orders
// 商品服务
GET /api/products
GraphQL与RESTful API对比
虽然RESTful API是主流,但GraphQL提供了更灵活的数据查询:
// RESTful - 多个请求
GET /users/1
GET /users/1/orders
// GraphQL - 单个请求
query {
user(id: 1) {
name
orders {
id
total
}
}
}
实际项目中的最佳实践
- 使用中间件处理公共逻辑
// 认证中间件
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) return res.status(401).json({ error: '未授权' });
// 验证token
next();
};
app.get('/profile', authenticate, (req, res) => {
// 返回用户资料
});
- 使用路由分离
// routes/users.js
const router = express.Router();
router.get('/', getUserList);
router.post('/', createUser);
module.exports = router;
// app.js
const userRoutes = require('./routes/users');
app.use('/users', userRoutes);
- 使用环境变量配置
// .env
API_PORT=3000
DB_URL=mongodb://localhost:27017/myapp
// app.js
require('dotenv').config();
const port = process.env.API_PORT || 3000;
app.listen(port);