API 设计原则:REST 和 GraphQL 最佳实践 - Openclaw Skills
作者:互联网
2026-03-30
什么是 API 设计原则?
此技能为希望使用 Openclaw Skills 构建稳健接口的开发人员提供了权威指南。它提供了一个详细的决策框架,根据数据复杂性和缓存需求等项目要求在 REST 和 GraphQL 之间进行选择。该文档综合了资源建模、HTTP 语义和模式设计的行业最佳实践,确保创建的每个 API 既对开发人员友好又具备前瞻性。
除了简单的理论,该资源还为 FastAPI 等流行框架提供了可操作的代码实现。它解决了关键的生产问题,如分页策略、一致的错误处理以及频率限制等安全措施。通过遵循这些原则,工程团队可以在其服务架构中保持高标准,同时减少技术债务并改进集成工作流。
下载入口:https://github.com/openclaw/skills/tree/main/skills/wpank/api-design-principles
安装与下载
1. ClawHub CLI
从源直接安装技能的最快方式。
npx clawhub@latest install api-design-principles
2. 手动安装
将技能文件夹复制到以下位置之一
全局模式~/.openclaw/skills/
工作区
/skills/
优先级:工作区 > 本地 > 内置
3. 提示词安装
将此提示词复制到 OpenClaw 即可自动安装。
请帮我使用 Clawhub 安装 api-design-principles。如果尚未安装 Clawhub,请先安装(npm i -g clawhub)。
API 设计原则 应用场景
- 使用行业标准模式设计新的 REST 或 GraphQL API。
- 在实施阶段之前对 API 规范进行技术评审。
- 为分布式工程团队建立一套统一的 API 设计标准。
- 重构现有的遗留 API 以提高可用性和一致性。
- 在 REST 和 GraphQL 范式之间迁移服务,以满足不断演进的前端需求。
- 分析数据需求,确定 RESTful 或 GraphQL 架构是否最适合该用例。
- 使用复数名词和清晰的嵌套结构(最多 2 层)定义资源层级。
- 将逻辑操作映射到适当的 HTTP 方法,并为所有响应建立清晰的状态码映射。
- 根据数据更新的大小和频率选择分页策略(偏移量或游标)。
- 实现标准化的错误响应格式,以确保可预测的客户端处理。
- 配置版本控制和频率限制,以管理 API 生命周期并保护系统资源。
API 设计原则 配置指南
要在 Python 环境中应用这些原则,您可以从安装 Openclaw Skills 提供的示例中所使用的核心库开始:
pip install fastapi pydantic aiodataloader
安装完成后,使用提供的 FastAPI 样板和 GraphQL 模式模式来构建您的应用程序控制器和数据模型。
API 设计原则 数据架构与分类体系
该技能将 API 设计数据组织到以下分类中,以确保各端点的一致性:
| 组件 | 组织方法 | 详情 |
|---|---|---|
| 资源命名 | 基于集合 | 使用复数名词(例如 /users, /orders)并避免在 URL 中使用动词。 |
| HTTP 语义 | 状态码映射 | 将结果映射到特定代码,如 201 (Created) 或 422 (Validation Error)。 |
| 分页 | 元数据信封 | 在响应体中包含总数、页面大小和游标。 |
| 错误模式 | 一致的 JSON 对象 | 使用包含代码、消息、详细信息和时间戳的结构化格式。 |
| GraphQL 模式 | Relay 风格模式 | 利用 Connections、Edges 和 PageInfo 进行标准化列表导航。 |
name: api-design-principles
model: reasoning
API Design Principles
WHAT
Design intuitive, scalable REST and GraphQL APIs that developers love. Covers resource modeling, HTTP semantics, pagination, error handling, versioning, and GraphQL schema patterns.
WHEN
- Designing new REST or GraphQL APIs
- Reviewing API specifications before implementation
- Establishing API design standards for teams
- Refactoring APIs for better usability
- Migrating between API paradigms
KEYWORDS
REST, GraphQL, API design, HTTP methods, pagination, error handling, versioning, OpenAPI, HATEOAS, schema design
Decision Framework: REST vs GraphQL
| Choose REST when... | Choose GraphQL when... |
|---|---|
| Simple CRUD operations | Complex nested data requirements |
| Public APIs with broad audience | Mobile apps needing bandwidth optimization |
| Heavy caching requirements | Clients need to specify exact data shape |
| Team is unfamiliar with GraphQL | Aggregating multiple data sources |
| Simple response structures | Rapidly evolving frontend requirements |
REST API Design
Resource Naming Rules
? Plural nouns for collections
GET /api/users
GET /api/orders
GET /api/products
? Avoid verbs (let HTTP methods be the verb)
POST /api/createUser ← Wrong
POST /api/users ← Correct
? Nested resources (max 2 levels)
GET /api/users/{id}/orders
? Avoid deep nesting
GET /api/users/{id}/orders/{orderId}/items/{itemId}/reviews ← Too deep
GET /api/order-items/{id}/reviews ← Better
HTTP Methods and Status Codes
| Method | Purpose | Success | Common Errors |
|---|---|---|---|
| GET | Retrieve | 200 OK | 404 Not Found |
| POST | Create | 201 Created | 400/422 Validation |
| PUT | Replace | 200 OK | 404 Not Found |
| PATCH | Partial update | 200 OK | 404 Not Found |
| DELETE | Remove | 204 No Content | 404/409 Conflict |
Complete Status Code Reference
SUCCESS = {
200: "OK", # GET, PUT, PATCH success
201: "Created", # POST success
204: "No Content", # DELETE success
}
CLIENT_ERROR = {
400: "Bad Request", # Malformed syntax
401: "Unauthorized", # Missing/invalid auth
403: "Forbidden", # Valid auth, no permission
404: "Not Found", # Resource doesn't exist
409: "Conflict", # State conflict (duplicate email)
422: "Unprocessable Entity", # Validation errors
429: "Too Many Requests", # Rate limited
}
SERVER_ERROR = {
500: "Internal Server Error",
503: "Service Unavailable", # Temporary downtime
}
Pagination
Offset-Based (Simple)
GET /api/users?page=2&page_size=20
{
"items": [...],
"page": 2,
"page_size": 20,
"total": 150,
"pages": 8
}
Cursor-Based (For Large Datasets)
GET /api/users?limit=20&cursor=eyJpZCI6MTIzfQ
{
"items": [...],
"next_cursor": "eyJpZCI6MTQzfQ",
"has_more": true
}
Filtering and Sorting
# Filtering
GET /api/users?status=active&role=admin
# Sorting (- prefix for descending)
GET /api/users?sort=-created_at,name
# Search
GET /api/users?search=john
# Field selection
GET /api/users?fields=id,name,email
Error Response Format
Always use consistent structure:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{"field": "email", "message": "Invalid email format"}
],
"timestamp": "2025-10-16T12:00:00Z"
}
}
FastAPI Implementation
from fastapi import FastAPI, Query, Path, HTTPException, status
from pydantic import BaseModel, Field, EmailStr
from typing import Optional, List
from datetime import datetime
app = FastAPI(title="API", version="1.0.0")
# Models
class UserCreate(BaseModel):
email: EmailStr
name: str = Field(..., min_length=1, max_length=100)
class User(BaseModel):
id: str
email: str
name: str
created_at: datetime
class PaginatedResponse(BaseModel):
items: List[User]
total: int
page: int
page_size: int
pages: int
# Endpoints
@app.get("/api/users", response_model=PaginatedResponse)
async def list_users(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
status: Optional[str] = Query(None),
search: Optional[str] = Query(None)
):
"""List users with pagination and filtering."""
total = await count_users(status=status, search=search)
offset = (page - 1) * page_size
users = await fetch_users(limit=page_size, offset=offset, status=status, search=search)
return PaginatedResponse(
items=users,
total=total,
page=page,
page_size=page_size,
pages=(total + page_size - 1) // page_size
)
@app.post("/api/users", response_model=User, status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate):
"""Create new user."""
if await user_exists(user.email):
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail={"code": "EMAIL_EXISTS", "message": "Email already registered"}
)
return await save_user(user)
@app.get("/api/users/{user_id}", response_model=User)
async def get_user(user_id: str = Path(...)):
"""Get user by ID."""
user = await fetch_user(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@app.delete("/api/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(user_id: str):
"""Delete user."""
if not await fetch_user(user_id):
raise HTTPException(status_code=404, detail="User not found")
await remove_user(user_id)
GraphQL API Design
Schema Structure
# Types
type User {
id: ID!
email: String!
name: String!
createdAt: DateTime!
orders(first: Int = 20, after: String): OrderConnection!
}
# Pagination (Relay-style)
type OrderConnection {
edges: [OrderEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type OrderEdge {
node: Order!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# Queries
type Query {
user(id: ID!): User
users(first: Int = 20, after: String, search: String): UserConnection!
}
# Mutations with Input/Payload pattern
input CreateUserInput {
email: String!
name: String!
password: String!
}
type CreateUserPayload {
user: User
errors: [Error!]
}
type Error {
field: String
message: String!
code: String!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
}
DataLoader (Prevent N+1)
from aiodataloader import DataLoader
class UserLoader(DataLoader):
async def batch_load_fn(self, user_ids: List[str]) -> List[Optional[dict]]:
"""Load multiple users in single query."""
users = await fetch_users_by_ids(user_ids)
user_map = {user["id"]: user for user in users}
return [user_map.get(uid) for uid in user_ids]
# In resolver
@user_type.field("orders")
async def resolve_orders(user: dict, info):
loader = info.context["loaders"]["orders_by_user"]
return await loader.load(user["id"])
Query Protection
# Depth limiting
MAX_QUERY_DEPTH = 5
# Complexity limiting
MAX_QUERY_COMPLEXITY = 100
# Timeout
QUERY_TIMEOUT_SECONDS = 10
Versioning Strategies
URL Versioning (Recommended)
/api/v1/users
/api/v2/users
Pros: Clear, easy to route, cacheable Cons: Multiple URLs for same resource
Header Versioning
GET /api/users
Accept: application/vnd.api+json; version=2
Pros: Clean URLs Cons: Less visible, harder to test
Deprecation Strategy
- Add deprecation headers:
Deprecation: true - Document migration path
- Give 6-12 months notice
- Monitor usage before removal
Rate Limiting
Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 742
X-RateLimit-Reset: 1640000000
# When limited:
429 Too Many Requests
Retry-After: 3600
Implementation
from datetime import datetime, timedelta
class RateLimiter:
def __init__(self, calls: int, period: int):
self.calls = calls
self.period = period
self.cache = {}
def check(self, key: str) -> tuple[bool, dict]:
now = datetime.now()
if key not in self.cache:
self.cache[key] = []
# Remove old requests
cutoff = now - timedelta(seconds=self.period)
self.cache[key] = [ts for ts in self.cache[key] if ts > cutoff]
remaining = self.calls - len(self.cache[key])
if remaining <= 0:
return False, {"limit": self.calls, "remaining": 0}
self.cache[key].append(now)
return True, {"limit": self.calls, "remaining": remaining - 1}
Pre-Implementation Checklist
Resources
- Nouns, not verbs
- Plural for collections
- Max 2 levels nesting
HTTP
- Correct method for each action
- Correct status codes
- Idempotent operations are idempotent
Data
- All collections paginated
- Filtering/sorting supported
- Error format consistent
Security
- Authentication defined
- Rate limiting configured
- Input validation on all fields
- HTTPS enforced
Documentation
- OpenAPI spec generated
- All endpoints documented
- Examples provided
NEVER
- Verbs in URLs:
/api/getUser→ use/api/users/{id}with GET - POST for Retrieval: Use GET for safe, idempotent reads
- Inconsistent Errors: Always same error format
- Unbounded Lists: Always paginate collections
- Secrets in URLs: Query params are logged
- Breaking Changes Without Versioning: Plan for evolution from day 1
- Database Schema as API: API should be stable even when schema changes
- Ignoring HTTP Semantics: Status codes and methods have meaning
相关推荐
专题
+ 收藏
+ 收藏
+ 收藏
+ 收藏
+ 收藏
最新数据
相关文章
信号管道:自动化营销情报工具 - Openclaw Skills
技能收益追踪器:监控 Openclaw 技能并实现变现
AI 合规准备就绪度:评估与治理工具 - Openclaw Skills
FOSMVVM ServerRequest 测试生成器:自动化 API 测试 - Openclaw Skills
酒店搜索器:AI 赋能的住宿与位置情报 - Openclaw Skills
Dub 链接 API:程序化链接管理 - Openclaw Skills
IntercomSwap:P2P BTC 与 USDT 跨链兑换 - Openclaw Skills
spotplay:macOS 原生 Spotify 播放控制 - Openclaw Skills
DeepSeek OCR:AI驱动的图像文本识别 - Openclaw Skills
Web Navigator:自动化网页研究与浏览 - Openclaw Skills
AI精选
