
api-development
Comprehensive skill for designing, implementing, and documenting RESTful APIs with best practices
提供方 mdaashir|开源
API Development Skill
This skill provides comprehensive guidance for developing robust, secure, and well-documented RESTful APIs.
Capabilities
- Design RESTful API endpoints following best practices
- Implement proper HTTP methods and status codes
- Add authentication and authorization
- Validate input and handle errors gracefully
- Generate API documentation (OpenAPI/Swagger)
- Write API tests
- Implement rate limiting and security measures
When to Use
Use this skill when:
- Creating new API endpoints
- Designing API structure
- Adding security to APIs
- Writing API documentation
- Troubleshooting API issues
API Design Principles
Resource-Based URLs
# ✅ Good - noun-based, resource-oriented
GET /api/users
GET /api/users/{id}
POST /api/users
PUT /api/users/{id}
PATCH /api/users/{id}
DELETE /api/users/{id}
GET /api/users/{id}/orders
POST /api/users/{id}/orders
# ❌ Bad - verb-based
GET /api/getUsers
POST /api/createUser
POST /api/deleteUser
HTTP Methods
- GET: Retrieve resource(s) - Idempotent, Safe
- POST: Create new resource - Not idempotent
- PUT: Update/replace entire resource - Idempotent
- PATCH: Partially update resource - Not always idempotent
- DELETE: Remove resource - Idempotent
HTTP Status Codes
Success Codes (2xx)
200 OK: Successful GET, PUT, PATCH201 Created: Successful POST (resource created)204 No Content: Successful DELETE or update with no response body
Client Error Codes (4xx)
400 Bad Request: Invalid request data401 Unauthorized: Missing or invalid authentication403 Forbidden: Authenticated but not authorized404 Not Found: Resource doesn't exist409 Conflict: Resource state conflict422 Unprocessable Entity: Validation errors429 Too Many Requests: Rate limit exceeded
Server Error Codes (5xx)
500 Internal Server Error: Generic server error502 Bad Gateway: Upstream service error503 Service Unavailable: Temporary unavailability
Implementation Examples
FastAPI (Python)
from fastapi import FastAPI, HTTPException, Depends, status
from fastapi.security import HTTPBearer
from pydantic import BaseModel, EmailStr, Field
from typing import List, Optional
app = FastAPI(title="User API", version="1.0.0")
security = HTTPBearer()
# Data models
class UserCreate(BaseModel):
"""Schema for creating a user."""
name: str = Field(..., min_length=1, max_length=100)
email: EmailStr
age: Optional[int] = Field(None, ge=0, le=150)
class UserResponse(BaseModel):
"""Schema for user response."""
id: str
name: str
email: str
age: Optional[int]
created_at: str
class ErrorResponse(BaseModel):
"""Standard error response."""
error: str
message: str
details: Optional[dict] = None
# Endpoints
@app.post(
"/api/users",
response_model=UserResponse,
status_code=status.HTTP_201_CREATED,
responses={
400: {"model": ErrorResponse, "description": "Bad Request"},
422: {"model": ErrorResponse, "description": "Validation Error"}
}
)
async def create_user(user: UserCreate):
"""Create a new user.
- **name**: User's full name (required)
- **email**: Valid email address (required)
- **age**: User's age (optional, 0-150)
"""
try:
# Validation logic
if await user_exists(user.email):
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User with this email already exists"
)
# Create user
new_user = await db.create_user(user)
return new_user
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
@app.get(
"/api/users/{user_id}",
response_model=UserResponse,
responses={
404: {"model": ErrorResponse, "description": "User Not Found"}
}
)
async def get_user(user_id: str):
"""Retrieve user by ID."""
user = await db.get_user(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with ID {user_id} not found"
)
return user
@app.get("/api/users", response_model=List[UserResponse])
async def list_users(
skip: int = 0,
limit: int = 10,
sort_by: Optional[str] = None
):
"""List users with pagination."""
if limit > 100:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Limit cannot exceed 100"
)
users = await db.get_users(skip=skip, limit=limit, sort_by=sort_by)
return users
@app.put("/api/users/{user_id}", response_model=UserResponse)
async def update_user(user_id: str, user: UserCreate):
"""Update user (replace entire resource)."""
existing_user = await db.get_user(user_id)
if not existing_user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with ID {user_id} not found"
)
updated_user = await db.update_user(user_id, user)
return updated_user
@app.delete("/api/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(user_id: str):
"""Delete user."""
deleted = await db.delete_user(user_id)
if not deleted:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with ID {user_id} not found"
)
return None
Express (TypeScript)
import express, { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
const app = express();
app.use(express.json());
// Validation schemas
const userCreateSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
});
type UserCreate = z.infer<typeof userCreateSchema>;
// Middleware for validation
function validate(schema: z.ZodSchema) {
return (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse(req.body);
next();
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(422).json({
error: 'ValidationError',
message: 'Request validation failed',
details: error.errors,
});
}
next(error);
}
};
}
// Error handler
function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
console.error(err);
if (err.name === 'NotFoundError') {
return res.status(404).json({
error: 'NotFound',
message: err.message,
});
}
res.status(500).json({
error: 'InternalServerError',
message: 'An unexpected error occurred',
});
}
// Routes
app.post('/api/users', validate(userCreateSchema), async (req, res, next) => {
try {
const userData: UserCreate = req.body;
// Check if user exists
const existing = await db.getUserByEmail(userData.email);
if (existing) {
return res.status(409).json({
error: 'Conflict',
message: 'User with this email already exists',
});
}
// Create user
const user = await db.createUser(userData);
res.status(201).json(user);
} catch (error) {
next(error);
}
});
app.get('/api/users/:id', async (req, res, next) => {
try {
const { id } = req.params;
const user = await db.getUser(id);
if (!user) {
return res.status(404).json({
error: 'NotFound',
message: `User with ID ${id} not found`,
});
}
res.json(user);
} catch (error) {
next(error);
}
});
app.get('/api/users', async (req, res, next) => {
try {
const skip = parseInt(req.query.skip as string) || 0;
const limit = Math.min(parseInt(req.query.limit as string) || 10, 100);
const users = await db.getUsers({ skip, limit });
res.json(users);
} catch (error) {
next(error);
}
});
app.use(errorHandler);
Authentication & Authorization
JWT Authentication
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from datetime import datetime, timedelta
security = HTTPBearer()
SECRET_KEY = "your-secret-key" # Use environment variable
ALGORITHM = "HS256"
def create_token(user_id: str, role: str) -> str:
"""Create JWT token."""
payload = {
"sub": user_id,
"role": role,
"exp": datetime.utcnow() + timedelta(hours=24)
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> dict:
"""Verify JWT token."""
try:
token = credentials.credentials
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token has expired"
)
except jwt.JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
def require_role(required_role: str):
"""Dependency to check user role."""
def role_checker(token_data: dict = Depends(verify_token)):
if token_data.get("role") != required_role:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Insufficient permissions"
)
return token_data
return role_checker
# Protected endpoint
@app.get("/api/admin/users", dependencies=[Depends(require_role("admin"))])
async def admin_list_users():
"""Admin-only endpoint."""
return await db.get_all_users()
Rate Limiting
from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.get("/api/users")
@limiter.limit("10/minute")
async def get_users(request: Request):
"""Limited to 10 requests per minute."""
return await db.get_users()
API Documentation
OpenAPI/Swagger
FastAPI automatically generates OpenAPI documentation. Access at:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc - OpenAPI JSON:
http://localhost:8000/openapi.json
Custom Documentation
@app.get(
"/api/users/{user_id}",
summary="Get user by ID",
description="Retrieve detailed information about a specific user",
response_description="User details",
responses={
200: {
"description": "Successful response",
"content": {
"application/json": {
"example": {
"id": "123",
"name": "John Doe",
"email": "john@example.com"
}
}
}
},
404: {
"description": "User not found"
}
}
)
async def get_user(user_id: str):
...
Testing
from fastapi.testclient import TestClient
client = TestClient(app)
def test_create_user():
"""Test user creation."""
response = client.post("/api/users", json={
"name": "Test User",
"email": "test@example.com",
"age": 25
})
assert response.status_code == 201
data = response.json()
assert data["name"] == "Test User"
assert data["email"] == "test@example.com"
assert "id" in data
def test_create_user_validation_error():
"""Test validation error."""
response = client.post("/api/users", json={
"name": "", # Invalid: empty name
"email": "invalid-email" # Invalid: bad email
})
assert response.status_code == 422
def test_get_nonexistent_user():
"""Test 404 response."""
response = client.get("/api/users/nonexistent")
assert response.status_code == 404
Best Practices
- Use proper HTTP methods and status codes
- Validate all input data
- Implement authentication and authorization
- Add rate limiting
- Provide clear error messages
- Version your API (
/api/v1/...) - Use pagination for list endpoints
- Document your API thoroughly
- Test all endpoints
- Handle errors gracefully