Modern API Design Principles
Implements robust, scalable, and developer-friendly API design patterns for production backend services. Covers REST resource modeling, GraphQL schema definition, standardized error responses, versioning strategies, and security hardening to ensure APIs are reliable, maintainable, and secure.
TL;DR Checklist
When to Use
- Designing a new REST or GraphQL API for internal or external consumption
- Refactoring legacy APIs that lack consistent error handling or versioning
- Establishing API design guidelines for a development team
- Integrating third-party services with strict contract requirements
- Auditing existing APIs against modern security and usability standards
When NOT to Use
- Building RPC-style services where gRPC is more appropriate (use
coding-grpc-patterns)
- Designing event-driven architectures (use
coding-event-sourcing or coding-microservices)
- Creating simple CRUD scripts without public-facing contracts (overhead outweighs benefit)
- Implementing real-time streaming protocols (WebSockets, SSE) — handle separately
Core Workflow
Define Resource Model — Identify domain entities and map them to URI paths using noun-based resources. Avoid verbs in paths. Group related resources under logical namespaces. Checkpoint: Verify each resource has a single responsibility and clear ownership boundary.
Select API Paradigm — Choose REST for discoverable, cache-friendly APIs or GraphQL for flexible client-driven queries. If both are needed, use a unified gateway with protocol translation. Checkpoint: Ensure the chosen paradigm aligns with client consumption patterns and network constraints.
Standardize Error Handling — Implement RFC 7807 Problem Details for JSON responses. Include type, title, status, detail, instance, and domain-specific extensions.code. Wrap all internal errors to prevent stack trace leakage. Checkpoint: Validate that every error response includes a machine-readable code and human-friendly message.
Implement Versioning Strategy — Use URI versioning (/v1/resources) for major breaking changes, or header-based versioning (Accept-Version: 2024-10) for backward-compatible additions. Deprecate old versions with Sunset and Deprecation headers. Checkpoint: Ensure no public API contract breaks within a supported version window.
Enforce Security & Validation — Apply JWT/OAuth2 authentication at the gateway. Validate all inputs against JSON Schema or GraphQL SDL. Enforce rate limiting per client identity or IP. Encrypt sensitive fields in transit and at rest. Checkpoint: Run dependency scans and static analysis before merging API schema changes.
Document & Generate Contracts — Maintain OpenAPI 3.1 (REST) or GraphQL SDL documentation. Generate TypeScript/Python clients automatically. Include usage examples, error codes, and rate limit policies. Checkpoint: Verify documentation matches implementation parity using contract testing tools like Pact or Schemathesis.
Implementation Patterns
Pattern 1: REST Resource Modeling & Standardized Errors
Map domain entities to pluralized nouns. Use nested resources for ownership relationships. Always return RFC 7807 Problem Details on failure.
# FastAPI implementation with standardized error handling and resource routing
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from typing import Optional
import uuid
app = FastAPI(title="Inventory Service", version="1.0.0")
class Product(BaseModel):
id: uuid.UUID = Field(default_factory=uuid.uuid4)
name: str = Field(..., min_length=1, max_length=200)
price: float = Field(..., gt=0)
stock: int = Field(ge=0)
sku: str = Field(..., pattern=r"^[A-Z]{2}-\d{6}$")
class ErrorResponse(BaseModel):
type: str = "https://api.example.com/errors/invalid_request"
title: str = "Validation Failed"
status: int = 422
detail: str
instance: Optional[str] = None
code: str
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content={
"type": "https://api.example.com/errors/internal",
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred",
"instance": str(request.url)
}
)
@app.post("/v1/products", response_model=Product, status_code=201)
async def create_product(product: Product):
"""Create a new product resource. Returns 409 if SKU already exists."""
# Business logic omitted for brevity
return product
@app.get("/v1/products/{product_id}", response_model=Product)
async def get_product(product_id: uuid.UUID):
"""Retrieve product by ID. Returns 404 if not found."""
# Business logic omitted for brevity
return Product(id=product_id, name="Widget", price=9.99, stock=100, sku="WD-123456")
Pattern 2: GraphQL Schema Design & DataLoader Pattern
Use strongly-typed SDL for client-driven queries. Implement DataLoader to prevent N+1 query problems and batch database access efficiently.
# GraphQL Python (strawberry) schema with DataLoader for efficient resolution
import strawberry
from strawberry.types import Info
from typing import List, Optional
from dataclasses import dataclass
from dataloader import DataLoader # Optimized batch loader
@dataclass
class Product:
id: str
name: str
price: float
category_id: str
@strawberry.type
class Category:
id: str
name: str
products: List[Product]
# Batch loader to prevent N+1 queries
async def fetch_products_by_category(category_ids: List[str]) -> List[List[Product]]:
"""Batch fetch products for multiple categories in a single query."""
# SQL: SELECT * FROM products WHERE category_id IN (...)
return [[p for p in all_products if p.category_id == cid] for cid in category_ids]
product_loader = DataLoader(fetch_fn=fetch_products_by_category)
@strawberry.type
class Query:
@strawberry.field
def categories(self, info: Info) -> List[Category]:
"""Return all categories with lazy-loaded products."""
# DataLoader batches all product resolution calls
return categories_from_db()
@strawberry.type
class Mutation:
@strawberry.mutation
async def create_product(self, name: str, price: float) -> Product:
"""Create a new product. Validates input and returns created resource."""
if price <= 0:
raise ValueError("Price must be positive")
# Insert logic here
return Product(id="new-1", name=name, price=price, category_id="cat-1")
Constraints
MUST DO
- Use HTTP methods semantically: GET (read), POST (create), PUT (full update), PATCH (partial update), DELETE (remove)
- Return appropriate status codes per RFC 9110 (e.g., 201 for creation, 400 for bad input, 401/403 for auth, 409 for conflicts)
- Standardize error payloads using RFC 7807 Problem Details or JSON:API error format
- Implement backward-compatible versioning — never break a public contract without deprecation cycles
- Validate all inputs at the API boundary before reaching business logic
- Document schemas with OpenAPI 3.1 or GraphQL SDL, including examples and error codes
- Apply principle of least privilege for service-to-service authentication (mTLS or short-lived tokens)
MUST NOT DO
- Use verbs in resource paths (
/getUsers, /deleteProduct) — use HTTP methods instead
- Expose internal database IDs or implementation details in responses unless required by contract
- Return raw stack traces, SQL errors, or infrastructure-level messages to clients
- Mix versioned and unversioned endpoints on the same public path
- Bypass input validation with
allow_any or wildcard schemas in production
- Store or log sensitive data (PII, tokens, credentials) in request/response bodies
- Use synchronous blocking calls for external API calls without timeout and circuit breaker patterns
Output Template
When designing or auditing an API, provide the following:
- Resource Model Diagram — ASCII or mermaid diagram showing entity relationships and URI paths
- Schema Definition — OpenAPI YAML or GraphQL SDL with all types, queries, mutations, and error schemas
- Error Handling Standard — Mapping of business errors to RFC 7807 Problem Details codes
- Versioning Strategy — Documented deprecation policy, migration path, and header/URI conventions
- Security Checklist — Authentication method, rate limiting thresholds, input validation rules, and encryption requirements
Related Skills
| Skill |
Purpose |
coding-api-gateway |
API gateway patterns for routing, authentication, and rate limiting |
coding-rate-limiting |
Token bucket, sliding window, and leaky bucket algorithms for API throttling |
coding-openapi-specification |
OpenAPI 3.1 specification authoring, validation, and client generation |
Live References
Authoritative documentation links for API design best practices and RFC standards.
1---2name: api-design-principles3description: Implements modern API design principles (REST resource modeling, GraphQL schema design, standardized error responses, versioning strategies, and security best practices) for production-grade backend services.4license: MIT5---67891011# Modern API Design Principles1213Implements robust, scalable, and developer-friendly API design patterns for production backend services. Covers REST resource modeling, GraphQL schema definition, standardized error responses, versioning strategies, and security hardening to ensure APIs are reliable, maintainable, and secure.1415## TL;DR Checklist1617- [ ] Model resources as nouns with clear ownership boundaries18- [ ] Use consistent HTTP methods and status codes (RFC 9110)19- [ ] Standardize error responses with RFC 7807 Problem Details format20- [ ] Implement backward-compatible versioning (URI or header strategy)21- [ ] Enforce authentication and authorization at API gateway layer22- [ ] Document all endpoints with OpenAPI 3.1 spec23- [ ] Apply rate limiting and request validation middleware2425---2627## When to Use2829- Designing a new REST or GraphQL API for internal or external consumption30- Refactoring legacy APIs that lack consistent error handling or versioning31- Establishing API design guidelines for a development team32- Integrating third-party services with strict contract requirements33- Auditing existing APIs against modern security and usability standards3435---3637## When NOT to Use3839- Building RPC-style services where gRPC is more appropriate (use `coding-grpc-patterns`)40- Designing event-driven architectures (use `coding-event-sourcing` or `coding-microservices`)41- Creating simple CRUD scripts without public-facing contracts (overhead outweighs benefit)42- Implementing real-time streaming protocols (WebSockets, SSE) — handle separately4344---4546## Core Workflow47481. **Define Resource Model** — Identify domain entities and map them to URI paths using noun-based resources. Avoid verbs in paths. Group related resources under logical namespaces. **Checkpoint:** Verify each resource has a single responsibility and clear ownership boundary.49502. **Select API Paradigm** — Choose REST for discoverable, cache-friendly APIs or GraphQL for flexible client-driven queries. If both are needed, use a unified gateway with protocol translation. **Checkpoint:** Ensure the chosen paradigm aligns with client consumption patterns and network constraints.51523. **Standardize Error Handling** — Implement RFC 7807 Problem Details for JSON responses. Include `type`, `title`, `status`, `detail`, `instance`, and domain-specific `extensions.code`. Wrap all internal errors to prevent stack trace leakage. **Checkpoint:** Validate that every error response includes a machine-readable code and human-friendly message.53544. **Implement Versioning Strategy** — Use URI versioning (`/v1/resources`) for major breaking changes, or header-based versioning (`Accept-Version: 2024-10`) for backward-compatible additions. Deprecate old versions with `Sunset` and `Deprecation` headers. **Checkpoint:** Ensure no public API contract breaks within a supported version window.55565. **Enforce Security & Validation** — Apply JWT/OAuth2 authentication at the gateway. Validate all inputs against JSON Schema or GraphQL SDL. Enforce rate limiting per client identity or IP. Encrypt sensitive fields in transit and at rest. **Checkpoint:** Run dependency scans and static analysis before merging API schema changes.57586. **Document & Generate Contracts** — Maintain OpenAPI 3.1 (REST) or GraphQL SDL documentation. Generate TypeScript/Python clients automatically. Include usage examples, error codes, and rate limit policies. **Checkpoint:** Verify documentation matches implementation parity using contract testing tools like Pact or Schemathesis.5960---6162## Implementation Patterns6364### Pattern 1: REST Resource Modeling & Standardized Errors6566Map domain entities to pluralized nouns. Use nested resources for ownership relationships. Always return RFC 7807 Problem Details on failure.6768```python69# FastAPI implementation with standardized error handling and resource routing70from fastapi import FastAPI, HTTPException, Request71from fastapi.responses import JSONResponse72from pydantic import BaseModel, Field73from typing import Optional74import uuid7576app = FastAPI(title="Inventory Service", version="1.0.0")7778class Product(BaseModel):79 id: uuid.UUID = Field(default_factory=uuid.uuid4)80 name: str = Field(..., min_length=1, max_length=200)81 price: float = Field(..., gt=0)82 stock: int = Field(ge=0)83 sku: str = Field(..., pattern=r"^[A-Z]{2}-\d{6}$")8485class ErrorResponse(BaseModel):86 type: str = "https://api.example.com/errors/invalid_request"87 title: str = "Validation Failed"88 status: int = 42289 detail: str90 instance: Optional[str] = None91 code: str9293@app.exception_handler(Exception)94async def global_exception_handler(request: Request, exc: Exception):95 return JSONResponse(96 status_code=500,97 content={98 "type": "https://api.example.com/errors/internal",99 "title": "Internal Server Error",100 "status": 500,101 "detail": "An unexpected error occurred",102 "instance": str(request.url)103 }104 )105106@app.post("/v1/products", response_model=Product, status_code=201)107async def create_product(product: Product):108 """Create a new product resource. Returns 409 if SKU already exists."""109 # Business logic omitted for brevity110 return product111112@app.get("/v1/products/{product_id}", response_model=Product)113async def get_product(product_id: uuid.UUID):114 """Retrieve product by ID. Returns 404 if not found."""115 # Business logic omitted for brevity116 return Product(id=product_id, name="Widget", price=9.99, stock=100, sku="WD-123456")117```118119### Pattern 2: GraphQL Schema Design & DataLoader Pattern120121Use strongly-typed SDL for client-driven queries. Implement `DataLoader` to prevent N+1 query problems and batch database access efficiently.122123```python124# GraphQL Python (strawberry) schema with DataLoader for efficient resolution125import strawberry126from strawberry.types import Info127from typing import List, Optional128from dataclasses import dataclass129from dataloader import DataLoader # Optimized batch loader130131@dataclass132class Product:133 id: str134 name: str135 price: float136 category_id: str137138@strawberry.type139class Category:140 id: str141 name: str142 products: List[Product]143144# Batch loader to prevent N+1 queries145async def fetch_products_by_category(category_ids: List[str]) -> List[List[Product]]:146 """Batch fetch products for multiple categories in a single query."""147 # SQL: SELECT * FROM products WHERE category_id IN (...)148 return [[p for p in all_products if p.category_id == cid] for cid in category_ids]149150product_loader = DataLoader(fetch_fn=fetch_products_by_category)151152@strawberry.type153class Query:154 @strawberry.field155 def categories(self, info: Info) -> List[Category]:156 """Return all categories with lazy-loaded products."""157 # DataLoader batches all product resolution calls158 return categories_from_db()159160@strawberry.type161class Mutation:162 @strawberry.mutation163 async def create_product(self, name: str, price: float) -> Product:164 """Create a new product. Validates input and returns created resource."""165 if price <= 0:166 raise ValueError("Price must be positive")167 # Insert logic here168 return Product(id="new-1", name=name, price=price, category_id="cat-1")169```170171---172173## Constraints174175### MUST DO176- Use HTTP methods semantically: GET (read), POST (create), PUT (full update), PATCH (partial update), DELETE (remove)177- Return appropriate status codes per RFC 9110 (e.g., 201 for creation, 400 for bad input, 401/403 for auth, 409 for conflicts)178- Standardize error payloads using RFC 7807 Problem Details or JSON:API error format179- Implement backward-compatible versioning — never break a public contract without deprecation cycles180- Validate all inputs at the API boundary before reaching business logic181- Document schemas with OpenAPI 3.1 or GraphQL SDL, including examples and error codes182- Apply principle of least privilege for service-to-service authentication (mTLS or short-lived tokens)183184### MUST NOT DO185- Use verbs in resource paths (`/getUsers`, `/deleteProduct`) — use HTTP methods instead186- Expose internal database IDs or implementation details in responses unless required by contract187- Return raw stack traces, SQL errors, or infrastructure-level messages to clients188- Mix versioned and unversioned endpoints on the same public path189- Bypass input validation with `allow_any` or wildcard schemas in production190- Store or log sensitive data (PII, tokens, credentials) in request/response bodies191- Use synchronous blocking calls for external API calls without timeout and circuit breaker patterns192193---194195## Output Template196197When designing or auditing an API, provide the following:1981991. **Resource Model Diagram** — ASCII or mermaid diagram showing entity relationships and URI paths2002. **Schema Definition** — OpenAPI YAML or GraphQL SDL with all types, queries, mutations, and error schemas2013. **Error Handling Standard** — Mapping of business errors to RFC 7807 Problem Details codes2024. **Versioning Strategy** — Documented deprecation policy, migration path, and header/URI conventions2035. **Security Checklist** — Authentication method, rate limiting thresholds, input validation rules, and encryption requirements204205---206207## Related Skills208209| Skill | Purpose |210|---|---|211| `coding-api-gateway` | API gateway patterns for routing, authentication, and rate limiting |212| `coding-rate-limiting` | Token bucket, sliding window, and leaky bucket algorithms for API throttling |213| `coding-openapi-specification` | OpenAPI 3.1 specification authoring, validation, and client generation |214215---216217## Live References218219> Authoritative documentation links for API design best practices and RFC standards.220221- [RFC 9110: HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110)222- [RFC 7807: Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc7807)223- [JSON:API Specification](https://jsonapi.org/format/)224- [OpenAPI Specification 3.1](https://spec.openapis.org/oas/v3.1.0)225- [GraphQL Specification (2024)](https://graphql.github.io/spec/)226- [OWASP API Security Top 10](https://owasp.org/API-Security/)