
api-design-patterns
Comprehensive REST and GraphQL API design patterns with versioning, pagination, error handling, and HATEOAS principles. Use when designing APIs, defining endpoints, or architecting service contracts requiring production-grade patterns.
Comprehensive REST and GraphQL API design patterns with versioning, pagination, error handling, and HATEOAS principles. Use when designing APIs, defining endpoints, or architecting service contracts requiring production-grade patterns.
API Design Patterns
Expert guidance for designing scalable, maintainable REST and GraphQL APIs with industry-standard patterns for versioning, pagination, error handling, authentication, and service contracts.
When to Use This Skill
- Designing new REST or GraphQL APIs from scratch
- Refactoring existing APIs for better scalability and consistency
- Defining service contracts for microservices architectures
- Implementing versioning strategies for API evolution
- Standardizing error handling and response formats across services
- Designing pagination for large datasets
- Implementing HATEOAS or hypermedia-driven APIs
- Creating API specifications (OpenAPI, GraphQL Schema)
Core Principles
1. Resource-Oriented Design (REST)
URLs represent resources, not actions:
✓ GET /users/123
✓ POST /users
✓ PUT /users/123
✓ DELETE /users/123
✗ GET /getUser?id=123
✗ POST /createUser
✗ POST /deleteUser
Use HTTP methods semantically:
- GET: Retrieve resource(s), idempotent, cacheable
- POST: Create resource, non-idempotent
- PUT: Replace entire resource, idempotent
- PATCH: Partial update, idempotent
- DELETE: Remove resource, idempotent
2. Consistent Naming Conventions
Resources: /users, /orders, /products (plural nouns)
Nested: /users/123/orders
Collections: /users?status=active&page=2
Sub-resources: /users/123/settings
Actions (rare): /users/123/activate (POST)
3. HTTP Status Codes
Success:
- 200 OK: Standard response for GET, PUT, PATCH
- 201 Created: Resource created (POST), return Location header
- 202 Accepted: Async processing started
- 204 No Content: Success with no response body (DELETE)
Client Errors:
- 400 Bad Request: Invalid syntax or validation failure
- 401 Unauthorized: Authentication required or failed
- 403 Forbidden: Authenticated but insufficient permissions
- 404 Not Found: Resource doesn't exist
- 409 Conflict: State conflict (duplicate, version mismatch)
- 422 Unprocessable Entity: Semantic validation failure
- 429 Too Many Requests: Rate limit exceeded
Server Errors:
- 500 Internal Server Error: Unexpected server failure
- 502 Bad Gateway: Upstream service failure
- 503 Service Unavailable: Temporary overload or maintenance
- 504 Gateway Timeout: Upstream timeout
Versioning Strategies
URI Versioning (Most Common)
GET /v1/users/123
GET /v2/users/123
Pros: Clear, easy to route, browser-testable
Cons: URL proliferation, cache fragmentation
When: Public APIs, major breaking changes
Header Versioning
GET /users/123
Accept: application/vnd.myapi.v2+json
Pros: Clean URLs, content negotiation
Cons: Harder to test, caching complexity
When: Internal APIs, minor version differences
Query Parameter Versioning
GET /users/123?version=2
Pros: Simple, backward compatible
Cons: Pollutes query space, inconsistent
When: Rare, legacy compatibility
Deprecation Headers
Sunset: Sat, 31 Dec 2024 23:59:59 GMT
Deprecation: true
Link: <https://api.example.com/v2/users/123>; rel="successor-version"
Pagination Patterns
Offset-Based Pagination
GET /users?limit=20&offset=40
Response:
{
"data": [...],
"pagination": {
"limit": 20,
"offset": 40,
"total": 1543
},
"links": {
"next": "/users?limit=20&offset=60",
"prev": "/users?limit=20&offset=20"
}
}
Pros: Simple, predictable, supports total count
Cons: Inconsistent with concurrent writes, performance degrades
When: Small datasets, stable data, admin UIs
Cursor-Based Pagination
GET /users?limit=20&cursor=eyJpZCI6MTIzfQ
Response:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTQzfQ",
"has_more": true
},
"links": {
"next": "/users?limit=20&cursor=eyJpZCI6MTQzfQ"
}
}
Pros: Consistent with writes, scalable, efficient
Cons: No total count, can't jump to arbitrary page
When: Large datasets, real-time feeds, infinite scroll
Keyset Pagination (Seek Method)
GET /users?limit=20&after_id=123&created_after=2024-01-01T00:00:00Z
Pros: Most performant, index-friendly
Cons: Requires sortable field, complex queries
When: Very large datasets, time-series data
Error Response Format
Standard Error Schema
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Email format is invalid"
},
{
"field": "age",
"code": "OUT_OF_RANGE",
"message": "Age must be between 18 and 120"
}
],
"request_id": "req_a3f7c9b2",
"timestamp": "2024-01-15T10:30:00Z",
"documentation_url": "https://docs.api.com/errors/VALIDATION_ERROR"
}
}
Error Code Patterns
Format: CATEGORY_SPECIFIC_REASON
Authentication:
- AUTH_MISSING_TOKEN
- AUTH_INVALID_TOKEN
- AUTH_EXPIRED_TOKEN
Authorization:
- AUTHZ_INSUFFICIENT_PERMISSIONS
- AUTHZ_RESOURCE_FORBIDDEN
Validation:
- VALIDATION_MISSING_FIELD
- VALIDATION_INVALID_FORMAT
- VALIDATION_OUT_OF_RANGE
Business Logic:
- BUSINESS_DUPLICATE_EMAIL
- BUSINESS_INSUFFICIENT_BALANCE
- BUSINESS_OPERATION_NOT_ALLOWED
System:
- SYSTEM_INTERNAL_ERROR
- SYSTEM_SERVICE_UNAVAILABLE
- SYSTEM_RATE_LIMIT_EXCEEDED
Filtering and Searching
Query Parameters for Filtering
GET /users?status=active&role=admin&created_after=2024-01-01
GET /users?search=john&fields=name,email
GET /users?sort=-created_at,name # - prefix for descending
Complex Filtering (FIQL/RSQL)
GET /users?filter=status==active;role==admin,role==moderator
# AND between semicolons, OR between commas
GET /products?filter=price>100;price<500;category==electronics
Full-Text Search
GET /users?q=john+smith&fields=name,bio,company
Response includes relevance scoring:
{
"data": [
{
"id": 123,
"name": "John Smith",
"_score": 0.95
}
]
}
Field Selection (Sparse Fieldsets)
GET /users/123?fields=id,name,email
Response:
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
# Nested resources
GET /users/123?fields=id,name,profile(avatar,bio)
Benefits:
- Reduced payload size
- Faster response times
- Lower bandwidth consumption
- Better mobile performance
HATEOAS (Hypermedia)
HAL (Hypertext Application Language)
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"_links": {
"self": { "href": "/users/123" },
"orders": { "href": "/users/123/orders" },
"update": { "href": "/users/123", "method": "PUT" },
"delete": { "href": "/users/123", "method": "DELETE" }
},
"_embedded": {
"recent_orders": [
{
"id": 456,
"total": 99.99,
"_links": {
"self": { "href": "/orders/456" }
}
}
]
}
}
JSON:API Format
{
"data": {
"type": "users",
"id": "123",
"attributes": {
"name": "John Doe",
"email": "john@example.com"
},
"relationships": {
"orders": {
"links": {
"self": "/users/123/relationships/orders",
"related": "/users/123/orders"
}
}
},
"links": {
"self": "/users/123"
}
}
}
Rate Limiting Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 742
X-RateLimit-Reset: 1705320000
Retry-After: 3600
# Standard (RFC 6585)
RateLimit-Limit: 1000
RateLimit-Remaining: 742
RateLimit-Reset: 3600
Authentication Patterns
Bearer Token (OAuth 2.0, JWT)
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Pros: Stateless, scalable, standard
Cons: Token size, revocation complexity
When: Modern APIs, microservices
API Key
X-API-Key: ak_live_a3f7c9b2d8e1f4g6h9
Pros: Simple, server-side management
Cons: Less secure, harder to scope
When: Internal services, admin APIs
Basic Auth
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Pros: Simple, built-in browser support
Cons: Credentials in every request
When: Internal tools, development only
Idempotency
Idempotency Keys (POST)
POST /payments
Idempotency-Key: a3f7c9b2-d8e1-4f6g-h9i0-j1k2l3m4n5o6
Content-Type: application/json
{
"amount": 100.00,
"currency": "USD",
"description": "Payment for order #123"
}
# Server stores key + response for 24 hours
# Duplicate requests return cached response with 200 OK
Natural Idempotency
PUT /users/123 # Always idempotent
DELETE /users/123 # Idempotent (404 on repeat)
POST /users/123/follow # Use PUT for idempotency
Caching Strategies
ETags (Conditional Requests)
# Initial request
GET /users/123
ETag: "a3f7c9b2"
# Subsequent request
GET /users/123
If-None-Match: "a3f7c9b2"
# Response if unchanged:
304 Not Modified
Cache-Control Headers
# Never cache
Cache-Control: no-store
# Cache for 1 hour, revalidate
Cache-Control: max-age=3600, must-revalidate
# Cache forever (immutable)
Cache-Control: public, max-age=31536000, immutable
GraphQL Patterns
Query Structure
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
orders(first: 10) {
edges {
node {
id
total
status
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
Error Handling
{
"data": {
"user": null
},
"errors": [
{
"message": "User not found",
"locations": [{ "line": 2, "column": 3 }],
"path": ["user"],
"extensions": {
"code": "NOT_FOUND",
"userId": "123"
}
}
]
}
Mutation Patterns
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
user {
id
name
email
}
errors {
field
message
}
}
}
Best Practices Summary
- Consistency: Follow conventions across all endpoints
- Versioning: Plan deprecation strategy from day one
- Documentation: Use OpenAPI/GraphQL schemas, keep updated
- Error Handling: Detailed, actionable error messages with codes
- Security: Always use HTTPS, validate inputs, rate limit
- Performance: Implement caching, pagination, field selection
- Monitoring: Log request IDs, track latency and error rates
- Backward Compatibility: Additive changes only within versions
- Testing: Contract tests, integration tests, load tests
- Documentation: Interactive docs (Swagger UI, GraphQL Playground)
Anti-Patterns to Avoid
- Chatty APIs: Too many round trips (use batching, GraphQL)
- Over-fetching: Returning unnecessary data (use field selection)
- Under-fetching: Requiring multiple calls (use includes/embeds)
- Leaking Implementation: Exposing DB structure in API
- Poor Error Messages: Generic errors without details
- Breaking Changes: Modifying existing fields without versioning
- No Rate Limiting: Allowing resource exhaustion
- Missing Documentation: Undocumented endpoints and parameters
- Inconsistent Naming: Mixed conventions across endpoints
- Ignoring HTTP Semantics: Misusing status codes and methods
Resources
- REST: Roy Fielding's dissertation, RFC 7231 (HTTP semantics)
- OpenAPI: https://spec.openapis.org/oas/latest.html
- GraphQL: https://graphql.org/learn/
- HAL: https://stateless.group/hal_specification.html
- JSON:API: https://jsonapi.org/
- RFC 7807: Problem Details for HTTP APIs
You Might Also Like
Related Skills

gog
Google Workspace CLI for Gmail, Calendar, Drive, Contacts, Sheets, and Docs.
openclaw
orpc-contract-first
Guide for implementing oRPC contract-first API patterns in Dify frontend. Triggers when creating new API contracts, adding service endpoints, integrating TanStack Query with typed contracts, or migrating legacy service calls to oRPC. Use for all API layer work in web/contract and web/service directories.
langgenius
