agentleFS
I-OnlabsI-Onlabs/claude-code-skills/api-design-patterns/SKILL.md

api-design-patterns

Skill4 starsChanged 15 days agoMITView on GitHub

Use when creating REST APIs, FastAPI endpoints, GraphQL schemas, or designing API architecture. Provides patterns for models, services, routers, and comprehensive testing.

We split this skill into the rules it gives an agent

1 rule. 0 are common, 1 is distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 4 edited versions we read keep these.

  • 1

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Every API endpoint should have these layers:

    line 17

“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

API Design Patterns

When This Skill Activates

  • Creating new API endpoints
  • Designing REST or GraphQL APIs
  • Building FastAPI/Flask/Express services
  • Questions about API architecture
  • CRUD operation implementation

The 5-Layer Pattern

Every API endpoint should have these layers:

┌─────────────────────────────┐
│  1. MODELS (Data Shapes)    │  Pydantic/TypeScript interfaces
├─────────────────────────────┤
│  2. SERVICE (Business Logic)│  CRUD operations, validation
├─────────────────────────────┤
│  3. ROUTER (HTTP Layer)     │  Endpoints, status codes
├─────────────────────────────┤
│  4. TESTS (Quality)         │  Unit + integration tests
├─────────────────────────────┤
│  5. DOCS (OpenAPI)          │  Auto-generated from types
└─────────────────────────────┘

Model Patterns (Pydantic)

from pydantic import BaseModel
from datetime import datetime

# Base - shared fields
class UserBase(BaseModel):
    email: str
    name: str

# Create - for POST requests
class UserCreate(UserBase):
    password: str

# Update - for PATCH requests (all optional)
class UserUpdate(BaseModel):
    email: str | None = None
    name: str | None = None
    password: str | None = None

# Response - what API returns
class UserResponse(UserBase):
    id: int
    created_at: datetime

    class Config:
        from_attributes = True

Service Layer Pattern

class UserService:
    def __init__(self, db: Session):
        self.db = db

    async def create(self, data: UserCreate) -> User:
        user = User(**data.model_dump())
        self.db.add(user)
        await self.db.commit()
        return user

    async def get(self, id: int) -> User | None:
        return await self.db.get(User, id)

    async def list(self, skip: int = 0, limit: int = 100) -> list[User]:
        return await self.db.execute(
            select(User).offset(skip).limit(limit)
        )

    async def update(self, id: int, data: UserUpdate) -> User | None:
        user = await self.get(id)
        if user:
            for key, value in data.model_dump(exclude_unset=True).items():
                setattr(user, key, value)
            await self.db.commit()
        return user

    async def delete(self, id: int) -> bool:
        user = await self.get(id)
        if user:
            await self.db.delete(user)
            await self.db.commit()
            return True
        return False

Router Pattern (FastAPI)

from fastapi import APIRouter, Depends, HTTPException, status

router = APIRouter(prefix="/users", tags=["users"])

@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(data: UserCreate, service: UserService = Depends()):
    return await service.create(data)

@router.get("/{id}", response_model=UserResponse)
async def get_user(id: int, service: UserService = Depends()):
    user = await service.get(id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

@router.get("/", response_model=list[UserResponse])
async def list_users(
    skip: int = 0,
    limit: int = 100,
    service: UserService = Depends()
):
    return await service.list(skip, limit)

@router.patch("/{id}", response_model=UserResponse)
async def update_user(id: int, data: UserUpdate, service: UserService = Depends()):
    user = await service.update(id, data)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(id: int, service: UserService = Depends()):
    if not await service.delete(id):
        raise HTTPException(status_code=404, detail="User not found")

REST Conventions

OperationMethodPathStatus
CreatePOST/users201
Read oneGET/users/{id}200
Read allGET/users200
UpdatePATCH/users/{id}200
ReplacePUT/users/{id}200
DeleteDELETE/users/{id}204

Pagination Pattern

class PaginatedResponse(BaseModel, Generic[T]):
    items: list[T]
    total: int
    page: int
    per_page: int
    pages: int

@router.get("/", response_model=PaginatedResponse[UserResponse])
async def list_users(page: int = 1, per_page: int = 20):
    ...

Error Handling

from fastapi import HTTPException

# Standard error responses
raise HTTPException(status_code=400, detail="Bad request")
raise HTTPException(status_code=401, detail="Unauthorized")
raise HTTPException(status_code=403, detail="Forbidden")
raise HTTPException(status_code=404, detail="Not found")
raise HTTPException(status_code=422, detail="Validation error")
raise HTTPException(status_code=500, detail="Internal server error")

Integration

  • Works with /fastapi-endpoint command
  • Delegates complex work to backend-developer agent
  • Follows python-best-practices skill for code quality

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.