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)
// GET - Retrieve usersfetch('https://api.example.com/users') .then(res => res.json()) .then(data => console.log(data));// GET - Retrieve specific userfetch('https://api.example.com/users/123') .then(res => res.json()) .then(user => console.log(user));// POST - Create new userfetch('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 userfetch('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 userfetch('https://api.example.com/users/123', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'newemail@example.com' })});// DELETE - Remove userfetch('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 URL Design ✓GET /api/v1/users # Get all usersGET /api/v1/users/123 # Get specific userGET /api/v1/users/123/orders # Get user's ordersGET /api/v1/users/123/orders/456 # Get specific orderPOST /api/v1/users # Create userPUT /api/v1/users/123 # Update userDELETE /api/v1/users/123 # Delete user# BAD URL Design ✗GET /api/getUsers # Don't use verbsGET /api/user/123 # Use pluralGET /api/Users # Use lowercaseGET /api/user_profiles # Use hyphens not underscoresGET /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
# URL Versioning (Most Common)GET https://api.example.com/v1/usersGET https://api.example.com/v2/users# Header VersioningGET https://api.example.com/usersHeaders: Accept: application/vnd.myapi.v1+json# Query Parameter VersioningGET https://api.example.com/users?version=1GET 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 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
// 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 tokenconst 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 APIfetch('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
// Login and get JWTconst 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 requestsfetch('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
# API Response with Rate Limit InfoHTTP/1.1 200 OKX-RateLimit-Limit: 100 # Total requests allowedX-RateLimit-Remaining: 87 # Requests remainingX-RateLimit-Reset: 1735689600 # When limit resets (Unix timestamp)# When Rate Limit ExceededHTTP/1.1 429 Too Many RequestsRetry-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
// Need 3 separate requests!// 1. Get userconst user = await fetch('/api/users/123') .then(r => r.json());// 2. Get user's postsconst posts = await fetch('/api/users/123/posts') .then(r => r.json());// 3. Get user's friendsconst 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# 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: 3.0.0info: title: User API version: 1.0.0 description: API for managing usersservers: - url: https://api.example.com/v1paths: /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 foundcomponents: 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
// POST https://api.twitter.com/2/tweetsconst 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
// GET https://maps.googleapis.com/maps/api/geocode/jsonconst 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
// POST https://api.stripe.com/v1/chargesconst 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
// GET https://api.openweathermap.org/data/2.5/weatherconst 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:
// 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