| name | api-design-principles |
| description | Interface design for APIs (REST, GraphQL, gRPC, library/SDK, CLI) — consistency, evolvability, error design, versioning, backward compatibility. Use when: designing APIs, reviewing API contracts, versioning decisions, error response design, choosing between REST/GraphQL/gRPC. Triggers: API design, REST, GraphQL, gRPC, versioning, backward compatibility, error response, pagination, rate limiting, OpenAPI |
API Design Principles
Interface design judgment for APIs (REST, GraphQL, gRPC, library/SDK, CLI). Not architecture-level decisions (→ architect-thinking), not internal module design (→ design-philosophy).
Core Principles
- Consistency over cleverness: Uniform naming, consistent patterns across endpoints. Predictability reduces consumer cognitive load.
- Principle of Least Surprise: API behaves as consumers expect. No hidden side effects.
- Make common case easy, hard case possible: 80% of usage should be simple. Power users get escape hatches.
API Design Wisdom (Bloch)
| Principle | Rationale |
|---|
| Design for inheritance or prohibit it | Expose only what you intend to support long-term |
| Return empty collections, not null | Avoid forcing null checks on every consumer |
| Use overloading judiciously | Prefer distinct method names over overloaded signatures |
| Minimize mutability | Immutable objects: simpler reasoning, thread-safe |
| Favor static factory methods over constructors | Named, can return subtypes, can cache |
REST API Design
- Resource naming: Plural nouns (
/users, not /getUsers). No verbs in URLs.
- HTTP method semantics: GET=read (idempotent), POST=create, PUT=full replace (idempotent), PATCH=partial update, DELETE=remove (idempotent)
- Status codes: Use correctly — 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests, 500 Internal Server Error
- Pagination: Cursor-based (scalable, no skipping) vs Offset-based (simple, skip-friendly). Default cursor for large datasets.
- Filtering/Sorting: Query parameters (
?status=active&sort=-created_at), not path segments
- HATEOAS: Cost vs benefit. Useful for discoverable APIs, overhead for tight client-server coupling.
REST vs GraphQL vs gRPC
| Dimension | REST | GraphQL | gRPC |
|---|
| Best for | CRUD, broad audience | Flexible queries, mobile | Internal services, high perf |
| Fetching | Over-fetching common | Client controls fields | Proto defines shape |
| Caching | HTTP caching natural | Complex (POST-based) | No HTTP caching |
| Schema | OpenAPI (optional) | Strong typed (required) | Protobuf (required) |
| Learning curve | Low | Medium | High |
| Streaming | SSE / WebSocket | Subscriptions | Bidirectional native |
Error Design
- Structured response:
{ "error": { "code": "VALIDATION_ERROR", "message": "...", "details": [...] } }
- Machine-readable codes (stable) + human-readable messages (can change)
- RFC 9457 Problem Details: Standard format —
type, title, status, detail, instance
- Don't leak internals: No stack traces, DB column names, or internal service names in production
Versioning & Backward Compatibility
| Strategy | Pros | Cons |
|---|
URL path (/v1/users) | Explicit, easy routing | Proliferates endpoints |
Header (Accept: ...;version=2) | Clean URLs | Hidden, harder to test |
Query param (?version=2) | Easy to add | Pollutes query space |
| Content negotiation | RESTful | Complex |
- Safe changes: Adding fields, adding endpoints, adding optional params
- Breaking changes (need versioning): Removing/renaming fields, changing types/semantics
- Deprecation: Announce →
Sunset header → grace period → remove. Never surprise consumers.
- Postel's Law: Be liberal in what you accept, conservative in what you send. Caveat: liberal acceptance can hide producer bugs.
Rate Limiting & Idempotency
- Rate limiting: Return
429 with Retry-After header. Document limits. Token bucket or sliding window.
- Idempotency keys: For non-idempotent operations (POST). Prevents duplicate processing on retry.
API Documentation
- OpenAPI/Swagger: Machine-readable spec → auto-generated docs, client SDKs, contract testing
- Examples over descriptions: Show request/response pairs. Real examples > abstract schemas.
Anti-Patterns
| Pattern | Problem |
|---|
| Chatty API | Too many round trips → batch endpoints or GraphQL |
| God endpoint | One endpoint does everything → decompose by resource |
| Stringly-typed | Everything is string → use proper types, enums |
| Version in body | Routing nightmare → use URL or header |
| Breaking silently | No deprecation notice → sunset header + changelog |
Reference Books
- Effective Java — Bloch (API design chapters)
- RESTful Web APIs — Richardson, Amundsen
- API Design Patterns — Geewax
- Designing Web APIs — Jin, Sahni, Shevat