8. API Design

APIs (Application Programming Interfaces) define how services communicate with each other. Good API design is critical for building maintainable, scalable, and developer-friendly systems.


REST (Representational State Transfer)

The most widely used API style for web services. REST is an architectural style, not a protocol.

REST Principles

Principle Description
Client-Server Separation of concerns; client handles UI, server handles data
Stateless Each request contains all information needed; no server-side session
Cacheable Responses must define themselves as cacheable or not
Uniform Interface Consistent resource-based URLs, standard HTTP methods
Layered System Client doesn't know if it talks to server directly or via intermediary
Code on Demand (optional) Server can send executable code (e.g., JavaScript)

HTTP Methods (Verbs)

Method Operation Idempotent Safe Request Body
GET Read a resource Yes Yes No
POST Create a resource No No Yes
PUT Replace a resource entirely Yes No Yes
PATCH Partially update a resource No* No Yes
DELETE Remove a resource Yes No No
HEAD Get headers only (no body) Yes Yes No
OPTIONS Get supported operations Yes Yes No

*PATCH can be made idempotent depending on implementation.

RESTful URL Design

# Resources (nouns, not verbs)
GET    /users              # List all users
GET    /users/123          # Get user 123
POST   /users              # Create a new user
PUT    /users/123          # Replace user 123
PATCH  /users/123          # Update user 123 partially
DELETE /users/123          # Delete user 123

# Nested resources
GET    /users/123/orders          # List orders for user 123
GET    /users/123/orders/456      # Get order 456 for user 123
POST   /users/123/orders          # Create order for user 123

# Filtering, sorting, pagination
GET    /users?status=active&sort=name&page=2&limit=20
GET    /orders?start_date=2025-01-01&end_date=2025-12-31

# Search
GET    /users/search?q=alice

# Actions (when CRUD doesn't fit)
POST   /orders/123/cancel         # Cancel order 123
POST   /users/123/verify-email    # Trigger email verification

HTTP Status Codes

Code Meaning When to Use
200 OK Successful GET, PUT, PATCH, DELETE
201 Created Successful POST (resource created)
204 No Content Successful DELETE (no body)
301 Moved Permanently Resource URL changed permanently
304 Not Modified Cached response is still valid
400 Bad Request Invalid request format/data
401 Unauthorized Missing or invalid authentication
403 Forbidden Authenticated but not authorized
404 Not Found Resource doesn't exist
405 Method Not Allowed HTTP method not supported for this resource
409 Conflict Resource conflict (e.g., duplicate)
422 Unprocessable Entity Valid format but invalid data
429 Too Many Requests Rate limit exceeded
500 Internal Server Error Unexpected server error
502 Bad Gateway Upstream service error
503 Service Unavailable Server overloaded or under maintenance
504 Gateway Timeout Upstream service timeout

REST Response Format

// Success response
{
  "data": {
    "id": "user-123",
    "name": "Alice",
    "email": "alice@example.com",
    "createdAt": "2025-01-15T10:30:00Z"
  }
}

// List response with pagination
{
  "data": [
    {"id": "user-1", "name": "Alice"},
    {"id": "user-2", "name": "Bob"}
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 150,
    "totalPages": 8,
    "nextCursor": "eyJpZCI6InVzZXItMjAifQ=="
  }
}

// Error response
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid email format",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address"
      }
    ]
  }
}

Pagination Strategies

Strategy Pros Cons
Offset-based (?page=2&limit=20) Simple, allows jumping to any page Slow for large offsets; inconsistent with concurrent writes
Cursor-based (?cursor=abc&limit=20) Consistent, performant at any depth Can't jump to arbitrary pages
Keyset (?after_id=123&limit=20) Very fast (uses indexed column) Only forward/backward navigation

REST Versioning

Strategy Example Pros Cons
URL path /v1/users, /v2/users Clear, easy to route URL pollution
Query param /users?version=2 Clean URLs Easy to miss
Header Accept: application/vnd.api.v2+json Clean URLs, proper HTTP Hard to test in browser
Content negotiation Accept: application/vnd.company.v2+json Most "RESTful" Complex

GraphQL

A query language for APIs developed by Facebook (2015). The client specifies exactly what data it needs.

Key Concepts

# Schema definition
type User {
  id: ID!
  name: String!
  email: String!
  orders: [Order!]!
}

type Order {
  id: ID!
  total: Float!
  status: String!
  items: [OrderItem!]!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
}

type Subscription {
  orderStatusChanged(orderId: ID!): Order!
}
# Client query — request exactly what you need
query {
  user(id: "123") {
    name
    email
    orders {
      id
      total
      status
    }
  }
}
// Response — matches the query shape exactly
{
  "data": {
    "user": {
      "name": "Alice",
      "email": "alice@example.com",
      "orders": [
        {"id": "ord-1", "total": 99.99, "status": "shipped"},
        {"id": "ord-2", "total": 49.50, "status": "pending"}
      ]
    }
  }
}

Advantages Over REST

Problem REST GraphQL
Over-fetching GET /users/123 returns ALL fields Client requests only needed fields
Under-fetching Need multiple requests: /users/123 + /users/123/orders Single query gets everything
Versioning /v1/users, /v2/users Add fields without breaking clients
Documentation External (Swagger/OpenAPI) Introspective (self-documenting schema)

GraphQL Challenges

Challenge Description
N+1 problem Resolving nested relations can trigger many DB queries. Solve with DataLoader (batching).
Complexity Deep/nested queries can be expensive. Use query depth limiting and cost analysis.
Caching Harder than REST (single endpoint, POST requests). Use persisted queries or client-side caching (Apollo).
File uploads Not natively supported. Use multipart upload extensions.
Learning curve Schema design, resolvers, type system — more concepts to learn.

