AgentSkillsCN

Api Design

REST API 设计(资源命名、HTTP 方法、状态码)、OpenAPI/Swagger 文档、API 版本控制策略(URL、Header、Query)、GraphQL 架构设计、gRPC proto 定义、分页模式、错误响应格式、速率限制 Header

SKILL.md
--- frontmatter
description: REST API design (resource naming, HTTP methods, status codes), OpenAPI/Swagger documentation, API versioning strategies (URL, header, query), GraphQL schema design, gRPC proto definitions, Pagination patterns, Error response format, Rate limiting headers

Api Design

REST API design (resource naming, HTTP methods, status codes), OpenAPI/Swagger documentation, API versioning strategies (URL, header, query), GraphQL schema design, gRPC proto definitions, Pagination patterns, Error response format, Rate limiting headers

API Design Skill

Design production-ready APIs following RESTful principles, OpenAPI standards, and best practices for versioning, pagination, and error handling.

API Design Skill

Design production-ready APIs following RESTful principles, OpenAPI standards, and best practices for versioning, pagination, and error handling.

Process

Step 1: REST Resource Naming

Follow RESTful naming conventions:

python
# Good: Nouns, plural, lowercase, hyphens
GET    /api/v1/users
GET    /api/v1/users/123
POST   /api/v1/users
PUT    /api/v1/users/123
DELETE /api/v1/users/123

GET    /api/v1/users/123/orders
POST   /api/v1/users/123/orders

# Bad: Verbs, mixed case, underscores
GET    /api/v1/getUsers
GET    /api/v1/GetUserById
POST   /api/v1/create_user

Step 2: HTTP Methods and Status Codes

Use appropriate HTTP methods and status codes:

python
# GET - Retrieve resources
GET /api/v1/users/123
# 200 OK - Success
# 404 Not Found - Resource doesn't exist

# POST - Create resources
POST /api/v1/users
# 201 Created - Resource created
# 400 Bad Request - Invalid input
# 409 Conflict - Resource already exists

# PUT - Update/replace resource
PUT /api/v1/users/123
# 200 OK - Updated
# 204 No Content - Updated (no body)
# 404 Not Found - Resource doesn't exist

# PATCH - Partial update
PATCH /api/v1/users/123
# 200 OK - Updated
# 404 Not Found

# DELETE - Delete resource
DELETE /api/v1/users/123
# 204 No Content - Deleted
# 404 Not Found

Step 3: OpenAPI/Swagger Documentation

Document APIs with OpenAPI:

python
# FastAPI example
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(
    title="My API",
    version="1.0.0",
    description="API documentation",
    openapi_url="/api/v1/openapi.json",
    docs_url="/docs",
    redoc_url="/redoc"
)

class UserCreate(BaseModel):
    email: str
    name: str

class UserResponse(BaseModel):
    id: int
    email: str
    name: str

@app.post("/api/v1/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
    """Create a new user.
    
    - **email**: User email address
    - **name**: User full name
    """
    return UserResponse(id=1, email=user.email, name=user.name)
yaml
# OpenAPI YAML
openapi: 3.0.0
info:
  title: My API
  version: 1.0.0
paths:
  /api/v1/users:
    post:
      summary: Create user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreate'
      responses:
        '201':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'
components:
  schemas:
    UserCreate:
      type: object
      required:
        - email
        - name
      properties:
        email:
          type: string
          format: email
        name:
          type: string

Step 4: API Versioning Strategies

Implement API versioning:

python
# URL Versioning (Recommended)
GET /api/v1/users
GET /api/v2/users

# Header Versioning
GET /api/users
Headers: API-Version: 1.0

# Query Parameter Versioning
GET /api/users?version=1.0

# FastAPI example
from fastapi import APIRouter

v1_router = APIRouter(prefix="/api/v1", tags=["v1"])
v2_router = APIRouter(prefix="/api/v2", tags=["v2"])

@v1_router.get("/users")
async def get_users_v1():
    return {"version": "v1", "users": []}

@v2_router.get("/users")
async def get_users_v2():
    return {"version": "v2", "users": []}

Step 5: Pagination Patterns

Implement pagination:

python
# Offset-based pagination
GET /api/v1/users?page=1&limit=20

# Cursor-based pagination (better for large datasets)
GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20

# Response format
{
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "total_pages": 5,
    "has_next": true,
    "has_prev": false
  }
}

# FastAPI example
from fastapi import Query
from typing import Optional

@app.get("/api/v1/users")
async def get_users(
    page: int = Query(1, ge=1),
    limit: int = Query(20, ge=1, le=100),
    cursor: Optional[str] = None
):
    skip = (page - 1) * limit
    # Fetch users with skip/limit or cursor
    return {
        "data": users,
        "pagination": {
            "page": page,
            "limit": limit,
            "total": total_count,
            "has_next": skip + limit < total_count
        }
    }

Step 6: Error Response Format

Standardize error responses:

python
# Error response format
{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "User with ID 123 not found",
    "details": {
      "user_id": 123
    },
    "request_id": "req-123-456"
  }
}

