Api Design Principles
Design REST and GraphQL APIs that developers love to use
✨ The solution you've been looking for
Master REST and GraphQL API design principles to build intuitive, scalable, and maintainable APIs that delight developers. Use when designing new APIs, reviewing API specifications, or establishing API design standards.
See It In Action
Interactive preview & real-world examples
AI Conversation Simulator
See how users interact with this skill
User Prompt
I need to design a REST API for an e-commerce platform that handles users, products, and orders. Help me structure the endpoints and define the resource relationships.
Skill Processing
Analyzing request...
Agent Response
Complete API design with resource-oriented endpoints, proper HTTP method usage, pagination patterns, and consistent error handling
Quick Start (3 Steps)
Get up and running in minutes
Install
claude-code skill install api-design-principles
claude-code skill install api-design-principlesConfig
First Trigger
@api-design-principles helpCommands
| Command | Description | Required Args |
|---|---|---|
| @api-design-principles design-a-new-rest-api | Create a well-structured REST API with proper resource modeling, HTTP methods, and error handling | None |
| @api-design-principles graphql-schema-design | Build a GraphQL schema with proper type definitions, relationships, and mutation patterns | None |
| @api-design-principles api-standards-review | Evaluate existing API designs and establish team standards | None |
Typical Use Cases
Design a New REST API
Create a well-structured REST API with proper resource modeling, HTTP methods, and error handling
GraphQL Schema Design
Build a GraphQL schema with proper type definitions, relationships, and mutation patterns
API Standards Review
Evaluate existing API designs and establish team standards
Overview
API Design Principles
Master REST and GraphQL API design principles to build intuitive, scalable, and maintainable APIs that delight developers and stand the test of time.
When to Use This Skill
- Designing new REST or GraphQL APIs
- Refactoring existing APIs for better usability
- Establishing API design standards for your team
- Reviewing API specifications before implementation
- Migrating between API paradigms (REST to GraphQL, etc.)
- Creating developer-friendly API documentation
- Optimizing APIs for specific use cases (mobile, third-party integrations)
Core Concepts
1. RESTful Design Principles
Resource-Oriented Architecture
- Resources are nouns (users, orders, products), not verbs
- Use HTTP methods for actions (GET, POST, PUT, PATCH, DELETE)
- URLs represent resource hierarchies
- Consistent naming conventions
HTTP Methods Semantics:
GET: Retrieve resources (idempotent, safe)POST: Create new resourcesPUT: Replace entire resource (idempotent)PATCH: Partial resource updatesDELETE: Remove resources (idempotent)
2. GraphQL Design Principles
Schema-First Development
- Types define your domain model
- Queries for reading data
- Mutations for modifying data
- Subscriptions for real-time updates
Query Structure:
- Clients request exactly what they need
- Single endpoint, multiple operations
- Strongly typed schema
- Introspection built-in
3. API Versioning Strategies
URL Versioning:
/api/v1/users
/api/v2/users
Header Versioning:
Accept: application/vnd.api+json; version=1
Query Parameter Versioning:
/api/users?version=1
REST API Design Patterns
Pattern 1: Resource Collection Design
1# Good: Resource-oriented endpoints
2GET /api/users # List users (with pagination)
3POST /api/users # Create user
4GET /api/users/{id} # Get specific user
5PUT /api/users/{id} # Replace user
6PATCH /api/users/{id} # Update user fields
7DELETE /api/users/{id} # Delete user
8
9# Nested resources
10GET /api/users/{id}/orders # Get user's orders
11POST /api/users/{id}/orders # Create order for user
12
13# Bad: Action-oriented endpoints (avoid)
14POST /api/createUser
15POST /api/getUserById
16POST /api/deleteUser
Pattern 2: Pagination and Filtering
1from typing import List, Optional
2from pydantic import BaseModel, Field
3
4class PaginationParams(BaseModel):
5 page: int = Field(1, ge=1, description="Page number")
6 page_size: int = Field(20, ge=1, le=100, description="Items per page")
7
8class FilterParams(BaseModel):
9 status: Optional[str] = None
10 created_after: Optional[str] = None
11 search: Optional[str] = None
12
13class PaginatedResponse(BaseModel):
14 items: List[dict]
15 total: int
16 page: int
17 page_size: int
18 pages: int
19
20 @property
21 def has_next(self) -> bool:
22 return self.page < self.pages
23
24 @property
25 def has_prev(self) -> bool:
26 return self.page > 1
27
28# FastAPI endpoint example
29from fastapi import FastAPI, Query, Depends
30
31app = FastAPI()
32
33@app.get("/api/users", response_model=PaginatedResponse)
34async def list_users(
35 page: int = Query(1, ge=1),
36 page_size: int = Query(20, ge=1, le=100),
37 status: Optional[str] = Query(None),
38 search: Optional[str] = Query(None)
39):
40 # Apply filters
41 query = build_query(status=status, search=search)
42
43 # Count total
44 total = await count_users(query)
45
46 # Fetch page
47 offset = (page - 1) * page_size
48 users = await fetch_users(query, limit=page_size, offset=offset)
49
50 return PaginatedResponse(
51 items=users,
52 total=total,
53 page=page,
54 page_size=page_size,
55 pages=(total + page_size - 1) // page_size
56 )
Pattern 3: Error Handling and Status Codes
1from fastapi import HTTPException, status
2from pydantic import BaseModel
3
4class ErrorResponse(BaseModel):
5 error: str
6 message: str
7 details: Optional[dict] = None
8 timestamp: str
9 path: str
10
11class ValidationErrorDetail(BaseModel):
12 field: str
13 message: str
14 value: Any
15
16# Consistent error responses
17STATUS_CODES = {
18 "success": 200,
19 "created": 201,
20 "no_content": 204,
21 "bad_request": 400,
22 "unauthorized": 401,
23 "forbidden": 403,
24 "not_found": 404,
25 "conflict": 409,
26 "unprocessable": 422,
27 "internal_error": 500
28}
29
30def raise_not_found(resource: str, id: str):
31 raise HTTPException(
32 status_code=status.HTTP_404_NOT_FOUND,
33 detail={
34 "error": "NotFound",
35 "message": f"{resource} not found",
36 "details": {"id": id}
37 }
38 )
39
40def raise_validation_error(errors: List[ValidationErrorDetail]):
41 raise HTTPException(
42 status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
43 detail={
44 "error": "ValidationError",
45 "message": "Request validation failed",
46 "details": {"errors": [e.dict() for e in errors]}
47 }
48 )
49
50# Example usage
51@app.get("/api/users/{user_id}")
52async def get_user(user_id: str):
53 user = await fetch_user(user_id)
54 if not user:
55 raise_not_found("User", user_id)
56 return user
Pattern 4: HATEOAS (Hypermedia as the Engine of Application State)
1class UserResponse(BaseModel):
2 id: str
3 name: str
4 email: str
5 _links: dict
6
7 @classmethod
8 def from_user(cls, user: User, base_url: str):
9 return cls(
10 id=user.id,
11 name=user.name,
12 email=user.email,
13 _links={
14 "self": {"href": f"{base_url}/api/users/{user.id}"},
15 "orders": {"href": f"{base_url}/api/users/{user.id}/orders"},
16 "update": {
17 "href": f"{base_url}/api/users/{user.id}",
18 "method": "PATCH"
19 },
20 "delete": {
21 "href": f"{base_url}/api/users/{user.id}",
22 "method": "DELETE"
23 }
24 }
25 )
GraphQL Design Patterns
Pattern 1: Schema Design
1# schema.graphql
2
3# Clear type definitions
4type User {
5 id: ID!
6 email: String!
7 name: String!
8 createdAt: DateTime!
9
10 # Relationships
11 orders(first: Int = 20, after: String, status: OrderStatus): OrderConnection!
12
13 profile: UserProfile
14}
15
16type Order {
17 id: ID!
18 status: OrderStatus!
19 total: Money!
20 items: [OrderItem!]!
21 createdAt: DateTime!
22
23 # Back-reference
24 user: User!
25}
26
27# Pagination pattern (Relay-style)
28type OrderConnection {
29 edges: [OrderEdge!]!
30 pageInfo: PageInfo!
31 totalCount: Int!
32}
33
34type OrderEdge {
35 node: Order!
36 cursor: String!
37}
38
39type PageInfo {
40 hasNextPage: Boolean!
41 hasPreviousPage: Boolean!
42 startCursor: String
43 endCursor: String
44}
45
46# Enums for type safety
47enum OrderStatus {
48 PENDING
49 CONFIRMED
50 SHIPPED
51 DELIVERED
52 CANCELLED
53}
54
55# Custom scalars
56scalar DateTime
57scalar Money
58
59# Query root
60type Query {
61 user(id: ID!): User
62 users(first: Int = 20, after: String, search: String): UserConnection!
63
64 order(id: ID!): Order
65}
66
67# Mutation root
68type Mutation {
69 createUser(input: CreateUserInput!): CreateUserPayload!
70 updateUser(input: UpdateUserInput!): UpdateUserPayload!
71 deleteUser(id: ID!): DeleteUserPayload!
72
73 createOrder(input: CreateOrderInput!): CreateOrderPayload!
74}
75
76# Input types for mutations
77input CreateUserInput {
78 email: String!
79 name: String!
80 password: String!
81}
82
83# Payload types for mutations
84type CreateUserPayload {
85 user: User
86 errors: [Error!]
87}
88
89type Error {
90 field: String
91 message: String!
92}
Pattern 2: Resolver Design
1from typing import Optional, List
2from ariadne import QueryType, MutationType, ObjectType
3from dataclasses import dataclass
4
5query = QueryType()
6mutation = MutationType()
7user_type = ObjectType("User")
8
9@query.field("user")
10async def resolve_user(obj, info, id: str) -> Optional[dict]:
11 """Resolve single user by ID."""
12 return await fetch_user_by_id(id)
13
14@query.field("users")
15async def resolve_users(
16 obj,
17 info,
18 first: int = 20,
19 after: Optional[str] = None,
20 search: Optional[str] = None
21) -> dict:
22 """Resolve paginated user list."""
23 # Decode cursor
24 offset = decode_cursor(after) if after else 0
25
26 # Fetch users
27 users = await fetch_users(
28 limit=first + 1, # Fetch one extra to check hasNextPage
29 offset=offset,
30 search=search
31 )
32
33 # Pagination
34 has_next = len(users) > first
35 if has_next:
36 users = users[:first]
37
38 edges = [
39 {
40 "node": user,
41 "cursor": encode_cursor(offset + i)
42 }
43 for i, user in enumerate(users)
44 ]
45
46 return {
47 "edges": edges,
48 "pageInfo": {
49 "hasNextPage": has_next,
50 "hasPreviousPage": offset > 0,
51 "startCursor": edges[0]["cursor"] if edges else None,
52 "endCursor": edges[-1]["cursor"] if edges else None
53 },
54 "totalCount": await count_users(search=search)
55 }
56
57@user_type.field("orders")
58async def resolve_user_orders(user: dict, info, first: int = 20) -> dict:
59 """Resolve user's orders (N+1 prevention with DataLoader)."""
60 # Use DataLoader to batch requests
61 loader = info.context["loaders"]["orders_by_user"]
62 orders = await loader.load(user["id"])
63
64 return paginate_orders(orders, first)
65
66@mutation.field("createUser")
67async def resolve_create_user(obj, info, input: dict) -> dict:
68 """Create new user."""
69 try:
70 # Validate input
71 validate_user_input(input)
72
73 # Create user
74 user = await create_user(
75 email=input["email"],
76 name=input["name"],
77 password=hash_password(input["password"])
78 )
79
80 return {
81 "user": user,
82 "errors": []
83 }
84 except ValidationError as e:
85 return {
86 "user": None,
87 "errors": [{"field": e.field, "message": e.message}]
88 }
Pattern 3: DataLoader (N+1 Problem Prevention)
1from aiodataloader import DataLoader
2from typing import List, Optional
3
4class UserLoader(DataLoader):
5 """Batch load users by ID."""
6
7 async def batch_load_fn(self, user_ids: List[str]) -> List[Optional[dict]]:
8 """Load multiple users in single query."""
9 users = await fetch_users_by_ids(user_ids)
10
11 # Map results back to input order
12 user_map = {user["id"]: user for user in users}
13 return [user_map.get(user_id) for user_id in user_ids]
14
15class OrdersByUserLoader(DataLoader):
16 """Batch load orders by user ID."""
17
18 async def batch_load_fn(self, user_ids: List[str]) -> List[List[dict]]:
19 """Load orders for multiple users in single query."""
20 orders = await fetch_orders_by_user_ids(user_ids)
21
22 # Group orders by user_id
23 orders_by_user = {}
24 for order in orders:
25 user_id = order["user_id"]
26 if user_id not in orders_by_user:
27 orders_by_user[user_id] = []
28 orders_by_user[user_id].append(order)
29
30 # Return in input order
31 return [orders_by_user.get(user_id, []) for user_id in user_ids]
32
33# Context setup
34def create_context():
35 return {
36 "loaders": {
37 "user": UserLoader(),
38 "orders_by_user": OrdersByUserLoader()
39 }
40 }
Best Practices
REST APIs
- Consistent Naming: Use plural nouns for collections (
/users, not/user) - Stateless: Each request contains all necessary information
- Use HTTP Status Codes Correctly: 2xx success, 4xx client errors, 5xx server errors
- Version Your API: Plan for breaking changes from day one
- Pagination: Always paginate large collections
- Rate Limiting: Protect your API with rate limits
- Documentation: Use OpenAPI/Swagger for interactive docs
GraphQL APIs
- Schema First: Design schema before writing resolvers
- Avoid N+1: Use DataLoaders for efficient data fetching
- Input Validation: Validate at schema and resolver levels
- Error Handling: Return structured errors in mutation payloads
- Pagination: Use cursor-based pagination (Relay spec)
- Deprecation: Use
@deprecateddirective for gradual migration - Monitoring: Track query complexity and execution time
Common Pitfalls
- Over-fetching/Under-fetching (REST): Fixed in GraphQL but requires DataLoaders
- Breaking Changes: Version APIs or use deprecation strategies
- Inconsistent Error Formats: Standardize error responses
- Missing Rate Limits: APIs without limits are vulnerable to abuse
- Poor Documentation: Undocumented APIs frustrate developers
- Ignoring HTTP Semantics: POST for idempotent operations breaks expectations
- Tight Coupling: API structure shouldn’t mirror database schema
Resources
- references/rest-best-practices.md: Comprehensive REST API design guide
- references/graphql-schema-design.md: GraphQL schema patterns and anti-patterns
- references/api-versioning-strategies.md: Versioning approaches and migration paths
- assets/rest-api-template.py: FastAPI REST API template
- assets/graphql-schema-template.graphql: Complete GraphQL schema example
- assets/api-design-checklist.md: Pre-implementation review checklist
- scripts/openapi-generator.py: Generate OpenAPI specs from code
What Users Are Saying
Real feedback from the community
Environment Matrix
Dependencies
Framework Support
Context Window
Security & Privacy
Information
- Author
- wshobson
- Updated
- 2026-01-30
- Category
- productivity-tools
Related Skills
Api Design Principles
Master REST and GraphQL API design principles to build intuitive, scalable, and maintainable APIs …
View Details →Senior Backend
Comprehensive backend development skill for building scalable backend systems using NodeJS, Express, …
View Details →Senior Backend
Comprehensive backend development skill for building scalable backend systems using NodeJS, Express, …
View Details →