| name | api-design |
| description | Choose the API protocol (REST / GraphQL / gRPC) by traffic shape and design resources, versioning, and async patterns. Use when adding a new API surface, designing a service boundary, or when clients complain about over/under-fetching. Not for the schema/error envelope details (use api-contract) or per-resource access control (use authorization). |
| license | MIT |
API Design
Purpose
Pick the right protocol for each API surface by traffic characteristics — not by fashion — and model resources, versioning, and async operations so the contract can evolve without breaking clients.
Universal — protocol selection criteria (latency, client shaping needs, internal vs external), resource modeling, and versioning strategy apply to any language; only the framework's routing/serialization differs.
Procedure
-
Choose protocol by traffic shape
- REST — default for CRUD, public APIs, broad client compatibility, cacheable via HTTP
- gRPC — internal service-to-service, sub-10ms latency mesh, streaming, strict contracts
- GraphQL — clients need field-level shaping / aggregate many resources; accept the schema-design + N+1 discipline cost (requires a per-request batching/caching layer)
- Decide by concrete thresholds (latency budget, payload variability), not "it depends"
-
Model resources (REST)
- Nouns not verbs (
/orders/{id} not /getOrder)
- Nest only one level deep (
/orders/{id}/items, not 4 levels)
- Use sub-resources for relationships, query params for filtering
-
Design versioning before v1 ships
- URL versioning (
/v1/) — simplest, most visible
- Header versioning — cleaner URLs, less discoverable
- Commit to one; document the deprecation policy (sunset header + timeline)
-
Async / long-running operations
- Return
202 Accepted + a status resource (/operations/{id}) for jobs > a few seconds
- Don't hold the request open; pair with
background-jobs skill
-
Validate the design
- Walk the top 3 client use cases through the API — count round-trips
- If a common screen needs 5+ calls → consider GraphQL or a BFF aggregate endpoint
Anti-patterns
| ❌ Anti-pattern | ✅ Correct |
|---|
Verbs in paths (/createUser) | Nouns + HTTP methods (POST /users) |
| GraphQL "because it's modern" for a simple CRUD API | REST default; GraphQL only when field-shaping is a real need |
| Breaking change shipped without a version bump | New version OR additive-only change (see api-contract) |
Deep nesting (/a/{}/b/{}/c/{}) | Max one level; use query params / sub-resource links |
Completion Criteria
Output
- API design doc / ADR:
docs/adr/ADR-NNN-api-<surface>.md — protocol choice + rationale + versioning policy
- Route definitions: with resource modeling applied
- Commit format:
feat(api): design <surface> endpoints [protocol: REST|gRPC|GraphQL]
Implementation
TypeScript + NestJS (default)
- REST: NestJS controllers +
@nestjs/swagger for OpenAPI generation
- GraphQL:
@nestjs/graphql (code-first) + DataLoader for N+1
- gRPC:
@nestjs/microservices gRPC transport + .proto contracts
- Versioning: NestJS
enableVersioning({ type: VersioningType.URI })
Other stacks
- Python / FastAPI: native OpenAPI; Strawberry/Ariadne for GraphQL; grpcio for gRPC
- Go: chi/echo for REST; gqlgen for GraphQL; google.golang.org/grpc
- Universal: resource modeling, versioning, and 202-async patterns are protocol-level, not framework-level
Related skills
api-contract — once protocol is chosen, define the schema-first contract there
schema-design — resource modeling aligns with DB schema
authorization — per-resource access control
Reference
- Key insight encoded: Pick protocol by traffic shape — REST default for CRUD, gRPC for sub-10ms internal mesh, GraphQL only when clients need field-level shaping (and you accept the schema-design discipline).