System Design API Design and REST

An API (Application Programming Interface) is a defined way for two software systems to communicate with each other. It is a contract that specifies what requests a system accepts and what responses it returns. REST (Representational State Transfer) is the most widely used architectural style for building web APIs today.

Think of an API as a restaurant menu. The menu lists what dishes are available, the rules for ordering (specify size, toppings), and what to expect in return. The kitchen's internal workings remain hidden — only the menu interface matters.

Why API Design Matters in System Design

In large systems, different services need to communicate — the payment service talks to the order service, the mobile app talks to the backend, and third-party apps integrate with the platform. APIs are the communication contracts that make all these interactions possible and predictable. Poor API design leads to tight coupling, brittle integrations, and security vulnerabilities.

What Is REST?

REST is an architectural style (not a protocol) for designing networked applications. A REST API uses standard HTTP methods and follows a set of principles that make it simple, stateless, and scalable.

Six Principles of REST

PrincipleWhat It Means
Client-ServerClient and server are separate; client handles UI, server handles data
StatelessEach request contains all information needed; server stores no session state
CacheableResponses can be cached to improve performance
Uniform InterfaceConsistent, predictable URL structure and HTTP methods
Layered SystemClient does not know if it is talking to the final server or a proxy
Code on Demand (optional)Server can send executable code to the client (like JavaScript)

HTTP Methods in REST

REST uses standard HTTP verbs to define the action being performed:

HTTP MethodActionExample
GETRetrieve data (read only, no side effects)GET /users/42 → Get user with ID 42
POSTCreate a new resourcePOST /users → Create a new user
PUTReplace an entire resourcePUT /users/42 → Replace all fields of user 42
PATCHUpdate part of a resourcePATCH /users/42 → Update only the email of user 42
DELETERemove a resourceDELETE /users/42 → Delete user 42

REST URL Design Best Practices

Good URL design makes an API intuitive and easy to use. Resources use nouns (things), not verbs (actions).

Correct REST URL Design

Resource: Users
GET    /users              → List all users
POST   /users              → Create a new user
GET    /users/42           → Get user with ID 42
PUT    /users/42           → Replace user 42
PATCH  /users/42           → Update part of user 42
DELETE /users/42           → Delete user 42

Nested Resources (relationships):
GET    /users/42/orders    → Get all orders for user 42
GET    /users/42/orders/7  → Get order 7 belonging to user 42
POST   /users/42/orders    → Create a new order for user 42

Incorrect REST URL Design

❌ GET  /getUser          (verb in URL)
❌ POST /createUser       (verb in URL)
❌ GET  /user_orders_42   (unclear structure)
❌ POST /deleteUser/42    (wrong method for deletion)

HTTP Status Codes

Every API response includes a status code that tells the client whether the request succeeded or failed, and why.

Code RangeCategoryCommon Examples
2xxSuccess200 OK, 201 Created, 204 No Content
3xxRedirect301 Moved Permanently, 302 Found
4xxClient Error400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
5xxServer Error500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable

Real Example:

Request:  GET /users/99999 (user does not exist)
Response: HTTP 404 Not Found
Body: { "error": "User not found", "code": "USER_404" }

Request:  POST /users (missing required field)
Response: HTTP 400 Bad Request
Body: { "error": "Email is required", "field": "email" }

Request and Response Structure

REST APIs typically use JSON for request and response bodies. A consistent response structure makes the API predictable for all consumers.

Good API Response Structure

Success response:
{
  "status": "success",
  "data": {
    "id": 42,
    "name": "Alice",
    "email": "alice@example.com",
    "createdAt": "2024-01-15T10:30:00Z"
  }
}

Error response:
{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email address is invalid",
    "field": "email"
  }
}

List response (with pagination):
{
  "status": "success",
  "data": [ ... ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 500,
    "totalPages": 25
  }
}

API Versioning

APIs change over time. Versioning ensures old clients keep working when the API evolves, while new clients can use new features.

URL Versioning (most common):
/api/v1/users     ← original version
/api/v2/users     ← new version with changes

Header Versioning:
GET /users
API-Version: 2

Query Parameter Versioning:
GET /users?version=2

URL versioning is the most visible and widely understood approach. Most public APIs (Twitter, Stripe, GitHub) use this approach.

API Authentication

APIs must verify the identity of callers and control access to resources.

API Keys

A long string passed in request headers. Simple but offers no granular permission control.

GET /users
Authorization: ApiKey sk_live_abc123xyz

JWT (JSON Web Token)

A signed token containing user identity and permissions. The server validates the signature without needing to query a database for every request.

GET /users/42
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR...

JWT structure: header.payload.signature
Payload contains: { "userId": 42, "role": "admin", "exp": 1735689600 }

OAuth 2.0

The standard for delegated authorization. Allows a user to grant a third-party app limited access to their account without sharing their password. Used by "Login with Google" and "Login with GitHub" buttons.

API Pagination

Never return all records at once. A database with 10 million users cannot dump everything in one response. Pagination divides results into manageable chunks.

Offset-Based Pagination

GET /users?page=3&limit=20
→ Skip first 40 records (page 3 × limit 20), return next 20

Problem: Slow for large offsets. "Skip 1 million rows" makes the database scan 1 million rows anyway.

Cursor-Based Pagination

GET /users?limit=20&after=cursor_abc123
→ "Give me 20 users after this specific cursor position"

Response includes:
{
  "data": [ ... ],
  "nextCursor": "cursor_xyz789"
}

Advantage: Fast even for millions of records. Used by Twitter, Facebook, Stripe.

REST vs GraphQL vs gRPC

AspectRESTGraphQLgRPC
Data FetchingFixed response structureClient specifies exact fields neededDefined by .proto schema
Over-fetchingCommonNoneNone
SpeedFastModerateVery fast (binary protocol)
Browser SupportNativeNativeLimited
Best ForPublic APIs, CRUD servicesMobile apps, flexible queriesInternal microservice communication
Example UsersTwitter, GitHubGitHub v4, ShopifyGoogle internal services

API Rate Limiting

Rate limiting restricts how many requests a client can make in a given time window. It protects the API from abuse and ensures fair use.

Rate limit: 100 requests per minute per API key

Response when limit exceeded:
HTTP 429 Too Many Requests
{
  "error": "Rate limit exceeded",
  "retryAfter": 45  (seconds until limit resets)
}

Response headers always include:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 23
X-RateLimit-Reset: 1735690000

Idempotency in API Design

An idempotent operation produces the same result no matter how many times it executes. This is critical for reliability — if a network failure causes a client to retry a request, a non-idempotent endpoint might create duplicate records.

GET, PUT, DELETE → Always idempotent
GET  /users/42     → Always returns same user (no side effect)
DELETE /users/42   → First call deletes. Repeated calls: user already deleted, same outcome.

POST → Not idempotent by default
POST /orders       → Creates a new order each time. Network retry = duplicate order!

Solution: Use Idempotency Keys for POST requests
POST /orders
Idempotency-Key: unique-key-12345

Server stores the key. If same key arrives again, return original response without creating duplicate.

Summary

REST is the foundation of modern web API design. Well-designed APIs use intuitive URL structures, correct HTTP methods, consistent response formats, and proper status codes. Authentication, versioning, pagination, and rate limiting are non-negotiable in production APIs. Understanding when to use REST versus GraphQL or gRPC depends on the use case — REST remains the best default for public APIs, while gRPC excels for internal high-performance service-to-service communication.

Leave a Comment