gRPC (Google Remote Procedure Call)

A high-performance RPC framework using Protocol Buffers (protobuf) for serialization and HTTP/2 for transport.

Protocol Buffers (Protobuf)

// user.proto
syntax = "proto3";

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
  rpc ListUsers (ListUsersRequest) returns (stream User);
  rpc CreateUser (CreateUserRequest) returns (User);
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
}

message GetUserRequest {
  string id = 1;
}

gRPC Communication Patterns

Pattern Description Use Case
Unary Single request, single response Standard API calls
Server streaming Single request, stream of responses Real-time feeds, large result sets
Client streaming Stream of requests, single response File upload, telemetry
Bidirectional streaming Stream both ways Chat, real-time collaboration
Unary:               Client ──req──→ Server ──res──→ Client
Server streaming:    Client ──req──→ Server ══res══▷ Client (multiple responses)
Client streaming:    Client ══req══▷ Server ──res──→ Client
Bidirectional:       Client ◁══════▷ Server (both stream simultaneously)

gRPC Advantages

  • Performance: Binary serialization (protobuf) is 5-10x faster than JSON.
  • HTTP/2: Multiplexing, header compression, bi-directional streaming.
  • Strong typing: Protobuf schema generates type-safe client/server code.
  • Code generation: Auto-generate clients in 10+ languages from .proto files.
  • Streaming: Native support for streaming data.

gRPC Limitations

  • Browser support: Limited (needs gRPC-Web proxy).
  • Human readability: Binary format — can't inspect with curl.
  • Tooling: Less mature ecosystem than REST.
  • Load balancing: L7 load balancers must understand HTTP/2.

WebSocket

A protocol for full-duplex, persistent communication over a single TCP connection.

Client ←→ Server  (persistent bidirectional connection)

1. Client sends HTTP Upgrade request
2. Server responds with 101 Switching Protocols
3. Both sides can send messages at any time

Use cases: Real-time chat, live dashboards, multiplayer games, stock tickers, collaborative editing.

Comparison with alternatives:

Feature HTTP Polling Long Polling SSE WebSocket
Direction Client → Server Client → Server Server → Client Bidirectional
Latency High (interval) Medium Low Very low
Connection New per request Held open Held open Persistent
Overhead High Medium Low Very low
Protocol HTTP HTTP HTTP WS (TCP)

Server-Sent Events (SSE)

A standard for server-to-client unidirectional streaming over HTTP.

Client ──GET /events──→ Server
Client ◁──event: update\ndata: {...}──
Client ◁──event: update\ndata: {...}──
Client ◁──event: update\ndata: {...}──

Use cases: Live notifications, stock prices, news feeds — where only server-to-client is needed.


REST vs GraphQL vs gRPC

Aspect REST GraphQL gRPC
Protocol HTTP/1.1 or HTTP/2 HTTP (usually POST) HTTP/2
Data format JSON JSON Protobuf (binary)
Schema OpenAPI (optional) Required (SDL) Required (.proto)
Typing Weak Strong Strong
Performance Good Good Excellent
Browser support Excellent Good Limited
Streaming Limited (SSE, WS) Subscriptions Native
Caching Easy (HTTP caching) Complex Complex
Learning curve Low Medium Medium-High
Best for Public APIs, CRUD Flexible client queries Internal microservices

API Design Best Practices

1. Use Proper HTTP Semantics

  • Correct methods (GET for reads, POST for creates).
  • Correct status codes (don't return 200 for errors).
  • Correct headers (Content-Type, Cache-Control).

2. Design for Idempotency

  • PUT and DELETE should be idempotent.
  • Use idempotency keys for POST:
    POST /payments
    Idempotency-Key: unique-request-id-123
    

3. Handle Errors Consistently

{
  "error": {
    "code": "INSUFFICIENT_FUNDS",
    "message": "Account balance is too low",
    "requestId": "req-abc-123",
    "timestamp": "2025-01-15T10:30:00Z"
  }
}

4. Rate Limiting Headers

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1705312800
Retry-After: 30

5. API Security

  • Authentication: OAuth 2.0, API keys, JWT.
  • Authorization: Role-based access control (RBAC).
  • HTTPS: Always encrypt in transit.
  • Input validation: Validate all inputs server-side.
  • Rate limiting: Protect against abuse.

6. Documentation

  • OpenAPI/Swagger for REST.
  • GraphQL schema introspection for GraphQL.
  • Protobuf definitions for gRPC.

API Gateway

A single entry point that routes requests to appropriate backend services.

Client → [API Gateway] → /users/* → User Service
                       → /orders/* → Order Service
                       → /payments/* → Payment Service

Responsibilities:

  • Request routing
  • Authentication & authorization
  • Rate limiting & throttling
  • Request/response transformation
  • Caching
  • Logging & monitoring
  • SSL termination
  • API versioning

Technologies: Kong, AWS API Gateway, Apigee, Nginx, Envoy, Traefik.


Summary

Concept Key Point
REST Simple, widely supported, cacheable — best for public APIs
GraphQL Flexible queries, no over/under-fetching — best for complex UIs
gRPC High performance, streaming, strong typing — best for internal services
WebSocket Bidirectional real-time — best for chat, games, live updates
SSE Server-to-client streaming — best for notifications, feeds
API Gateway Single entry point for routing, auth, rate limiting

Rule of thumb: Use REST for public APIs and simple CRUD. Use GraphQL for complex client-driven queries (especially mobile). Use gRPC for internal service-to-service communication where performance matters.