🔌 Lesson 6: API and Interface Design
APIs are like restaurants — they serve requests from hungry clients. A good API, like a good restaurant, has a clear menu, consistent service, and delivers exactly what was ordered. A bad API makes every developer who touches it wish they'd called in sick. Let's learn how to design APIs that developers will love!
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Apply the restaurant analogy to understand the request-response cycle
- Design RESTful endpoints that follow resource-oriented conventions
- Implement a versioning strategy that doesn't break existing clients
- Document authentication flows using sequence diagrams
- Define consistent error response contracts with actionable messages
- Choose between REST and GraphQL based on your project's needs
Estimated Time: 30 minutes
📑 In This Lesson
The Restaurant Menu Analogy
Imagine walking into a restaurant with no menu, where the waiter speaks a different language every visit, and sometimes your food arrives raw. That's what a poorly designed API feels like. A well-designed API is the opposite: the menu is clear (documentation), the orders are predictable (consistent endpoints), and the food arrives exactly as described (reliable responses).
💡 The Menu Is the Contract
In API terms, the menu is your API specification — an OpenAPI/Swagger document that describes every endpoint, every parameter, and every response shape. Just as a restaurant menu sets expectations before you order, a good API spec lets developers know exactly what to send and what they'll get back before they write a single line of code.
RESTful API Design Principles
REST is like the grammar of API design — it gives us rules to communicate clearly. The word "RESTful" gets thrown around loosely, but at its core, REST is about treating everything as a resource identified by a URL and manipulated through standard HTTP verbs.
POST: Create
PUT: Update
DELETE: Remove"] D --> D1[Each request carries all context] E --> E1[Consistent URL patterns] F --> F1[API knows nothing about the UI]
⚠️ REST Is a Style, Not a Standard
There's no official "REST certification." Two teams can both claim to be RESTful and disagree on nearly every design choice. What matters isn't purity — it's consistency. Document your conventions (plural nouns? nested routes? query params or body?) in your SDD and stick to them across every endpoint.
📖 The Verb Cheat Sheet
GET is safe and idempotent — calling it 100 times has the same effect as calling it once. PUT is idempotent but not safe — it changes state, but repeating it doesn't change state further. POST is neither — every call may create a new resource. DELETE is idempotent — deleting something twice gives you the same result (it's gone). Understanding idempotency is critical for designing retry-safe APIs.
Real-World Example: E-Commerce API
Let's see these principles in action. Below is an e-commerce product catalog API that follows REST conventions: resource-based URLs, proper HTTP verbs, pagination, and consistent JSON response shapes.
Product Catalog API Endpoints
GET /api/v1/products — List products with pagination
{
"products": [
{
"id": "prod_123",
"name": "Wireless Headphones",
"price": 79.99,
"stock": 150,
"category": "electronics"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 485
}
}
POST /api/v1/orders — Create a new order
// Request
{
"customer_id": "cust_456",
"items": [
{ "product_id": "prod_123", "quantity": 2 }
],
"shipping_address": { ... }
}
// Response (201 Created)
{
"order_id": "ord_789",
"status": "pending",
"total": 159.98,
"created_at": "2024-03-15T10:30:00Z"
}
✅ What Makes These Endpoints Good
Notice the patterns: resources are plural nouns (/products, /orders), IDs use prefixed strings (prod_123, ord_789) so you can tell what type of ID it is at a glance, pagination metadata is separate from the data array, and the POST response returns the created resource (not just an ID) so the client doesn't need a second request.
API Versioning: The Evolution Strategy
APIs evolve, and old clients can't be forced to update overnight. Versioning lets you ship breaking changes without breaking existing integrations. The question isn't whether to version — it's how.
💡 Three Versioning Approaches
URL path versioning (/api/v1/products) is the most visible and easiest to understand. It's the most common choice and the one we recommend for SDDs.
Header versioning (Accept: application/vnd.myapi.v2+json) keeps URLs clean but is harder to test in a browser and easier to forget.
Query parameter versioning (/api/products?version=2) is a middle ground but can create confusing cache keys.
🚫 The "Just Don't Break Anything" Fantasy
Some teams try to avoid versioning by making every change backward-compatible. This works for a while — until you need to rename a field, change a data type, or remove a deprecated feature. The result is an API littered with legacy fields that nobody is sure whether they can delete. Plan for versioning from day one.
Authentication & Security: The Bouncer at the Door
Authentication answers "who are you?" and authorization answers "what are you allowed to do?" The most common pattern for APIs is token-based authentication using JWTs. Here's the full flow:
⚠️ JWT Pitfalls to Document
JWTs are stateless — once issued, the server can't revoke them (without a blocklist). Document the token lifetime in your SDD (15 minutes is a reasonable default for access tokens, days/weeks for refresh tokens). Also document where tokens are stored on the client: httpOnly cookies for web apps (protects against XSS), secure storage for mobile apps. Never localStorage.
Error Handling: Speaking the Same Language
Nothing destroys developer trust faster than inconsistent error responses. One endpoint returns {"error": "bad request"}, another returns {"message": "invalid"}, and a third returns bare HTML. Define a single error contract in your SDD and enforce it across every endpoint.
💡 The Error Contract Template
Every error response from your API should include at minimum:
{
"error": "machine_readable_code",
"message": "Human-readable explanation",
"status": 400,
"request_id": "req_abc123"
}
The error field is a stable string that client code can switch on. The message field is for developers reading logs. The request_id field connects client errors to server-side logs — invaluable for debugging production issues.
📖 Status Code Groups to Know
2xx — Success: 200 (OK), 201 (Created), 204 (No Content — for successful deletes).
4xx — Client Error: 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden — authenticated but not allowed), 404 (Not Found), 409 (Conflict), 429 (Rate Limited).
5xx — Server Error: 500 (Internal), 502 (Bad Gateway), 503 (Service Unavailable). Never leak stack traces in production 5xx responses.
GraphQL vs REST: Choose Your Fighter
This isn't a "one is better" debate — it's a trade-off analysis. REST is the safe, well-understood default. GraphQL solves specific problems (deeply nested data, multiple client types) at the cost of complexity. Document your choice and your reasoning in the SDD.
✅ When to Choose What
Choose REST when: your data model is resource-oriented, caching matters, your team is familiar with HTTP conventions, or you're building a public API (REST is easier for third-party developers to consume).
Choose GraphQL when: different clients need different slices of the same data (mobile wants less, dashboards want more), you have deeply nested relationships, or you're tired of creating one-off endpoints for every UI variation.
Consider both: many production systems use REST for public APIs and GraphQL for internal front-end-to-backend communication.
API Documentation & Rate Limiting
Even the best-designed API is useless if nobody knows how to use it. API documentation isn't a nice-to-have — it's the developer experience.
⚠️ Rate Limiting: Don't Skip It
Without rate limiting, a single misbehaving client can bring your API down for everyone. Document your strategy in the SDD:
Fixed window: simple (1000 requests per hour) but bursty at window boundaries. Sliding window: smoother but more complex. Token bucket: allows controlled bursts, refills over time — the best balance for most APIs.
Always return rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) so clients can self-throttle before hitting 429 errors.
The API Designer's Checklist
📏 Ten Principles to Design By
1. Use nouns for resources: /products not /getProducts. The HTTP verb already tells you the action.
2. Version your API: /api/v1/... from day one. It's free now and expensive to add later.
3. Use proper HTTP status codes: 201 for created, 204 for no content, 409 for conflicts. Don't return 200 for everything.
4. Support filtering and pagination: ?page=2&limit=20&category=electronics. Without pagination, your list endpoints become time bombs as data grows.
5. Return consistent response shapes: Success responses always have the same structure. Error responses always have the same structure. No surprises.
6. Include request IDs: Every response should include a request_id that maps to server-side logs. This turns "the API is broken" support tickets into 5-minute investigations.
7. Document everything: If it's not documented, it doesn't exist. Use OpenAPI/Swagger and generate docs automatically.
8. Use HTTPS everywhere: Security is not optional. Never support plain HTTP in production — not even for internal APIs.
9. Implement CORS properly: Don't use Access-Control-Allow-Origin: * on authenticated endpoints. Whitelist specific origins.
10. Design for failure: Every network call can fail. Document timeout behavior, retry policies, and circuit-breaker patterns in your SDD.
🚫 The "Internal API, Who Cares" Trap
Internal APIs become external APIs more often than you'd think — a partner integration, a mobile app, a microservice that another team adopts. Design every API as if it will eventually be consumed by people who can't walk over to your desk and ask questions. Your future self will thank you.
In the next lesson, we'll explore security considerations — how to build fortresses, not just houses. You'll learn to document authentication strategies, encryption requirements, input validation, and the threat modeling process.
💡 Key Takeaway
An API is a contract between your system and every client that depends on it. Design it like a public promise: be explicit about what you accept, what you return, how you handle errors, and when things will change. Document every decision in your SDD — because the worst API bugs aren't technical, they're expectation mismatches.