Back to Blog
6 min read

RESTful API Design: Best Practices for Building Scalable APIs

Learn how to design robust, scalable RESTful APIs with proper resource naming, versioning, authentication, error handling, and documentation strategies.

#API#REST#Backend#Architecture#Web Development
RESTful API Design: Best Practices for Building Scalable APIs

A well-designed API is the foundation of any successful application. This guide covers best practices for designing RESTful APIs that are intuitive, scalable, and maintainable.

Resource Naming Conventions#

Use Nouns, Not Verbs#

# Good - Resources are nouns
GET    /users              # List users
POST   /users              # Create user
GET    /users/123          # Get user
PUT    /users/123          # Update user
DELETE /users/123          # Delete user

# Bad - Verbs in URLs
GET    /getUsers
POST   /createUser
GET    /getUserById/123

Hierarchical Resources#

# Nested resources for relationships
GET    /users/123/orders           # User's orders
GET    /users/123/orders/456       # Specific order
POST   /users/123/orders           # Create order for user

# Alternative: Query parameters for filtering
GET    /orders?user_id=123         # Filter orders by user

Use plural nouns for collections (/users) and singular identifiers for specific resources (/users/123).

Consistent Naming#

# Use kebab-case for multi-word resources
GET /user-profiles
GET /order-items
GET /shipping-addresses

# Use snake_case for query parameters
GET /products?category_id=5&sort_by=price&sort_order=desc

HTTP Methods and Status Codes#

Proper Method Usage#

GET     - Retrieve resources (idempotent, cacheable)
POST    - Create new resources
PUT     - Replace entire resource (idempotent)
PATCH   - Partial update (not idempotent)
DELETE  - Remove resource (idempotent)
OPTIONS - Get allowed methods
HEAD    - Get headers only

Status Code Reference#

// Success responses
200 OK              // Successful GET, PUT, PATCH
201 Created         // Successful POST (include Location header)
204 No Content      // Successful DELETE
 
// Client errors
400 Bad Request     // Invalid request body/parameters
401 Unauthorized    // Missing or invalid authentication
403 Forbidden       // Authenticated but not authorized
404 Not Found       // Resource doesn't exist
409 Conflict        // Resource conflict (duplicate, etc.)
422 Unprocessable   // Validation errors
429 Too Many Requests // Rate limit exceeded
 
// Server errors
500 Internal Error  // Unexpected server error
502 Bad Gateway     // Upstream service error
503 Service Unavailable // Temporary overload

Request and Response Design#

Consistent Response Structure#

// Success response
{
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com",
    "created_at": "2024-01-15T10:30:00Z"
  },
  "meta": {
    "request_id": "req_abc123"
  }
}
 
// Collection response with pagination
{
  "data": [
    { "id": 1, "name": "Product A" },
    { "id": 2, "name": "Product B" }
  ],
  "meta": {
    "total": 150,
    "page": 1,
    "per_page": 20,
    "total_pages": 8
  },
  "links": {
    "self": "/products?page=1",
    "next": "/products?page=2",
    "last": "/products?page=8"
  }
}
 
// Error response
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid data",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      },
      {
        "field": "age",
        "message": "Must be at least 18"
      }
    ]
  },
  "meta": {
    "request_id": "req_xyz789"
  }
}

Filtering, Sorting, and Pagination#

# Filtering
GET /products?category=electronics&brand=apple&min_price=100&max_price=500

# Sorting
GET /products?sort=price:asc,created_at:desc

# Pagination (offset-based)
GET /products?page=2&per_page=20

# Pagination (cursor-based - better for large datasets)
GET /products?cursor=eyJpZCI6MTAwfQ&limit=20

# Field selection (sparse fieldsets)
GET /users/123?fields=id,name,email

# Including related resources
GET /orders/123?include=user,items,shipping_address

API Versioning#

GET /v1/users
GET /v2/users

# Clear and explicit
# Easy to route and cache
# Visible in logs and documentation

Header Versioning#

GET /users
Accept: application/vnd.myapi.v2+json

# Cleaner URLs
# More complex to implement
# Harder to test in browser

Version Deprecation Strategy#

