Welcome to your second lesson in the System Design: Scalability, Caching, and Storage module. In our last session, we learned how to perform back-of-the-envelope calculations to estimate a system's capacity needs. We figured out the scale of the problem—how much traffic, storage, and bandwidth to expect. Now, we'll design the front door to our system: its Application Programming Interface (API).
The API is the contract between your service and its clients (like a web frontend, mobile app, or another backend service). A well-designed API is crucial for scalability, ease of use, and long-term maintainability. Today, you will learn how to design a resource-based API following the principles of REST (Representational State Transfer), and then critically compare its trade-offs with its most popular alternative, GraphQL.
1. REST: Designing with Resources
REST is not a protocol or a standard; it's an architectural style for building scalable web services. It was defined by Roy Fielding in his 2000 dissertation, where he co-described the principles that made the World Wide Web itself so successful. The core idea of REST is to model your system as a collection of resources.
The Metaphor: A Well-Organized Library
Think of a REST API as a large, public library.
- Every book is a resource.
- Each book has a unique address (like a Dewey Decimal number), which is its URI (Uniform Resource Identifier). e.g.,
/books/978-0261102217. - You use a standard set of actions to interact with the books:
GET(read a book),POST(donate a new book to a collection),PUT(replace a damaged book with a new copy), andDELETE(remove a book from the collection). These are the HTTP methods. - The system is stateless: every time you interact with the librarian, you must present your library card and the book's address. The librarian doesn't remember your previous visit. This allows any librarian (any server) to help you.
To dive into the practical rules of REST, we'll start with a video from a Meta Staff Engineer who explains the core concepts used in system design interviews.
API Design in System Design Interviews w/ Meta Staff Engineer
This video, 'API Design in System Design Interviews', provides a concise and practical overview of REST. It establishes the foundational concepts of resources, HTTP methods, and how to structure requests and responses.
Please watch the section covering REST design, from 03:59 to 12:39. Focus on the four key components he discusses: resources as nouns in URLs, the meaning of each HTTP verb (GET, POST, PUT, etc.), the three ways to provide input (path, query, body), and the structure of a response (status code and body).
Core Principles of REST
The video gave a great practical overview. For an "under the hood" understanding, let's codify the principles from the "REST API Design Rulebook," a fantastic resource for mastering the details.
-
Resource-Based Naming:
- URIs should identify resources using plural nouns, not verbs.
- Good:
GET /users/123/posts - Bad:
GET /getUserPosts?userId=123 - Use hyphens (
-) for readability in URI segments, not underscores (_). - For a deep dive into these conventions, the REST API Design Rulebook (resource LINK) sections on "URI Format" and "Resource Modeling" (Chapter 2) are the definitive guide.
-
Standard HTTP Methods for Actions:
The action you want to perform is specified by the HTTP method, not the URL. Each method has specific properties of idempotence and safety that are critical for designing reliable, distributed systems.- Idempotent: Making the same request multiple times produces the same result as making it once.
- Safe: The request does not change the state of the resource on the server.

*This table summarizes the idempotence and safety of common HTTP methods. Understanding this is key to predicting system behavior. For instance, a client can safely retry an idempotent `PUT` or `DELETE` request if it fails, but retrying a non-idempotent `POST` might create duplicate resources.*
* `GET`: Retrieve a resource. (Safe, Idempotent)
* `POST`: Create a new resource within a collection. (Not Safe, Not Idempotent)
* `PUT`: Replace an existing resource entirely. If the resource doesn't exist, it can create it. (Not Safe, Idempotent)
* `PATCH`: Partially update an existing resource. (Not Safe, Not Idempotent)
* `DELETE`: Remove a resource. (Not Safe, Idempotent)
3. Statelessness:
Every request from the client must contain all the information the server needs to fulfill it. The server does not store any client context between requests. This is what allows you to scale horizontally by adding more servers—any server can handle any request because no session state is stored locally.
- HATEOAS (Hypermedia as the Engine of Application State):
This is an advanced principle that distinguishes truly RESTful APIs. The response from the server should contain links that tell the client what other actions are possible. For example, a response for a user account might include a link todeactivate-account. This makes the API self-discoverable and allows the server to evolve its capabilities without breaking clients. The REST API Design Rulebook (resource LINK, Chapter 5) has an excellent section on "Hypermedia Representation."
Python Implementation (using Flask)
Here's how you'd implement a simple REST API for a users resource in Python with Flask. This demonstrates the mapping of HTTP methods and URIs to backend logic.
from flask import Flask, jsonify, request
app = Flask(__name__)
# In-memory "database"
users = {
"1": {"name": "Alice", "email": "alice@example.com"},
"2": {"name": "Bob", "email": "bob@example.com"}
}
# GET a list of all users
@app.route("/users", methods=["GET"])
def get_users():
return jsonify(list(users.values()))
# GET a specific user by ID
@app.route("/users/<user_id>", methods=["GET"])
def get_user(user_id):
user = users.get(user_id)
if user:
return jsonify(user)
return jsonify({"error": "User not found"}), 404 # Use appropriate status codes
# CREATE a new user
@app.route("/users", methods=["POST"])
def create_user():
data = request.json
new_id = str(max(int(k) for k in users.keys()) + 1)
users[new_id] = {"name": data["name"], "email": data["email"]}
# A 201 response includes the new resource's location
response = jsonify(users[new_id])
response.status_code = 201
response.headers["Location"] = f"/users/{new_id}"
return response
if __name__ == '__main__':
app.run(debug=True)
2. The Challenge of REST: Over-fetching and Under-fetching
While REST is powerful and ubiquitous, it has pain points, especially for complex applications with diverse clients (e.g., web and mobile).