# FastAPI example
from fastapi import HTTPException

class APIError(Exception):
    def __init__(self, code: str, message: str, status_code: int = 400):
        self.code = code
        self.message = message
        self.status_code = status_code

@app.exception_handler(APIError)
async def api_error_handler(request: Request, exc: APIError):
    return JSONResponse(
        status_code=exc.status_code,
        content={
            "error": {
                "code": exc.code,
                "message": exc.message,
                "request_id": request.state.request_id
            }
        }
    )

Step 7: GraphQL Schema Design

Design GraphQL schemas:

graphql
# schema.graphql
type User {
  id: ID!
  email: String!
  name: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  posts(userId: ID): [Post!]!
}

type Mutation {
  createUser(input: UserInput!): User!
  updateUser(id: ID!, input: UserInput!): User!
  deleteUser(id: ID!): Boolean!
}

input UserInput {
  email: String!
  name: String!
}

Step 8: gRPC Proto Definitions

Define gRPC services:

protobuf
// user.proto
syntax = "proto3";

package api.v1;

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
  rpc CreateUser(CreateUserRequest) returns (User);
  rpc UpdateUser(UpdateUserRequest) returns (User);
  rpc DeleteUser(DeleteUserRequest) returns (Empty);
}

message User {
  int64 id = 1;
  string email = 2;
  string name = 3;
}

message GetUserRequest {
  int64 id = 1;
}

message ListUsersRequest {
  int32 page = 1;
  int32 limit = 2;
}

message ListUsersResponse {
  repeated User users = 1;
  int32 total = 2;
}

message CreateUserRequest {
  string email = 1;
  string name = 2;
}

message UpdateUserRequest {
  int64 id = 1;
  string email = 2;
  string name = 3;
}

message DeleteUserRequest {
  int64 id = 1;
}

message Empty {}

Step 9: Rate Limiting Headers

Implement rate limiting:

python
# Rate limit headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
Retry-After: 60

# FastAPI example
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@app.get("/api/v1/users")
@limiter.limit("100/minute")
async def get_users(request: Request):
    # Add rate limit headers
    return {
        "data": [],
        "rate_limit": {
            "limit": 100,
            "remaining": 95,
            "reset": 1640995200
        }
    }

Step 10: Filtering and Sorting

Implement filtering and sorting:

python
# Query parameters
GET /api/v1/users?status=active&role=admin&sort=name&order=asc

# FastAPI example
from fastapi import Query
from typing import Optional, List

@app.get("/api/v1/users")
async def get_users(
    status: Optional[str] = Query(None),
    role: Optional[str] = Query(None),
    sort: Optional[str] = Query("id"),
    order: Optional[str] = Query("asc", regex="^(asc|desc)$")
):
    # Apply filters and sorting
    return {"users": filtered_users}
python
# Good: Nouns, plural, lowercase, hyphens
GET    /api/v1/users
GET    /api/v1/users/123
POST   /api/v1/users
PUT    /api/v1/users/123
DELETE /api/v1/users/123

GET    /api/v1/users/123/orders
POST   /api/v1/users/123/orders

# Bad: Verbs, mixed case, underscores
GET    /api/v1/getUsers
GET    /api/v1/GetUserById
POST   /api/v1/create_user
python
# GET - Retrieve resources
GET /api/v1/users/123
# 200 OK - Success
# 404 Not Found - Resource doesn't exist

# POST - Create resources
POST /api/v1/users
# 201 Created - Resource created
# 400 Bad Request - Invalid input
# 409 Conflict - Resource already exists

# PUT - Update/replace resource
PUT /api/v1/users/123
# 200 OK - Updated
# 204 No Content - Updated (no body)
# 404 Not Found - Resource doesn't exist

# PATCH - Partial update
PATCH /api/v1/users/123
# 200 OK - Updated
# 404 Not Found

# DELETE - Delete resource
DELETE /api/v1/users/123
# 204 No Content - Deleted
# 404 Not Found
python
# FastAPI example
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(
    title="My API",
    version="1.0.0",
    description="API documentation",
    openapi_url="/api/v1/openapi.json",
    docs_url="/docs",
    redoc_url="/redoc"
)

class UserCreate(BaseModel):
    email: str
    name: str

class UserResponse(BaseModel):
    id: int
    email: str
    name: str

