rest-api-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseREST API Design
REST API 设计
Overview
概述
Design REST APIs that are intuitive, consistent, and follow industry best practices for resource-oriented architecture.
设计直观、一致且遵循面向资源架构行业最佳实践的REST API。
When to Use
适用场景
- Designing new RESTful APIs
- Creating endpoint structures
- Defining request/response formats
- Implementing API versioning
- Documenting API specifications
- Refactoring existing APIs
- 设计新的RESTful API
- 创建端点结构
- 定义请求/响应格式
- 实现API版本控制
- 编写API规范文档
- 重构现有API
Instructions
操作指南
1. Resource Naming
1. 资源命名
✅ Good Resource Names (Nouns, Plural)
GET /api/users
GET /api/users/123
GET /api/users/123/orders
POST /api/products
DELETE /api/products/456
❌ Bad Resource Names (Verbs, Inconsistent)
GET /api/getUsers
POST /api/createProduct
GET /api/user/123 (inconsistent singular/plural)✅ 良好的资源命名(名词、复数形式)
GET /api/users
GET /api/users/123
GET /api/users/123/orders
POST /api/products
DELETE /api/products/456
❌ 不佳的资源命名(动词、不一致)
GET /api/getUsers
POST /api/createProduct
GET /api/user/123 (单复数形式不一致)2. HTTP Methods & Operations
2. HTTP方法与操作
http
undefinedhttp
undefinedCRUD Operations
CRUD操作
GET /api/users # List all users (Read collection)
GET /api/users/123 # Get specific user (Read single)
POST /api/users # Create new user (Create)
PUT /api/users/123 # Replace user completely (Update)
PATCH /api/users/123 # Partial update user (Partial update)
DELETE /api/users/123 # Delete user (Delete)
GET /api/users # 列出所有用户(读取集合)
GET /api/users/123 # 获取指定用户(读取单个资源)
POST /api/users # 创建新用户(创建)
PUT /api/users/123 # 完整替换用户信息(全量更新)
PATCH /api/users/123 # 部分更新用户信息(增量更新)
DELETE /api/users/123 # 删除用户(删除)
Nested Resources
嵌套资源
GET /api/users/123/orders # Get user's orders
POST /api/users/123/orders # Create order for user
GET /api/users/123/orders/456 # Get specific order
undefinedGET /api/users/123/orders # 获取用户的订单列表
POST /api/users/123/orders # 为用户创建订单
GET /api/users/123/orders/456 # 获取指定订单
undefined3. Request Examples
3. 请求示例
Creating a Resource
创建资源
http
POST /api/users
Content-Type: application/json
{
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
"role": "admin"
}
Response: 201 Created
Location: /api/users/789
{
"id": "789",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
"role": "admin",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}http
POST /api/users
Content-Type: application/json
{
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
"role": "admin"
}
响应: 201 Created
Location: /api/users/789
{
"id": "789",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
"role": "admin",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}Updating a Resource
更新资源
http
PATCH /api/users/789
Content-Type: application/json
{
"firstName": "Jonathan"
}
Response: 200 OK
{
"id": "789",
"email": "john@example.com",
"firstName": "Jonathan",
"lastName": "Doe",
"role": "admin",
"updatedAt": "2025-01-15T11:00:00Z"
}http
PATCH /api/users/789
Content-Type: application/json
{
"firstName": "Jonathan"
}
响应: 200 OK
{
"id": "789",
"email": "john@example.com",
"firstName": "Jonathan",
"lastName": "Doe",
"role": "admin",
"updatedAt": "2025-01-15T11:00:00Z"
}4. Query Parameters
4. 查询参数
http
undefinedhttp
undefinedFiltering
过滤
GET /api/products?category=electronics&inStock=true
GET /api/products?category=electronics&inStock=true
Sorting
排序
GET /api/users?sort=lastName,asc
GET /api/users?sort=lastName,asc
Pagination
分页
GET /api/users?page=2&limit=20
GET /api/users?page=2&limit=20
Field Selection
字段选择
GET /api/users?fields=id,email,firstName
GET /api/users?fields=id,email,firstName
Search
搜索
GET /api/products?q=laptop
GET /api/products?q=laptop
Multiple filters combined
组合多个过滤条件
GET /api/orders?status=pending&customer=123&sort=createdAt,desc&limit=50
undefinedGET /api/orders?status=pending&customer=123&sort=createdAt,desc&limit=50
undefined5. Response Formats
5. 响应格式
Success Response
成功响应
json
{
"data": {
"id": "123",
"email": "user@example.com",
"firstName": "John"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"version": "1.0"
}
}json
{
"data": {
"id": "123",
"email": "user@example.com",
"firstName": "John"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"version": "1.0"
}
}Collection Response with Pagination
带分页的集合响应
json
{
"data": [
{ "id": "1", "name": "Product 1" },
{ "id": "2", "name": "Product 2" }
],
"pagination": {
"page": 2,
"limit": 20,
"total": 145,
"totalPages": 8,
"hasNext": true,
"hasPrev": true
},
"links": {
"self": "/api/products?page=2&limit=20",
"first": "/api/products?page=1&limit=20",
"prev": "/api/products?page=1&limit=20",
"next": "/api/products?page=3&limit=20",
"last": "/api/products?page=8&limit=20"
}
}json
{
"data": [
{ "id": "1", "name": "Product 1" },
{ "id": "2", "name": "Product 2" }
],
"pagination": {
"page": 2,
"limit": 20,
"total": 145,
"totalPages": 8,
"hasNext": true,
"hasPrev": true
},
"links": {
"self": "/api/products?page=2&limit=20",
"first": "/api/products?page=1&limit=20",
"prev": "/api/products?page=1&limit=20",
"next": "/api/products?page=3&limit=20",
"last": "/api/products?page=8&limit=20"
}
}Error Response
错误响应
json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "email",
"message": "Email format is invalid"
},
{
"field": "age",
"message": "Must be at least 18"
}
]
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"requestId": "abc-123-def"
}
}json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "email",
"message": "Email format is invalid"
},
{
"field": "age",
"message": "Must be at least 18"
}
]
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"requestId": "abc-123-def"
}
}6. HTTP Status Codes
6. HTTP状态码
Success:
200 OK - Successful GET, PATCH, DELETE
201 Created - Successful POST (resource created)
204 No Content - Successful DELETE (no response body)
Client Errors:
400 Bad Request - Invalid request format/data
401 Unauthorized - Missing or invalid authentication
403 Forbidden - Authenticated but not authorized
404 Not Found - Resource doesn't exist
409 Conflict - Resource conflict (e.g., duplicate email)
422 Unprocessable - Validation errors
429 Too Many Requests - Rate limit exceeded
Server Errors:
500 Internal Server Error - Generic server error
503 Service Unavailable - Temporary unavailability成功状态:
200 OK - 成功的GET、PATCH、DELETE请求
201 Created - 成功的POST请求(资源已创建)
204 No Content - 成功的DELETE请求(无响应体)
客户端错误:
400 Bad Request - 请求格式/数据无效
401 Unauthorized - 缺少或无效的身份验证
403 Forbidden - 已通过身份验证但无权限
404 Not Found - 资源不存在
409 Conflict - 资源冲突(例如:重复邮箱)
422 Unprocessable - 验证错误
429 Too Many Requests - 请求次数超出限制
服务端错误:
500 Internal Server Error - 通用服务端错误
503 Service Unavailable - 服务暂时不可用7. API Versioning
7. API版本控制
http
undefinedhttp
undefinedURL Path Versioning (Recommended)
URL路径版本控制(推荐)
GET /api/v1/users
GET /api/v2/users
GET /api/v1/users
GET /api/v2/users
Header Versioning
请求头版本控制
GET /api/users
Accept: application/vnd.myapi.v1+json
GET /api/users
Accept: application/vnd.myapi.v1+json
Query Parameter (Not recommended)
查询参数版本控制(不推荐)
GET /api/users?version=1
undefinedGET /api/users?version=1
undefined8. Authentication & Security
8. 身份验证与安全
http
undefinedhttp
undefinedJWT Bearer Token
JWT Bearer令牌
GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
API Key
API密钥
GET /api/users
X-API-Key: your-api-key-here
GET /api/users
X-API-Key: your-api-key-here
Always use HTTPS in production
生产环境中始终使用HTTPS
undefinedundefined9. Rate Limiting Headers
9. 请求频率限制响应头
http
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 995
X-RateLimit-Reset: 1642262400http
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 995
X-RateLimit-Reset: 164226240010. OpenAPI Documentation
10. OpenAPI文档
yaml
openapi: 3.0.0
info:
title: User API
version: 1.0.0
description: User management API
paths:
/users:
get:
summary: List all users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
post:
summary: Create a new user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserInput'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Invalid input
'409':
description: Email already exists
components:
schemas:
User:
type: object
properties:
id:
type: string
email:
type: string
format: email
firstName:
type: string
lastName:
type: string
createdAt:
type: string
format: date-time
UserInput:
type: object
required:
- email
- firstName
- lastName
properties:
email:
type: string
format: email
firstName:
type: string
lastName:
type: stringyaml
openapi: 3.0.0
info:
title: User API
version: 1.0.0
description: User management API
paths:
/users:
get:
summary: List all users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
post:
summary: Create a new user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserInput'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Invalid input
'409':
description: Email already exists
components:
schemas:
User:
type: object
properties:
id:
type: string
email:
type: string
format: email
firstName:
type: string
lastName:
type: string
createdAt:
type: string
format: date-time
UserInput:
type: object
required:
- email
- firstName
- lastName
properties:
email:
type: string
format: email
firstName:
type: string
lastName:
type: stringBest Practices
最佳实践
✅ DO
✅ 建议
- Use nouns for resources, not verbs
- Use plural names for collections
- Be consistent with naming conventions
- Return appropriate HTTP status codes
- Include pagination for collections
- Provide filtering and sorting options
- Version your API
- Document thoroughly with OpenAPI
- Use HTTPS
- Implement rate limiting
- Provide clear error messages
- Use ISO 8601 for dates
- 使用名词而非动词命名资源
- 集合资源使用复数名称
- 保持命名约定一致
- 返回合适的HTTP状态码
- 为集合资源提供分页功能
- 提供过滤和排序选项
- 对API进行版本控制
- 使用OpenAPI进行全面文档编写
- 使用HTTPS
- 实现请求频率限制
- 提供清晰的错误信息
- 日期使用ISO 8601格式
❌ DON'T
❌ 避免
- Use verbs in endpoint names
- Return 200 for errors
- Expose internal IDs unnecessarily
- Over-nest resources (max 2 levels)
- Use inconsistent naming
- Forget authentication
- Return sensitive data
- Break backward compatibility without versioning
- 端点名称使用动词
- 错误请求返回200状态码
- 不必要地暴露内部ID
- 资源过度嵌套(最多2层)
- 命名不一致
- 遗漏身份验证
- 返回敏感数据
- 不进行版本控制就破坏向后兼容性
Complete Example: Express.js
完整示例:Express.js
javascript
const express = require('express');
const app = express();
app.use(express.json());
// List users with pagination
app.get('/api/v1/users', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const offset = (page - 1) * limit;
const users = await User.findAndCountAll({
limit,
offset,
attributes: ['id', 'email', 'firstName', 'lastName']
});
res.json({
data: users.rows,
pagination: {
page,
limit,
total: users.count,
totalPages: Math.ceil(users.count / limit)
}
});
} catch (error) {
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'An error occurred while fetching users'
}
});
}
});
// Get single user
app.get('/api/v1/users/:id', async (req, res) => {
try {
const user = await User.findByPk(req.params.id);
if (!user) {
return res.status(404).json({
error: {
code: 'NOT_FOUND',
message: 'User not found'
}
});
}
res.json({ data: user });
} catch (error) {
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'An error occurred'
}
});
}
});
// Create user
app.post('/api/v1/users', async (req, res) => {
try {
const { email, firstName, lastName } = req.body;
// Validation
if (!email || !firstName || !lastName) {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Missing required fields',
details: [
!email && { field: 'email', message: 'Email is required' },
!firstName && { field: 'firstName', message: 'First name is required' },
!lastName && { field: 'lastName', message: 'Last name is required' }
].filter(Boolean)
}
});
}
const user = await User.create({ email, firstName, lastName });
res.status(201)
.location(`/api/v1/users/${user.id}`)
.json({ data: user });
} catch (error) {
if (error.name === 'SequelizeUniqueConstraintError') {
return res.status(409).json({
error: {
code: 'CONFLICT',
message: 'Email already exists'
}
});
}
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'An error occurred'
}
});
}
});
app.listen(3000);javascript
const express = require('express');
const app = express();
app.use(express.json());
// 带分页的用户列表接口
app.get('/api/v1/users', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const offset = (page - 1) * limit;
const users = await User.findAndCountAll({
limit,
offset,
attributes: ['id', 'email', 'firstName', 'lastName']
});
res.json({
data: users.rows,
pagination: {
page,
limit,
total: users.count,
totalPages: Math.ceil(users.count / limit)
}
});
} catch (error) {
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: '获取用户列表时发生错误'
}
});
}
});
// 获取单个用户接口
app.get('/api/v1/users/:id', async (req, res) => {
try {
const user = await User.findByPk(req.params.id);
if (!user) {
return res.status(404).json({
error: {
code: 'NOT_FOUND',
message: '用户不存在'
}
});
}
res.json({ data: user });
} catch (error) {
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: '发生错误'
}
});
}
});
// 创建用户接口
app.post('/api/v1/users', async (req, res) => {
try {
const { email, firstName, lastName } = req.body;
// 验证
if (!email || !firstName || !lastName) {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: '缺少必填字段',
details: [
!email && { field: 'email', message: '邮箱为必填项' },
!firstName && { field: 'firstName', message: '名字为必填项' },
!lastName && { field: 'lastName', message: '姓氏为必填项' }
].filter(Boolean)
}
});
}
const user = await User.create({ email, firstName, lastName });
res.status(201)
.location(`/api/v1/users/${user.id}`)
.json({ data: user });
} catch (error) {
if (error.name === 'SequelizeUniqueConstraintError') {
return res.status(409).json({
error: {
code: 'CONFLICT',
message: '该邮箱已存在'
}
});
}
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: '发生错误'
}
});
}
});
app.listen(3000);