关于 api-design
api-design 是面向开发者的 API 设计参考技能,涵盖 REST 及其他 API 的可维护性实践。内容从资源命名延伸到安全、协议和交付模式。
主要功能
- 01REST 资源与方法规范
- 02状态码与响应格式
- 03分页、筛选、排序和版本管理
- 04OpenAPI、gRPC 与 tRPC 指南
- 05身份验证、限流与安全
- 06Webhook 签名、重试与幂等性
适用场景
- 01规划新 REST API 的端点
- 02选择分页与版本管理模式
- 03整理身份验证和安全要求
- 04设计可靠的 Webhook 投递
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ travisjneuman/ .claude --skill api-design安装后在 Claude Code 中输入/api-design确认已加载。
详细介绍
API 设计指南
设计对开发者友好且易于维护的 API 的最佳实践。
REST API 原则
资源命名
使用名词,而非动词:
✓ GET /users
✓ GET /users/123
✓ GET /users/123/orders
✗ GET /getUsers
✗ GET /fetchUserById
✗ POST /createNewOrder
使用复数名词:
✓ /users
✓ /orders
✓ /products
✗ /user
✗ /order
层级关系:
/users/{userId}/orders # User's orders
/users/{userId}/orders/{orderId} # Specific order
HTTP 方法
| 方法 | 用途 | 幂等 | 请求正文 |
|---|---|---|---|
| GET | 获取资源 | 是 | 否 |
| POST | 创建资源 | 否 | 是 |
| PUT | 完全替换资源 | 是 | 是 |
| PATCH | 部分更新资源 | 否 | 是 |
| DELETE | 移除资源 | 是 | 否 |
状态码
成功(2xx):
200 OK- 请求成功201 Created- 资源已创建(返回 Location 标头)204 No Content- 请求成功,但无响应正文
客户端错误(4xx):
400 Bad Request- 请求格式错误401 Unauthorized- 需要身份验证403 Forbidden- 无权限404 Not Found- 资源不存在409 Conflict- 状态冲突422 Unprocessable Entity- 验证失败429 Too Many Requests- 请求频率受限
服务端错误(5xx):
500 Internal Server Error- 意外错误503 Service Unavailable- 服务暂时不可用
响应格式
成功响应:
{
"data": {
"id": "123",
"name": "John Doe",
"email": "[email protected]"
}
}
集合响应:
{
"data": [
{ "id": "1", "name": "Item 1" },
{ "id": "2", "name": "Item 2" }
],
"meta": {
"page": 1,
"perPage": 20,
"total": 100,
"totalPages": 5
},
"links": {
"self": "/items?page=1",
"next": "/items?page=2",
"prev": null
}
}
错误响应:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Must be a valid email address"
}
]
}
}
分页
基于偏移量(实现简单,但处理大型数据集时较慢):
GET /users?page=2&perPage=20
GET /users?offset=40&limit=20
基于游标(效率高,推荐使用):
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
响应中包含下一个游标:
{
"data": [...],
"meta": {
"nextCursor": "eyJpZCI6MTQzfQ",
"hasMore": true
}
}
筛选、排序和字段
筛选:
GET /users?status=active
GET /users?created_after=2024-01-01
GET /users?role=admin,moderator
排序:
GET /users?sort=name
GET /users?sort=-created_at # Descending
GET /users?sort=status,-created_at # Multiple fields
字段选择:
GET /users?fields=id,name,email
GET /users?include=orders,profile
版本控制
URL 路径(推荐):
/api/v1/users
/api/v2/users
标头:
Accept: application/vnd.api+json;version=2
OpenAPI 规范
openapi: 3.0.3
info:
title: My API
version: 1.0.0
description: API for managing users
servers:
- url: https://api.example.com/v1
paths:
/users:
get:
summary: List users
tags: [Users]
parameters:
- name: page
in: query
schema:
type: integer
default: 1
responses:
"200":
description: List of users
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/User"
components:
schemas:
User:
type: object
required: [id, email]
properties:
id:
type: string
email:
type: string
format: email
name:
type: string
身份验证
API 密钥(简单,适用于服务间通信):
Authorization: Api-Key YOUR_API_KEY
Bearer 令牌(JWT、OAuth):
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
在 OpenAPI 中包含:
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
速率限制
在响应中包含以下标头:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200
超出限制时,返回 429 Too Many Requests。
安全检查清单
- 仅使用 HTTPS
- 对受保护的路由进行身份验证
- 输入验证
- 输出编码
- 速率限制
- CORS 配置
- URL 中不包含敏感数据
- 审计日志记录
gRPC 和 Protocol Buffers
Proto 定义
syntax = "proto3";
package user.v1;
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (User);
rpc StreamUpdates(StreamRequest) returns (stream UserUpdate);
}
message User {
string id = 1;
string name = 2;
string email = 3;
google.protobuf.Timestamp created_at = 4;
}
message GetUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
}
何时使用 gRPC,何时使用 REST
| 因素 | gRPC | REST |
|---|---|---|
| 性能 | 二进制,速度快 | JSON,易于阅读 |
| 流式传输 | 双向 | 通过 SSE/WebSocket 变通实现 |
| 类型安全 | Proto 生成类型 | OpenAPI + 代码生成 |
| 浏览器 | 需要 gRPC-Web 代理 | 原生支持 |
| 工具链 | Protoc、Buf | Swagger、Postman |
| 适用场景 | 服务间通信、流式传输 | 公共 API、Web 客户端 |
面向 TypeScript 的 tRPC
// server/router.ts
import { router, publicProcedure, protectedProcedure } from './trpc';
import { z } from 'zod';
export const appRouter = router({
user: router({
get: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.user.findUnique({ where: { id: input.id } });
}),
create: protectedProcedure
.input(z.object({
name: z.string().min(1),
email: z.string().email(),
}))
.mutation(async ({ input, ctx }) => {
return db.user.create({ data: { ...input, createdBy: ctx.userId } });
}),
}),
});
export type AppRouter = typeof appRouter;
// client.ts - Full type inference, no codegen
const user = trpc.user.get.useQuery({ id: '123' });
const createUser = trpc.user.create.useMutation();
对于客户端和服务端共享同一代码库的单体仓库全栈 TypeScript 应用,tRPC 是理想选择。
Webhook 设计模式
Webhook 负载
{
"id": "evt_abc123",
"type": "order.completed",
"created_at": "2025-01-15T10:30:00Z",
"data": {
"order_id": "ord_456",
"total": 99.99,
"currency": "USD"
}
}
签名验证
// Sign webhooks with HMAC-SHA256
import crypto from 'crypto';
function signWebhook(payload: string, secret: string): string {
return crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
}
// Verify on receiving end
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expected = signWebhook(payload, secret);
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}
重试策略
Attempt 1: Immediately
Attempt 2: After 1 minute
Attempt 3: After 5 minutes
Attempt 4: After 30 minutes
Attempt 5: After 2 hours
Attempt 6: After 24 hours (final)
Failed webhooks: log, alert, manual retry UI
幂等性
// Include idempotency key in webhook
// Receivers should deduplicate based on event ID
async function handleWebhook(event: WebhookEvent) {
// Check if already processed
const existing = await db.processedEvents.findUnique({
where: { eventId: event.id },
});
if (existing) return { status: 'already_processed' };
// Process and record
await db.$transaction([
processEvent(event),
db.processedEvents.create({ data: { eventId: event.id } }),
]);
}
API 版本控制策略
| 策略 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| URL 路径 | /api/v1/users | 明确,易于路由 | URL 冗余 |
| 查询参数 | /api/users?version=1 | 可选参数 | 容易被忽略 |
| 标头 | Accept: application/vnd.api.v1 | URL 简洁 | 隐蔽,较难测试 |
| 内容协商 | Accept: application/json;v=2 | 基于标准 | 实现复杂 |
建议: 为求简单,采用 URL 路径版本控制。仅在存在破坏性变更时升级主版本。在同一版本中使用增量式、非破坏性变更。
评论
支持 Markdown