@app.post("/api/v1/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
    """Create a new user.
    
    - **email**: User email address
    - **name**: User full name
    """
    return UserResponse(id=1, email=user.email, name=user.name)
yaml
# OpenAPI YAML
openapi: 3.0.0
info:
  title: My API
  version: 1.0.0
paths:
  /api/v1/users:
    post:
      summary: Create user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreate'
      responses:
        '201':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'
components:
  schemas:
    UserCreate:
      type: object
      required:
        - email
        - name
      properties:
        email:
          type: string
          format: email
        name:
          type: string
python
# URL Versioning (Recommended)
GET /api/v1/users
GET /api/v2/users

# Header Versioning
GET /api/users
Headers: API-Version: 1.0

# Query Parameter Versioning
GET /api/users?version=1.0

# FastAPI example
from fastapi import APIRouter

v1_router = APIRouter(prefix="/api/v1", tags=["v1"])
v2_router = APIRouter(prefix="/api/v2", tags=["v2"])

@v1_router.get("/users")
async def get_users_v1():
    return {"version": "v1", "users": []}

@v2_router.get("/users")
async def get_users_v2():
    return {"version": "v2", "users": []}
python
# Offset-based pagination
GET /api/v1/users?page=1&limit=20

# Cursor-based pagination (better for large datasets)
GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20

# Response format
{
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "total_pages": 5,
    "has_next": true,
    "has_prev": false
  }
}

# FastAPI example
from fastapi import Query
from typing import Optional

@app.get("/api/v1/users")
async def get_users(
    page: int = Query(1, ge=1),
    limit: int = Query(20, ge=1, le=100),
    cursor: Optional[str] = None
):
    skip = (page - 1) * limit
    # Fetch users with skip/limit or cursor
    return {
        "data": users,
        "pagination": {
            "page": page,
            "limit": limit,
            "total": total_count,
            "has_next": skip + limit < total_count
        }
    }
python
# Error response format
{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "User with ID 123 not found",
    "details": {
      "user_id": 123
    },
    "request_id": "req-123-456"
  }
}

# FastAPI example
from fastapi import HTTPException

class APIError(Exception):
    def __init__(self, code: str, message: str, status_code: int = 400):
        self.code = code
        self.message = message
        self.status_code = status_code

@app.exception_handler(APIError)
async def api_error_handler(request: Request, exc: APIError):
    return JSONResponse(
        status_code=exc.status_code,
        content={
            "error": {
                "code": exc.code,
                "message": exc.message,
                "request_id": request.state.request_id
            }
        }
    )
graphql
# schema.graphql
type User {
  id: ID!
  email: String!
  name: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  posts(userId: ID): [Post!]!
}

type Mutation {
  createUser(input: UserInput!): User!
  updateUser(id: ID!, input: UserInput!): User!
  deleteUser(id: ID!): Boolean!
}

input UserInput {
  email: String!
  name: String!
}
protobuf
// user.proto
syntax = "proto3";

package api.v1;

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
  rpc CreateUser(CreateUserRequest) returns (User);
  rpc UpdateUser(UpdateUserRequest) returns (User);
  rpc DeleteUser(DeleteUserRequest) returns (Empty);
}

message User {
  int64 id = 1;
  string email = 2;
  string name = 3;
}

message GetUserRequest {
  int64 id = 1;
}

message ListUsersRequest {
  int32 page = 1;
  int32 limit = 2;
}

message ListUsersResponse {
  repeated User users = 1;
  int32 total = 2;
}

message CreateUserRequest {
  string email = 1;
  string name = 2;
}

message UpdateUserRequest {
  int64 id = 1;
  string email = 2;
  string name = 3;
}

message DeleteUserRequest {
  int64 id = 1;
}

message Empty {}
python
# Rate limit headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
Retry-After: 60

# FastAPI example
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@app.get("/api/v1/users")
@limiter.limit("100/minute")
async def get_users(request: Request):
    # Add rate limit headers
    return {
        "data": [],
        "rate_limit": {
            "limit": 100,
            "remaining": 95,
            "reset": 1640995200
        }
    }
python
# Query parameters
GET /api/v1/users?status=active&role=admin&sort=name&order=asc

# FastAPI example
from fastapi import Query
from typing import Optional, List

@app.get("/api/v1/users")
async def get_users(
    status: Optional[str] = Query(None),
    role: Optional[str] = Query(None),
    sort: Optional[str] = Query("id"),
    order: Optional[str] = Query("asc", regex="^(asc|desc)$")
):
    # Apply filters and sorting
    return {"users": filtered_users}

Best Practices

  • Use nouns for resources, verbs for actions
  • Use plural nouns for collections
  • Use appropriate HTTP methods
  • Return proper status codes
  • Document APIs with OpenAPI
  • Version APIs explicitly
  • Implement pagination for lists
  • Standardize error responses
  • Add rate limiting
  • Use HTTPS in production
  • Validate all inputs
  • Return consistent response formats
  • Include request IDs in responses
  • Support filtering and sorting
  • Use appropriate content types

Anti-Patterns

Anti-PatternFix
Verbs in URLsUse HTTP methods
Inconsistent namingFollow REST conventions
No versioningImplement versioning strategy
No paginationAdd pagination
Inconsistent errorsStandardize error format
No rate limitingImplement rate limiting
Missing documentationAdd OpenAPI docs

Related

  • Knowledge: knowledge/api-design-patterns.json
  • Skill: fastapi-development for FastAPI implementation
  • Skill: dotnet-backend for .NET implementation

Prerequisites

[!IMPORTANT] Requirements:

  • Knowledge: api-design-patterns.json