System Design Foundations
API Design
APIs are the contracts between system components. Good API design reduces coupling, enables evolution, and makes systems easier to use and maintain. This guide covers REST, GraphQL, gRPC, and the essential patterns for production APIs.
- REST — Resource-oriented, stateless, cacheable HTTP interfaces
- GraphQL — Client-driven queries with a typed schema
- gRPC — High-performance, contract-first RPC with Protocol Buffers
Design APIs for the consumer, not the implementer.
What Is an API?
An API (Application Programming Interface) defines the boundary between software components—how they communicate, what operations are available, and what data formats are used.
DfAPI (Application Programming Interface)
An API is a formally specified interface that defines the contracts, protocols, and data formats for communication between software components. A well-designed API abstracts implementation details, enabling consumers to interact with a system without knowing its internal structure.
API Paradigms
REST (Representational State Transfer)
REST is an architectural style for designing networked applications, defined by Roy Fielding in his 2000 doctoral dissertation.
DfREST Constraints
REST is defined by six constraints:
- Client-server: Separation of concerns between UI and data storage
- Stateless: Each request contains all information needed to process it
- Cacheable: Responses must define themselves as cacheable or not
- Uniform interface: Consistent resource identification and manipulation
- Layered system: Client cannot tell if connected directly to server
- Code on demand (optional): Servers can extend client functionality
REST API Design Principles
GET /api/v1/users # List users
GET /api/v1/users/123 # Get user 123
POST /api/v1/users # Create user
PUT /api/v1/users/123 # Replace user 123
PATCH /api/v1/users/123 # Update user 123
DELETE /api/v1/users/123 # Delete user 123
GET /api/v1/users/123/posts # List posts by user 123
REST best practices: use plural nouns for resources (/users, not /user), use HTTP methods for operations, return appropriate status codes, and use query parameters for filtering, sorting, and pagination.
REST Response Format
{
"data": {
"id": "123",
"name": "Alice",
"email": "alice@example.com",
"created_at": "2025-01-15T10:30:00Z"
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2025-01-15T10:30:01Z"
}
}
REST Pagination
{
"data": [...],
"pagination": {
"total": 1250,
"page": 3,
"per_page": 20,
"total_pages": 63,
"next": "/api/v1/users?page=4&per_page=20",
"prev": "/api/v1/users?page=2&per_page=20"
}
}
GraphQL
GraphQL is a query language and runtime for APIs, developed by Facebook.
DfGraphQL
GraphQL is a query language for APIs that gives clients the power to ask for exactly what they need. It uses a strongly-typed schema to define the data graph, and clients can query, mutate, and subscribe to data through a single endpoint.
GraphQL vs REST
| Aspect | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple endpoints | Single endpoint |
| Data fetching | Fixed structure per endpoint | Client specifies exact fields |
| Over-fetching | Common | Eliminated |
| Under-fetching | Common (N+1 problem) | Eliminated |
| Type system | Optional (OpenAPI) | Built-in |
| Caching | HTTP caching native | Requires custom caching |
| File upload | Native | Requires specification extension |
GraphQL Schema Example
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
body: String!
author: User!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String): User!
deleteUser(id: ID!): Boolean!
}
GraphQL's N+1 problem: querying users with their posts can generate N+1 database queries (1 for users, N for each user's posts). Solutions include DataLoader (batching and caching) and JOIN-based strategies.
gRPC (Google Remote Procedure Call)
gRPC is a high-performance RPC framework using Protocol Buffers and HTTP/2.
DfgRPC
gRPC is a language-agnostic, high-performance RPC framework that uses Protocol Buffers (protobuf) for serialization and HTTP/2 for transport. It supports four communication patterns: unary, server streaming, client streaming, and bidirectional streaming.
gRPC Communication Patterns
| Pattern | Description | Use Case |
|---|---|---|
| Unary | Single request, single response | Standard CRUD |
| Server streaming | Single request, stream of responses | Real-time updates |
| Client streaming | Stream of requests, single response | File upload, aggregation |
| Bidirectional | Stream of requests, stream of responses | Chat, real-time sync |
Protocol Buffer Example
syntax = "proto3";
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User);
rpc CreateUser (CreateUserRequest) returns (User);
}
message User {
string id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
string id = 1;
}
Protocol Comparison
| Feature | REST | GraphQL | gRPC |
|---|---|---|---|
| Protocol | HTTP/1.1 | HTTP | HTTP/2 |
| Format | JSON | JSON | Protobuf (binary) |
| Schema | OpenAPI (optional) | Required | Required |
| Streaming | Not native | Subscriptions | Native |
| Performance | Moderate | Good | Excellent |
| Browser support | Native | Native | Requires gRPC-Web |
| Learning curve | Low | Medium | Medium |
API Versioning
APIs evolve over time. Versioning strategies manage breaking changes.
DfAPI Versioning
API versioning is the practice of managing changes to an API by maintaining multiple versions simultaneously. This allows consumers to migrate at their own pace while the API evolves.
Versioning Strategies
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL path | /api/v1/users | Explicit, cacheable | URL proliferation |
| Query parameter | /api/users?version=1 | Easy to add | Less clean URLs |
| Header | Accept: application/vnd.api.v1+json | Clean URLs | Hidden from URL bar |
| Content negotiation | Accept: application/vnd.api.v1+json | RESTful | Complex to implement |
URL path versioning (/v1/, /v2/) is the most common and pragmatic approach. It's explicit, easy to route, and works well with CDNs and caches. Header-based versioning is more "RESTful" but adds complexity.
Rate Limiting
Rate limiting protects APIs from abuse and ensures fair resource usage.
DfRate Limiting
Rate limiting is a technique to control the number of requests a client can make to an API within a given time window. It protects against abuse, ensures fair usage, and maintains system stability.
Rate Limiting Algorithms
| Algorithm | How It Works | Trade-off |
|---|---|---|
| Fixed Window | Count requests in fixed time windows | Simple but allows burst at window boundary |
| Sliding Window Log | Timestamp-based, checks recent requests | Memory intensive, most accurate |
| Sliding Window Counter | Weighted average of current and previous window | Good balance of accuracy and efficiency |
| Token Bucket | Tokens refill at fixed rate, each request consumes one | Allows controlled bursts |
| Leaky Bucket | Requests queue, processed at fixed rate | Smooths traffic but adds latency |
Token Bucket Algorithm
Here,
- =Available tokens at time t
- =Maximum tokens (burst size)
- =Token refill rate (tokens per second)
- =Last request time
Token Bucket Calculation
API allows 100 requests/second with a burst capacity of 500.
If a client hasn't made requests for 5 seconds: tokens = min(500, 0 + 100 × 5) = 500 (full bucket)
If a client sent 200 requests 0.5 seconds ago: tokens = min(500, 300 + 100 × 0.5) = min(500, 350) = 350 The next request consumes 1 token, leaving 349.
Rate Limit Response Headers
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
Retry-After: 30
API Security
Authentication and Authorization
| Mechanism | Description | Use Case |
|---|---|---|
| API Keys | Simple token in header | Internal services, simple auth |
| OAuth 2.0 | Delegated authorization | Third-party access |
| JWT | Self-contained tokens | Stateless authentication |
| mTLS | Certificate-based | Service-to-service |
OAuth 2.0 Flows
Practice Exercises
-
Design: Design a RESTful API for a URL shortener. Include endpoints for creating, retrieving, and deleting short URLs, plus analytics. Consider rate limiting and versioning.
-
Compare: You're building a social media feed API. Compare REST, GraphQL, and gRPC for this use case. Which would you choose and why?
-
Implementation: Design a rate limiting strategy for an API that serves 10,000 requests/second. Which algorithm would you use? How would you handle distributed rate limiting across multiple servers?
-
Security: Design an authentication strategy for a system with three user types: anonymous readers, authenticated users, and admin users. How do you handle token refresh and revocation?
Key Takeaways:
- REST is resource-oriented, stateless, and cacheable; best for CRUD-heavy APIs
- GraphQL eliminates over/under-fetching by letting clients specify exact fields needed
- gRPC offers high-performance binary serialization with native streaming support
- Rate limiting protects APIs—token bucket is the most flexible algorithm
- API versioning manages breaking changes; URL path versioning is the most practical
What to Learn Next
-> Databases SQL vs NoSQL, indexing, replication, and sharding.
-> Caching Strategies Redis, Memcached, cache invalidation, and write strategies.
-> Load Balancing Algorithms, health checks, and L4 vs L7.
-> Message Queues Kafka, RabbitMQ, event-driven architecture.
-> Microservices Service decomposition, discovery, and API gateways.
-> Networking Fundamentals TCP/IP, HTTP, DNS, and network latency.