Home/System Design/API Design

API Design

🍽️ Explain Like I'm 5...

Imagine a restaurant menu! 📋 The menu tells you what food is available and how to order it. You don't need to know how the kitchen makes the food - you just need to know what to ask for!

🍕 The Restaurant Menu Rule:

  • The MENU shows what food you can order (GET = read menu, POST = place order)
  • You tell the waiter WHAT you want and HOW MANY (like sending data)
  • The kitchen makes it and sends it back (the response!)
  • If they're out of pizza, they tell you 'Sorry, not available!' (404 error)

🚀 Why Are APIs Useful?

  • Apps can talk to each other - like asking Google Maps for directions
  • You don't need to know HOW it works, just WHAT to ask for
  • Same menu for everyone - consistent way to get data!

🔧 REST API Principles

REST is like having clear rules for how to order from the restaurant:

HTTP Methods (Actions)

Think of these as different ways to interact with data:

  • GETGET - Read/Retrieve data (like looking at the menu)
  • POSTPOST - Create new data (like placing a new order)
  • PUTPUT - Update/Replace data (like changing your entire order)
  • PATCHPATCH - Partially update data (like adding extra cheese)
  • DELETEDELETE - Remove data (like canceling your order)
REST Methods Example
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// GET - Retrieve users
fetch('https://api.example.com/users')
.then(res => res.json())
.then(data => console.log(data));
// GET - Retrieve specific user
fetch('https://api.example.com/users/123')
.then(res => res.json())
.then(user => console.log(user));
// POST - Create new user
fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com'
})
});
// PUT - Update entire user
fetch('https://api.example.com/users/123', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'John Smith',
email: 'johnsmith@example.com',
age: 30
})
});
// PATCH - Update part of user
fetch('https://api.example.com/users/123', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'newemail@example.com'
})
});
// DELETE - Remove user
fetch('https://api.example.com/users/123', {
method: 'DELETE'
});

🎯 RESTful URL Design

URLs should be clear and predictable, like a well-organized menu:

Best Practices:

  • Use nouns, not verbs: /users (good) vs /getUsers (bad)
  • Use plural names: /users not /user
  • Nested resources: /users/123/orders
  • Use hyphens, not underscores: /user-profiles
  • Lowercase only: /users not /Users
Good vs Bad URL Design
bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# GOOD URL Design
GET /api/v1/users # Get all users
GET /api/v1/users/123 # Get specific user
GET /api/v1/users/123/orders # Get user's orders
GET /api/v1/users/123/orders/456 # Get specific order
POST /api/v1/users # Create user
PUT /api/v1/users/123 # Update user
DELETE /api/v1/users/123 # Delete user
# BAD URL Design
GET /api/getUsers # Don't use verbs
GET /api/user/123 # Use plural
GET /api/Users # Use lowercase
GET /api/user_profiles # Use hyphens not underscores
GET /api/getUserOrders?userId=123 # Use nested resources

📊 HTTP Status Codes

Status codes are like the restaurant's way of telling you what happened:

2xx - Success (Your order worked!)

  • 200 - 200 OK - Request successful
  • 201 - 201 Created - New resource created
  • 204 - 204 No Content - Success but no data to return

4xx - Client Errors (You made a mistake)

  • 400 - 400 Bad Request - Invalid data sent
  • 401 - 401 Unauthorized - Need to login first
  • 403 - 403 Forbidden - You're not allowed
  • 404 - 404 Not Found - Resource doesn't exist
  • 429 - 429 Too Many Requests - Slow down!

