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
code
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.
code
GET /users?cursor=abc123&limit=20
json
{
"data": [...],
"pagination": {
"nextCursor": "def456",
"prevCursor": "xyz789",
"hasMore": true
}
}
Filtering and Sorting
Query Parameters
code
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
code
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)
code
/api/v1/users /api/v2/users
Header Versioning
code
GET /users Accept: application/vnd.api+json; version=2
Authentication
Bearer Token
code
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
API Key
code
X-API-Key: your-api-key // or in query param (less secure) GET /users?api_key=your-api-key
Rate Limiting
Response Headers
code
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
code
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:
code
POST /api/v1/users/:id/activate POST /api/v1/orders/:id/cancel POST /api/v1/payments/:id/refund
GraphQL Patterns
Schema Design
graphql
type 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
graphql
type 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)
yaml
openapi: 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
code
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