API Design Patterns
Overview
RESTful and GraphQL API design patterns for building robust backend services.
REST API Design
Resource Naming
| Pattern | Example | Description |
|---|---|---|
| Plural nouns | /users, /orders | Collections |
| Nested resources | /users/{id}/orders | Sub-resources |
| No verbs in URLs | /users not /getUsers | Actions via HTTP methods |
| Lowercase, hyphens | /order-items | Consistent casing |
HTTP Methods
| Method | Purpose | Idempotent | Example |
|---|---|---|---|
| GET | Read | Yes | GET /users/123 |
| POST | Create | No | POST /users |
| PUT | Replace | Yes | PUT /users/123 |
| PATCH | Update | Yes | PATCH /users/123 |
| DELETE | Remove | Yes | DELETE /users/123 |
Status Codes
| Code | Meaning | Usage |
|---|---|---|
| 200 | OK | Successful GET/PUT/PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Validation error |
| 401 | Unauthorized | Missing/invalid auth |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate/conflict |
| 422 | Unprocessable | Semantic error |
| 500 | Server Error | Unexpected error |
Request/Response Format
json// Successful response { "data": { "id": "123", "name": "John Doe", "email": "john@example.com" }, "meta": { "requestId": "req_abc123" } } // Error response { "error": { "code": "VALIDATION_ERROR", "message": "Invalid email format", "details": [ { "field": "email", "message": "Must be a valid email address" } ] }, "meta": { "requestId": "req_abc123" } } // List response { "data": [ { "id": "1", "name": "User 1" }, { "id": "2", "name": "User 2" } ], "pagination": { "total": 100, "page": 1, "pageSize": 20, "totalPages": 5 } }
Pagination
Offset Pagination
GET /users?page=2&pageSize=20
json{ "data": [...], "pagination": { "total": 100, "page": 2, "pageSize": 20, "totalPages": 5 } }
Cursor Pagination
Better for large datasets and real-time data.
GET /users?cursor=abc123&limit=20
json{ "data": [...], "pagination": { "nextCursor": "def456", "prevCursor": "xyz789", "hasMore": true } }
Filtering and Sorting
Query Parameters
GET /users?status=active&role=admin # Filtering GET /users?sort=name&order=asc # Sorting GET /users?fields=id,name,email # Field selection GET /users?search=john # Search
Complex Filters
GET /orders?created_gte=2024-01-01&created_lte=2024-12-31 GET /products?price_min=10&price_max=100 GET /users?tags=premium,verified
Versioning
URL Versioning (Recommended)
/api/v1/users /api/v2/users
Header Versioning
GET /users Accept: application/vnd.api+json; version=2
Authentication
Bearer Token
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
API Key
X-API-Key: your-api-key // or in query param (less secure) GET /users?api_key=your-api-key
Rate Limiting
Response Headers
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1609459200
429 Response
json{ "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Too many requests", "retryAfter": 60 } }
Endpoint Examples
User CRUD
POST /api/v1/users # Create user GET /api/v1/users # List users GET /api/v1/users/:id # Get user PUT /api/v1/users/:id # Replace user PATCH /api/v1/users/:id # Update user DELETE /api/v1/users/:id # Delete user # Nested resources GET /api/v1/users/:id/orders # User's orders POST /api/v1/users/:id/orders # Create order for user
Actions (RPC-style)
For non-CRUD operations, use verbs as sub-resources:
POST /api/v1/users/:id/activate POST /api/v1/orders/:id/cancel POST /api/v1/payments/:id/refund
GraphQL Patterns
Schema Design
graphqltype User { id: ID! name: String! email: String! orders: [Order!]! } type Query { user(id: ID!): User users(filter: UserFilter, pagination: Pagination): UserConnection! } type Mutation { createUser(input: CreateUserInput!): User! updateUser(id: ID!, input: UpdateUserInput!): User! deleteUser(id: ID!): Boolean! } input UserFilter { status: UserStatus role: UserRole search: String } input Pagination { first: Int after: String last: Int before: String }
Error Handling
graphqltype MutationResult { success: Boolean! errors: [Error!] user: User } type Error { code: String! message: String! field: String } type Mutation { createUser(input: CreateUserInput!): MutationResult! }
API Documentation
OpenAPI (Swagger)
yamlopenapi: 3.0.0 info: title: User API version: 1.0.0 paths: /users: get: summary: List users parameters: - name: page in: query schema: type: integer responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/UserList' components: schemas: User: type: object properties: id: type: string name: type: string
Best Practices
1. Consistency
- Same response format across all endpoints
- Consistent naming conventions
- Predictable behavior
2. Error Messages
- Clear, actionable messages
- Include error codes for programmatic handling
- Don't expose internal details
3. Idempotency
- Support idempotency keys for POST requests
- Safe to retry without side effects
POST /orders Idempotency-Key: unique-request-id-123
4. HATEOAS (Hypermedia)
Include links to related resources:
json{ "data": { "id": "123", "name": "John" }, "links": { "self": "/users/123", "orders": "/users/123/orders" } }
API design patterns for RESTful and GraphQL services