5xx - Server Errors (Restaurant's fault)

  • 500 - 500 Internal Server Error - Something broke
  • 503 - 503 Service Unavailable - System is down

🔄 API Versioning

Versioning is like having different editions of the menu:

Why version? When you change the menu, old customers still need to order the old way!

Common Versioning Strategies:

  • URL: URL Versioning: /api/v1/users, /api/v2/users
  • Header: Header Versioning: Accept: application/vnd.api.v1+json
  • Query: Query Parameter: /api/users?version=1
API Versioning Examples
bash
1
2
3
4
5
6
7
8
9
10
11
12
# URL Versioning (Most Common)
GET https://api.example.com/v1/users
GET https://api.example.com/v2/users
# Header Versioning
GET https://api.example.com/users
Headers:
Accept: application/vnd.myapi.v1+json
# Query Parameter Versioning
GET https://api.example.com/users?version=1
GET https://api.example.com/users?version=2

🔒 Authentication & Authorization

Making sure only the right people can order:

1. API Keys

Like a membership card - simple but not super secure

API Key Authentication
javascript
1
2
3
4
5
6
7
8
9
// API Key in Header (Recommended)
fetch('https://api.example.com/users', {
headers: {
'X-API-Key': 'your-api-key-here-abc123'
}
});
// API Key in Query Parameter (Less secure)
fetch('https://api.example.com/users?api_key=your-api-key-here-abc123');

2. OAuth 2.0

Like letting Google verify who you are - secure and flexible

OAuth 2.0 Flow
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Step 1: Get authorization code (user redirected to login)
// https://oauth-provider.com/auth?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_CALLBACK
// Step 2: Exchange code for access token
const tokenResponse = await fetch('https://oauth-provider.com/token', {
method: 'POST',
body: JSON.stringify({
grant_type: 'authorization_code',
code: 'AUTH_CODE_FROM_STEP_1',
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET'
})
});
const { access_token } = await tokenResponse.json();
// Step 3: Use access token to call API
fetch('https://api.example.com/users/me', {
headers: {
'Authorization': `Bearer ${access_token}`
}
});

3. JWT (JSON Web Tokens)

Like a digital ID badge with your info encoded

JWT Authentication
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Login and get JWT
const loginResponse = await fetch('https://api.example.com/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'password123'
})
});
const { token } = await loginResponse.json();
// token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
// Use JWT for authenticated requests
fetch('https://api.example.com/users/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});
// JWT Structure (3 parts separated by dots):
// Header.Payload.Signature
// {
// "alg": "HS256",
// "typ": "JWT"
// }.{
// "userId": "123",
// "email": "user@example.com",
// "exp": 1735689600
// }.[signature]

⏱️ Rate Limiting & Throttling

Prevents abuse by limiting how many requests you can make:

Why? So one customer doesn't use all the restaurant's resources!

Common Limits:

  • 100 requests per hour per user
  • 1000 requests per day per API key
  • 10 requests per second globally
Rate Limit Response Headers
bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# API Response with Rate Limit Info
HTTP/1.1 200 OK
X-RateLimit-Limit: 100 # Total requests allowed
X-RateLimit-Remaining: 87 # Requests remaining
X-RateLimit-Reset: 1735689600 # When limit resets (Unix timestamp)
# When Rate Limit Exceeded
HTTP/1.1 429 Too Many Requests
Retry-After: 3600 # Try again after 1 hour (seconds)
Content-Type: application/json
{
"error": "rate_limit_exceeded",
"message": "You have exceeded your rate limit. Try again in 1 hour.",
"limit": 100,
"remaining": 0,
"reset_at": "2025-01-01T12:00:00Z"
}

🆚 GraphQL vs REST

Two different menu systems:

REST

REST - Fixed menu: You get what the chef decides

REST Pros: Simple, cacheable, widely understood

REST Cons: Over-fetching (too much data) or under-fetching (not enough)

GraphQL

GraphQL - Custom orders: Ask for exactly what you want

GraphQL Pros: Get exactly what you need, single endpoint

GraphQL Cons: More complex, harder to cache

REST - Multiple Requests
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Need 3 separate requests!
// 1. Get user
const user = await fetch('/api/users/123')
.then(r => r.json());
// 2. Get user's posts
const posts = await fetch('/api/users/123/posts')
.then(r => r.json());
// 3. Get user's friends
const friends = await fetch('/api/users/123/friends')
.then(r => r.json());
// Problem: Over-fetching - got all user fields
// even if you only needed name and email
GraphQL - Single Request
graphql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Single request, get exactly what you need!
query {
user(id: 123) {
name
email
posts {
title
createdAt
}
friends {
name
}
}
}
# Response: Only requested fields
{
"data": {
"user": {
"name": "John",
"email": "john@example.com",
"posts": [...],
"friends": [...]
}
}
}

📚 API Documentation

Good docs are like a detailed menu with pictures!

Swagger/OpenAPI

Industry standard for describing REST APIs - interactive and auto-generated

OpenAPI/Swagger Specification (YAML)
yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
openapi: 3.0.0
info:
title: User API
version: 1.0.0
description: API for managing users
servers:
- url: https://api.example.com/v1
paths:
/users:
get:
summary: Get all users
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
post:
summary: Create a new user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewUser'
responses:
'201':
description: User created
/users/{userId}:
get:
summary: Get user by ID
parameters:
- name: userId
in: path
required: true
schema:
type: integer
responses:
'200':
description: Successful response
'404':
description: User not found
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string
NewUser:
type: object
required:
- name
- email
properties:
name:
type: string
email:
type: string

🌍 Real-World API Examples

Twitter API - Post a Tweet

Send a new tweet to Twitter

Twitter API - Post Tweet
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// POST https://api.twitter.com/2/tweets
const response = await fetch('https://api.twitter.com/2/tweets', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
text: 'Hello Twitter! This is my first API tweet!'
})
});
const tweet = await response.json();
// Response:
{
"data": {
"id": "1234567890",
"text": "Hello Twitter! This is my first API tweet!",
"created_at": "2025-01-15T10:30:00.000Z"
}
}