// Response headers for deprecated versions
{
  "headers": {
    "Deprecation": "Sun, 01 Jan 2025 00:00:00 GMT",
    "Sunset": "Sun, 01 Jul 2025 00:00:00 GMT",
    "Link": "</v2/users>; rel=\"successor-version\""
  }
}

Authentication and Authorization#

JWT Authentication#

// Request with Bearer token
GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
 
// Token structure
{
  "header": {
    "alg": "RS256",
    "typ": "JWT"
  },
  "payload": {
    "sub": "user_123",
    "email": "user@example.com",
    "roles": ["user", "admin"],
    "iat": 1704067200,
    "exp": 1704153600
  }
}

API Key Authentication#

// Header-based (recommended)
GET /api/data
X-API-Key: sk_live_abc123xyz
 
// Query parameter (less secure - visible in logs)
GET /api/data?api_key=sk_live_abc123xyz

OAuth 2.0 Scopes#

# Define granular permissions
read:users      - Read user data
write:users     - Create/update users
delete:users    - Delete users
read:orders     - Read order data
admin           - Full access

# Token with scopes
{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:users read:orders"
}

Rate Limiting#

Implementation Headers#

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1704067200

# When rate limited
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067200

Rate Limit Strategies#

// Sliding window implementation
const rateLimiter = {
  // Per-user limits
  authenticated: {
    requests: 1000,
    window: '1 hour'
  },
  // Per-IP limits for unauthenticated
  anonymous: {
    requests: 100,
    window: '1 hour'
  },
  // Per-endpoint limits
  endpoints: {
    'POST /auth/login': { requests: 5, window: '1 minute' },
    'POST /users': { requests: 10, window: '1 hour' }
  }
};

Error Handling#

Comprehensive Error Response#

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "The requested user was not found",
    "details": {
      "resource_type": "User",
      "resource_id": "123"
    },
    "documentation_url": "https://api.example.com/docs/errors#RESOURCE_NOT_FOUND",
    "request_id": "req_abc123"
  }
}

Error Code Catalog#

const errorCodes = {
  // Authentication errors (1xxx)
  INVALID_CREDENTIALS: { status: 401, message: 'Invalid email or password' },
  TOKEN_EXPIRED: { status: 401, message: 'Authentication token has expired' },
  INSUFFICIENT_PERMISSIONS: { status: 403, message: 'You do not have permission' },
  
  // Validation errors (2xxx)
  VALIDATION_ERROR: { status: 422, message: 'Request validation failed' },
  INVALID_FORMAT: { status: 400, message: 'Invalid request format' },
  
  // Resource errors (3xxx)
  RESOURCE_NOT_FOUND: { status: 404, message: 'Resource not found' },
  RESOURCE_CONFLICT: { status: 409, message: 'Resource already exists' },
  
  // Rate limiting (4xxx)
  RATE_LIMIT_EXCEEDED: { status: 429, message: 'Too many requests' },
  
  // Server errors (5xxx)
  INTERNAL_ERROR: { status: 500, message: 'An unexpected error occurred' },
  SERVICE_UNAVAILABLE: { status: 503, message: 'Service temporarily unavailable' },
};

HATEOAS and Hypermedia#

Self-Describing Responses#

{
  "data": {
    "id": 123,
    "status": "pending",
    "total": 99.99
  },
  "links": {
    "self": { "href": "/orders/123" },
    "customer": { "href": "/users/456" },
    "items": { "href": "/orders/123/items" }
  },
  "actions": {
    "cancel": {
      "href": "/orders/123/cancel",
      "method": "POST",
      "title": "Cancel this order"
    },
    "pay": {
      "href": "/orders/123/pay",
      "method": "POST",
      "title": "Process payment"
    }
  }
}

API Documentation#

OpenAPI Specification#

openapi: 3.0.3
info:
  title: My API
  version: 1.0.0
  description: A sample API
 
paths:
  /users:
    get:
      summary: List all users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
 
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email

Conclusion#

Well-designed APIs are intuitive, consistent, and scalable. By following these best practices—proper resource naming, appropriate status codes, comprehensive error handling, and clear documentation—you create APIs that developers love to use.

Key takeaways:

  • Use nouns for resources, HTTP methods for actions
  • Return appropriate status codes
  • Implement proper versioning from the start
  • Design comprehensive error responses
  • Document everything with OpenAPI
OpenAPI Specification
0 views
More Articles