Api Design Principles

Design REST and GraphQL APIs that developers love to use

✨ The solution you've been looking for

Verified
Tested and verified by our team
25450 Stars

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.

api-design rest graphql backend web-development software-architecture developer-experience scalability
Repository

See It In Action

Interactive preview & real-world examples

Live Demo
Skill Demo Animation

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

1

Install

claude-code skill install api-design-principles

claude-code skill install api-design-principles
2

Config

3

First Trigger

@api-design-principles help

Commands

CommandDescriptionRequired Args
@api-design-principles design-a-new-rest-apiCreate a well-structured REST API with proper resource modeling, HTTP methods, and error handlingNone
@api-design-principles graphql-schema-designBuild a GraphQL schema with proper type definitions, relationships, and mutation patternsNone
@api-design-principles api-standards-reviewEvaluate existing API designs and establish team standardsNone

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 resources
  • PUT: Replace entire resource (idempotent)
  • PATCH: Partial resource updates
  • DELETE: 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

  1. Consistent Naming: Use plural nouns for collections (/users, not /user)
  2. Stateless: Each request contains all necessary information
  3. Use HTTP Status Codes Correctly: 2xx success, 4xx client errors, 5xx server errors
  4. Version Your API: Plan for breaking changes from day one
  5. Pagination: Always paginate large collections
  6. Rate Limiting: Protect your API with rate limits
  7. Documentation: Use OpenAPI/Swagger for interactive docs

GraphQL APIs

  1. Schema First: Design schema before writing resolvers
  2. Avoid N+1: Use DataLoaders for efficient data fetching
  3. Input Validation: Validate at schema and resolver levels
  4. Error Handling: Return structured errors in mutation payloads
  5. Pagination: Use cursor-based pagination (Relay spec)
  6. Deprecation: Use @deprecated directive for gradual migration
  7. 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

No specific dependencies required

Framework Support

FastAPI ✓ (recommended) Express.js ✓ Spring Boot ✓ Django REST Framework ✓ Apollo Server ✓ Ariadne ✓

Context Window

Token Usage ~3K-8K tokens for comprehensive API designs

Security & Privacy

Information

Author
wshobson
Updated
2026-01-30
Category
productivity-tools