This diagram perfectly illustrates the core problem. With REST (left), the client might need to make multiple, chained requests to different endpoints (/users then /orders) to gather all the data for one view. With GraphQL (right), the client sends a single, precise query and gets exactly what it needs in one response.
This leads to two main problems:
- Over-fetching: The client downloads more data than it needs. For example, a
GET /users/1endpoint might return 30 fields, but the mobile app's profile header only needsnameandprofile_picture. This wastes bandwidth, which is critical on mobile networks. - Under-fetching: The client doesn't get enough data in one request and must make multiple calls. To render a user's profile with their five most recent posts, the client first fetches the user (
GET /users/1) and then fetches their posts (GET /users/1/posts). This is the classic N+1 problem, increasing latency.
Facebook faced these issues at scale in the early 2010s with its mobile app and created GraphQL as a solution.
3. GraphQL: A Query Language for your API
GraphQL is not a replacement for REST but a different approach. It's a query language and a server-side runtime that prioritizes client flexibility and efficiency.
The Metaphor: A Custom Buffet Order
If REST is ordering dishes from a fixed menu, GraphQL is like going to a gourmet buffet with a very specific shopping list. You make one trip to a single counter (/graphql), hand over your list (the query), and the chef assembles a custom plate with exactly the items and quantities you requested.
GraphQL vs REST: What's The Difference And When To Use Which?
To see GraphQL in action and understand its core components, let's watch this segment from ArjanCodes. It introduces the main concepts and contrasts them with REST.
Watch from 09:40 to 24:10. This is a longer segment but provides a complete, code-driven walkthrough. Pay attention to: (1) The GraphQL schema definition (types, queries, mutations), (2) How a single query can fetch nested data (e.g., a blog and its author) in one request, and (3) The concept of 'resolvers' that connect the schema to your backend data sources.
Core Principles of GraphQL
- Single Endpoint: Almost all communication happens through a single endpoint, typically
/graphql. The action is determined by the query sent in the request body. - Strongly-Typed Schema: The API's capabilities are defined in a schema using the GraphQL Schema Definition Language (SDL). This schema is the contract between client and server, enabling powerful developer tools, validation, and introspection.
- Client-Specified Queries: The client asks for exactly the fields it needs, including nested relationships. The server responds with a JSON object that mirrors the structure of the query.
Python Implementation (Schema and Query)
This is what the GraphQL equivalent looks like conceptually.
1. Define a Schema (the "menu"):
# Schema Definition Language (SDL)
type User {
id: ID!
name: String
email: String
posts: [Post]
}
type Post {
id: ID!
title: String
content: String
}
# Defines what data can be fetched
type Query {
user(id: ID!): User
users: [User]
}
2. Client sends a query (the "order"):
# GraphQL Query
query GetUserAndPosts {
user(id: "1") {
name
email
posts {
title
}
}
}
3. Server sends a matching response:
{
"data": {
"user": {
"name": "Alice",
"email": "alice@example.com",
"posts": [
{ "title": "My First Post" },
{ "title": "A Post About GraphQL" }
]
}
}
}
Notice how the client got the user's name and email, plus the titles of their posts, all in a single request-response cycle, solving both over-fetching (no user id returned) and under-fetching.
4. The Trade-Offs: REST vs. GraphQL
In a system design interview, choosing your API style and justifying it is key. There is no universally "better" option.
GraphQL vs REST: Key Similarities and Differences ...
This article from Kong provides an excellent summary of the key differences and decision criteria when choosing between REST and GraphQL.
Read the sections from 'Key Differences in Data Fetching' through 'When to Choose GraphQL or REST'. Focus on building a mental checklist of the pros and cons related to performance, caching, versioning, and complexity.
Here is a summary of the most critical trade-offs for an elite-level discussion:
| Feature | REST | GraphQL | Architectural Implication |
|---|---|---|---|
| Data Fetching | Fixed data structure per endpoint. Prone to over/under-fetching. | Client specifies exact data needs in a single query. | GraphQL optimizes network use, crucial for mobile clients. REST is simpler but can be inefficient for complex views. |
| Caching | Simple and mature. Can use standard HTTP caching (CDNs, reverse proxies) on specific URLs. | More complex. Caching must be implemented at the field level within the client (e.g., Apollo) or server. | REST leverages existing web infrastructure for caching, making it easier to scale read-heavy, public content. |
| Performance | Latency can be high due to multiple round trips (under-fetching). | Fewer round trips. Potential for complex, slow queries on the server (N+1 problem if not solved with a DataLoader). |
GraphQL shifts complexity from the client to the server. A well-optimized GraphQL server can be faster, but a naive one can be a database bottleneck. |
| API Evolution | Typically versioned via URLs (/v1, /v2). Can lead to managing multiple API versions. |
Schema is evolved by adding new fields. Old clients continue to work as they don't request the new fields. | GraphQL offers more graceful evolution without "breaking" changes, ideal for rapidly changing applications. |
| Error Handling | Uses standard HTTP status codes (404 Not Found, 401 Unauthorized). |
Typically returns a 200 OK status, with errors included in a separate errors field in the JSON response. |
REST's approach is idiomatic for HTTP but can't report partial success. GraphQL can return partial data alongside errors. |
| Simplicity/Tooling | Conceptually simpler and universally understood. Massive ecosystem. | Steeper learning curve (schema, resolvers). Rich tooling (e.g., Apollo, GraphiQL) provides a great developer experience. | REST is the default, safe choice. GraphQL is a deliberate investment for applications that can leverage its benefits. |
Real-World Examples:
- Stripe API (REST): A classic example of a resource-oriented API. It's clean, predictable, and perfect for a service where operations map neatly to resources like
Customers,Charges, andSubscriptions. - GitHub API (GraphQL): GitHub serves both REST and GraphQL APIs. The GraphQL API is ideal for building complex integrations that need to fetch interconnected data about repositories, issues, pull requests, and users in one go.
Test your understanding! (Interview-Style Question)
You are designing the backend API for a product page on an e-commerce site like Amazon. The page needs to display:
- Product details (name, price, description).
- Seller information (name, rating).
- The top 5 customer reviews.
- A list of 3 related, recommended products.
Would you choose REST or GraphQL for the API that serves this page? Justify your choice by outlining the API design and discussing the key trade-offs.
Show Answer
This is a classic scenario where GraphQL shines, although a well-designed REST API could also work.
Argument for GraphQL:
This view requires data from multiple, distinct resources (Products, Sellers, Reviews).
- Design: You would have a single GraphQL query.
query GetProductPage($productId: ID!) { product(id: $productId) { name price description seller { name rating } reviews(first: 5) { authorName text rating } recommendations(first: 3) { id name imageUrl } } } - Justification: This approach solves the under-fetching problem. A single network request retrieves all necessary data, leading to lower latency and a better user experience. It avoids the "waterfall" of requests a REST client would have to make. It's also resilient to change; if the UI later needs to show the seller's location, you just add
locationto thesellerpart of the query without any backend changes.
Argument for REST (and its challenges):
A RESTful approach would require the client to orchestrate multiple calls.
- Design:
GET /products/{productId}-> returns product details and asellerId.GET /sellers/{sellerId}-> returns seller info.GET /products/{productId}/reviews?limit=5-> returns top reviews.GET /products/{productId}/recommendations?limit=3-> returns recommended products.
- Justification: While this design is clean and resource-oriented, it requires 4 separate network round trips. This increases page load time significantly, especially on mobile. To mitigate this, a common pattern is to create a Backend-for-Frontend (BFF) that acts as an aggregator. The BFF would expose a single, non-standard REST endpoint (e.g.,
GET /product-page-details/{productId}) that internally makes the 4 calls and combines the results. However, at that point, you've essentially hand-rolled a specific version of what GraphQL provides systematically.
Conclusion for the Interviewer: "I would choose GraphQL for this use case because the data required for the product page is a complex graph of related entities. GraphQL allows the client to fetch all this data in a single, efficient request, which is critical for performance. While a REST API could be used, it would likely lead to a 'chatty' client and higher latency, or it would require a custom aggregation endpoint on the backend, which GraphQL provides out-of-the-box."
Conclusion
You've now explored the two dominant paradigms for modern API design. You can design a clean, resource-oriented REST API and also understand its limitations. Crucially, you can articulate when and why GraphQL might be a better choice, focusing on the trade-offs around performance, complexity, and evolvability.
Key Takeaways:
- REST is an architectural style based on resources, standard HTTP verbs, and statelessness. It is simple, robust, and easy to cache.
- GraphQL is a query language for APIs that uses a single endpoint and a typed schema. It empowers clients to request exactly the data they need, solving over- and under-fetching.
- The choice is a critical design decision. Use REST for simple, resource-centric services or public APIs where predictability is paramount. Use GraphQL for complex applications with interconnected data, or when serving diverse clients with varying data needs.
Next Lesson Preview
We've designed the contract for our system, but what about the servers that will run the code to fulfill that contract? How do we ensure they can handle the traffic we estimated in the first lesson? In the next lesson, we will dive into horizontal vs. vertical scaling. You'll learn the fundamental strategies for adding capacity to a system and analyze the critical trade-offs between "scaling up" (bigger machines) and "scaling out" (more machines).