API Design: Building Great REST APIs

A well-designed API is a joy to use. It's intuitive, consistent, and well-documented. This guide teaches you how to design APIs that developers love.

What is an API?

An API (Application Programming Interface) is a contract between software systems. It defines how applications communicate with each other, typically over HTTP.

Why Good API Design Matters

  • Developer experience: Easy to understand and use correctly
  • Maintainability: Consistent patterns reduce bugs
  • Scalability: Well-structured APIs handle growth better
  • Documentation: Clear design makes docs easier to write

REST Principles

REST (Representational State Transfer) is the most common API architecture.

Core Principles

  • Resources: Everything is a resource (users, posts, orders)
  • HTTP Methods: Use verbs to indicate actions
  • Stateless: Each request contains all needed information
  • Uniform Interface: Consistent URL patterns and responses

HTTP Methods

Method Purpose Example
GET Retrieve resource(s) GET /users
POST Create new resource POST /users
PUT Replace entire resource PUT /users/123
PATCH Partial update PATCH /users/123
DELETE Remove resource DELETE /users/123

Designing Endpoints

URL Structure

# Good: Noun-based, hierarchical
GET    /users              # List all users
GET    /users/123          # Get user 123
POST   /users              # Create user
PUT    /users/123          # Update user 123
DELETE /users/123          # Delete user 123

# Nested resources
GET    /users/123/posts    # Get posts by user 123
POST   /users/123/posts    # Create post for user 123

# Bad: Verb-based (anti-pattern)
GET    /getUsers           # Don't do this
POST   /createUser         # HTTP method already indicates action
GET    /deleteUser/123     # Never use GET for mutations

Query Parameters

# Filtering
GET /products?category=electronics&in_stock=true

# Sorting
GET /products?sort=price&order=desc

# Pagination
GET /products?page=2&limit=20
GET /products?cursor=abc123&limit=20

# Searching
GET /products?search=laptop

# Field selection
GET /users/123?fields=name,email,avatar

Request & Response

Request Body (JSON)

// POST /users
{
  "name": "Alice Johnson",
  "email": "alice@example.com",
  "password": "securepassword123"
}

// PATCH /users/123 (partial update)
{
  "name": "Alicia Johnson"
}

Response Structure

// Single resource
{
  "id": 123,
  "name": "Alice Johnson",
  "email": "alice@example.com",
  "created_at": "2024-01-15T10:30:00Z"
}

// Collection with pagination
{
  "data": [
    { "id": 1, "name": "Alice" },
    { "id": 2, "name": "Bob" }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "total_pages": 5
  }
}

HTTP Status Codes

Code Meaning When to Use
200 OK Successful GET, PUT, PATCH
201 Created Successful POST
204 No Content Successful DELETE
400 Bad Request Invalid input data
401 Unauthorized Missing/invalid authentication
403 Forbidden Authenticated but not allowed
404 Not Found Resource doesn't exist
422 Unprocessable Entity Validation failed
500 Internal Server Error Unexpected server error

Authentication

JWT (JSON Web Tokens)

// Login endpoint returns token
POST /auth/login
{ "email": "user@example.com", "password": "secret" }

// Response
{ "token": "eyJhbGciOiJIUzI1NiIs..." }

// Use token in subsequent requests
GET /users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

API Keys

// API key in header
GET /api/data
X-API-Key: your-api-key-here

// Or in query parameter (less secure)
GET /api/data?api_key=your-api-key-here

OAuth 2.0 Flow

  1. User clicks "Login with Google"
  2. Redirect to Google's authorization page
  3. User grants permission
  4. Google redirects back with authorization code
  5. Your server exchanges code for access token
  6. Use token to access Google APIs

Error Handling

Consistent Error Format

// Error response structure
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      },
      {
        "field": "password",
        "message": "Must be at least 8 characters"
      }
    ]
  }
}

// Simple error
{
  "error": {
    "code": "NOT_FOUND",
    "message": "User with ID 123 not found"
  }
}

Error Codes

Use specific error codes that clients can handle programmatically:

  • VALIDATION_ERROR - Input validation failed
  • UNAUTHORIZED - Authentication required
  • FORBIDDEN - Permission denied
  • NOT_FOUND - Resource doesn't exist
  • RATE_LIMITED - Too many requests
  • INTERNAL_ERROR - Server error

Versioning

APIs evolve. Versioning lets you make breaking changes without breaking existing clients.

URL Path Versioning (Recommended)

GET /v1/users
GET /v2/users

# Clear, visible, easy to route

Header Versioning

GET /users
Accept: application/vnd.api+json; version=2

When to Version

  • Breaking changes: Removing fields, changing types
  • Major restructuring: Different response format
  • Additive changes don't need versions: New optional fields are safe

Best Practices

Naming Conventions

  • Use plural nouns for resources: /users, not /user
  • Use kebab-case for multi-word resources: /order-items
  • Use snake_case for JSON fields: created_at
  • Be consistent across your entire API

Security

  • Always use HTTPS - Never expose APIs over HTTP
  • Rate limiting - Prevent abuse and DDoS
  • Input validation - Validate all input server-side
  • Don't expose internal IDs - Use UUIDs when possible
  • Audit logging - Log all API access

Documentation

  • OpenAPI/Swagger - Industry standard for API docs
  • Include examples - Show request/response examples
  • Error documentation - Document all error codes
  • Authentication guide - Step-by-step auth instructions

Related Guides