Google Maps API - Get Location

Get coordinates for an address

Google Maps Geocoding API
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// GET https://maps.googleapis.com/maps/api/geocode/json
const address = '1600 Amphitheatre Parkway, Mountain View, CA';
const apiKey = 'YOUR_GOOGLE_API_KEY';
const response = await fetch(
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${apiKey}`
);
const data = await response.json();
// Response:
{
"results": [
{
"formatted_address": "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA",
"geometry": {
"location": {
"lat": 37.4224082,
"lng": -122.0856086
}
}
}
],
"status": "OK"
}

Stripe API - Process Payment

Create a payment charge

Stripe Payment API
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// POST https://api.stripe.com/v1/charges
const response = await fetch('https://api.stripe.com/v1/charges', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_test_YOUR_SECRET_KEY',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
'amount': '2000', // $20.00 in cents
'currency': 'usd',
'source': 'tok_visa', // Token from Stripe.js
'description': 'Payment for product'
})
});
const charge = await response.json();
// Response:
{
"id": "ch_1234567890",
"object": "charge",
"amount": 2000,
"currency": "usd",
"status": "succeeded",
"created": 1735689600
}

Weather API - Get Current Weather

Fetch weather data for a city

OpenWeatherMap API
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// GET https://api.openweathermap.org/data/2.5/weather
const city = 'London';
const apiKey = 'YOUR_OPENWEATHER_API_KEY';
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`
);
const weather = await response.json();
// Response:
{
"name": "London",
"main": {
"temp": 15.5,
"feels_like": 14.2,
"humidity": 72
},
"weather": [
{
"main": "Clouds",
"description": "scattered clouds"
}
],
"wind": {
"speed": 3.5
}
}

✅ Best Practices

  • Use HTTPS always - security first!
  • Version your API from day one
  • Use proper HTTP methods and status codes
  • Provide clear error messages with details
  • Implement pagination for large datasets
  • Use filtering, sorting, and field selection
  • Keep responses consistent in format
  • Document everything clearly

❌ Error Handling

Good error messages help developers fix problems quickly:

Error Response Format
json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Good Error Response Structure
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request contains invalid data",
"details": [
{
"field": "email",
"message": "Email format is invalid"
},
{
"field": "age",
"message": "Age must be greater than 0"
}
],
"timestamp": "2025-01-15T10:30:00Z",
"path": "/api/v1/users"
}
}
// Another Example - Not Found
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "User with ID 999 not found",
"timestamp": "2025-01-15T10:30:00Z",
"path": "/api/v1/users/999"
}
}
// Rate Limit Error
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please try again later.",
"retry_after": 3600,
"limit": 100,
"timestamp": "2025-01-15T10:30:00Z"
}
}

🔑 Key Takeaways

  • 1️⃣APIs are contracts between systems - be clear and consistent
  • 2️⃣REST uses HTTP methods naturally (GET, POST, PUT, DELETE)
  • 3️⃣Status codes communicate what happened (200 = good, 404 = not found)
  • 4️⃣Security is critical - never expose APIs without authentication
  • 5️⃣Good documentation makes APIs easy to use
  • 6️⃣Version your APIs to avoid breaking existing clients

💪 Practice Scenarios

  • Design a RESTful API for a blog system (posts, comments, users)
  • Create an API versioning strategy for a mobile app
  • Implement rate limiting for a public API
  • Design authentication flow using JWT tokens