DEV Community

Cover image for Full Stack System Design 3 : REST APIs from Architectures to Status Codes
Prasun Chakraborty
Prasun Chakraborty

Posted on • Edited on

Full Stack System Design 3 : REST APIs from Architectures to Status Codes

In our last episode, we explored the networking protocols that make data flow across the web, like TCP for reliability, HTTP/HTTPS for requests, and WebSockets for real time chats. Building on our web foundations, we're expanding to full-stack system design with the Episode 3, which dives into how applications communicate in more structured ways, starting with APIs. We'll focus on REST APIs first, using everyday analogies to keep it simple. This sets the stage for later topics in the series, like gRPC, GraphQL, and communication patterns. Let's get into it with the basics of app architectures and how REST fits in.

The Evolution of Tiers (The Restaurant Analogy)

To explain how apps are built, think of it like running a restaurant. We'll use this "digital kitchen" idea to break down the three main architectures.

  1. 1-Tier Architecture: The Food Truck Everything happens in one spot. The guy taking your order is also cooking and pulling stuff from the fridge. In software, this means the user interface, business logic and data storage are all on the same machine, just like an old desktop app. The issue with this is if a crowd suddenly shows up, the whole thing slows down or crashes. You can't easily add help without starting over.

1-Tier Architecture

  1. 2-Tier Architecture: The Small Diner Now the operation is split into two distinct spaces. There’s a front counter where customers place orders and interact with staff, and there’s a back kitchen where the actual work happens. The cashier focuses on talking to customers, taking orders, maybe doing light checks like “is this a valid order,” while the kitchen handles cooking, pricing rules, checking ingredients, and pulling items from the fridge. In software terms, the client handles the user interface and basic validation, while the server contains both the business logic and the database. This setup can handle far more customers than a food truck because multiple clients can talk to the same kitchen at once, and changes to the kitchen don’t necessarily require rebuilding the counter. The problem is that the kitchen becomes responsible for everything beyond taking the order. As the menu grows and customers want more custom options, all those special rules pile up in the kitchen, making it harder to manage and slower to respond. If demand increases or storage needs grow, you can’t just add another fridge or cook independently without redesigning the whole kitchen, because the logic and the data live together in one place.

2-Tier Architecture

  1. 3-Tier Architecture: The Fine-Dining Restaurant Now the restaurant is fully professional and built to scale. The front-of-house staff focuses only on the guest experience, guiding customers through the menu and taking orders without knowing how the food is prepared or where ingredients are stored. Orders are passed to the kitchen, where chefs concentrate purely on cooking and applying business rules, completely independent of how the order was taken or who took it. Behind all of this is a separate storage area where ingredients, recipes, and inventory are managed on their own, isolated from the chaos of the dining room and the heat of the kitchen. In software, this means the presentation layer handles user interaction, the application layer processes logic and decisions, and the data layer stores and manages information independently. Because each tier has a single responsibility, you can add more waiters, more chefs, or a bigger warehouse without redesigning the entire restaurant, making it far easier to handle growth, change menus, or support huge spikes in customers without the system slowing down or breaking.

3-Tier Architecture

What is an API? What is REST?

Before we talk REST, let's define API i.e. Application Programming Interface. In the restaurant, it's like the order slip the waiter hands to the chef. The frontend (waiter) writes what’s needed and passes it to the backend (chef). The waiter doesn't need to know how the oven works; they just follow the format. It's a simple contract between systems, letting them talk without knowing each other's insides.
REST, or Representational State Transfer, is a style for building APIs. If API is the order slip, REST is the standard way to fill it out, so everyone uses the same language.
Instead of random notes, REST uses consistent "nouns" and "verbs" over HTTP (from Episode 2).
Nouns (Resources): Things like /orders, /menu-items, /customers—the stuff you're dealing with.
Verbs (Methods): GET (fetch something), POST (create new), PUT (update), DELETE (remove).

API and REST API

Grammar and Vocabulary

If the 3-Tier architecture is the building, these topics explain how we navigate the hallways and what actions we can perform once we get there.
Let's start with the parts of a URL, since that's the starting point for any API call. Think of it as the map your browser (or app) to find and interact with resources on the server.

Anatomy of a URL: The Map

A URL (Uniform Resource Locator) Is like an address, a full set of directions for the browser on where to go and what to do. It guides the request from your client to the exact spot on the server, following the HTTP rules we talked about in Episode 2. Let's split it into parts with examples.

  1. Scheme (Protocol): Like https:// or http://. It tells the browser (or your app) what protocol to use for communication.Like https:// this tells how to talk securely, as we covered in Episode 2 with HTTP/HTTPS.

  2. Host (Domain): This is the main address, like api.prasunchakra.com. It points to the specific server or subdomain where the API is hosted. For This could be separate from your main site prasunchakra.com to keep API traffic separate and organized. For example, if prasunchakra.com has a blog, the host api.prasunchakra.com might handle requests for posts and users.

  3. Path: This comes after the host, like /v1/users/123. It's the route to the exact resource. In REST design, paths are always nouns representing things and never an actions. So /users for a list of users, or /users/123 for a specific one. The /v1 is a version number, common in APIs to let you update without breaking old clients. For prasunchakra.com, a path like /v1/posts/abc might fetch a blog post by ID abc. This makes APIs intuitive, as if you are navigating like folders on a computer.

  4. Query Strings: These start with ? and use & to separate params, like ?format=json&sort=desc. They're optional extras that tweak the request without changing the path. Think of them as filters, format=json says "send data as JSON, not XML," and sort=desc means "oldest first." For example say prasunchakra.com's API, you might do /posts?author=jagd&limit=10 to get the last 10 posts by Jagdish. They're great for searches.

  5. Fragment: This is the # part at the end, like #profile. It's for client side only. The browser uses it to scroll or jump to a section after the page loads. Servers never see it, so it's not for APIs. In prasunchakra.com, if a post has #comments, your browser scrolls to the comments without a new request. Handy for single page apps.

Anatomy of URL

CRUD and the HTTP Methods: The Actions

REST is all about doing basic operations on resources, called CRUD (Create, Read, Update, Delete). We tie these to HTTP methods to keep things standard and easy to predict. This predictability is why REST is so popular, it lets developers guess how an API works without docs. Let's expand on each with examples and why they matter.

  1. Create : This sends data to make something new. It's like submitting a form where the body of the request holds the details (e.g., in JSON). For example POST to /users with {"name": "New User", "email": "user@example.com"} creates a account. Servers respond with 201 Created and maybe with the new ID.

  2. Read : This grabs data without changing anything. It's safe and repeatable. For example GET /posts fetches all blog posts, or GET /posts/123 gets one. Servers send 200 OK with the data. This method is cacheable, speeding up apps.

  3. Update / : These change existing resources. PUT replaces the whole thing like send the full data to /users/123, and it overwrites everything. PATCH is partial it just send changes, like {"email": "new@email.com"} to update a single field. PUT is idempotent (repeatable safely), PATCH might not be. Choose based on if you want full control or efficiency.

  4. Delete : This removes a resource. A simple DELETE /users/123 and it's gone. Server replies 204 No Content if successful. For example DELETE /comments/xyz clears a comment. It's idempotent because deleting twice is fine (second time just says not found).

Rest & Crud

CRUD also maps well to databases like Create=INSERT, Read=SELECT, etc. making REST a natural fit for web apps.

The "Specialist" Methods

CRUD covers most stuff, but there are a few extra HTTP methods for special cases, like tools you pull out only when needed. They're less common but useful for optimization, security, or debugging. Let's dive deeper with use cases and caveats.

  • HEAD: Same as GET, but skips the body and just gets headers. It's like peeking at mail without opening it. For example, HEAD /images/bigfile.jpg checks Content-Length (size) or Last-Modified (date) to see if it's worth downloading there by saving bandwidth on mobiles or low network scenarios. Response is 200 OK with headers only and no data transfer.

  • OPTIONS: Asks what methods a URL supports. Server replies with Allow: GET, POST, etc. Crucial for CORS, when your frontend on prasunchakra.com calls an API on another domain, the browser sends OPTIONS first as a "preflight" to check if it's allowed (via headers like Access-Control-Allow-Origin). If not, the real request is blocked for security. Use it to test API capabilities without full calls.

  • CONNECT: Sets up a tunnel through proxies, turning the connection into a pipe for other protocols. Rare in APIs, but used for things like HTTPS over proxies or VPNs. For example behind a corporate firewall, CONNECT might establish a secure link.

  • TRACE: Loops back the request for testing, server echoes what it got, including headers. Good for debugging proxies or seeing if requests are tampered with. But security risks are often disabled because attacks like Cross Site Tracing (XST) can use it to steal cookies or headers.

Headers : Metadata that Drives the Web.

Headers are like the metadata or notes attached to your HTTP requests and responses. They're key for telling the server what you want and how the client should handle what's sent back. Think of headers as the envelope around your letter: the address is the URL, but headers add the stamps, return address, and special instructions.

Request Headers: The Client’s Instructions

When your frontend or a tool like Postman sends a request to the API, it includes these headers to give context, preferences, and security details. They're sent in the HTTP request and servers read them to decide how to process and respond. Let's break them down with more detail and why they matter in real world REST setups.

  • Identity & Context: These help the server know who's asking and from where.
  1. Host: This is mandatory in HTTP/1.1 and tells the server which domain the request is for, like servers often host multiple sites on one IP (virtual hosting), so this routes it right. If you forget this, the server might reject the request with a 400 Bad Request.

  2. User-Agent: This strings together your browser or app info, e.g., User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36. Servers use it to optimize, like sending mobile friendly JSON if it's from an android app, while the backend might log this for analytics or serve lighter data to slow devices. But users can fake it, so we don't rely on it for security it's only for better user experience.

  3. Origin and Referer: Origin is for security, showing just the domain and port (e.g., Origin: https://prasunchakra.com) during CORS checks . Referer gives the full previous URL, like Referer: https://prasunchakra.com/blog/post1. Use Origin for strict checks and Referer for tracking how users arrived. In APIs, if Origin doesn't match allowed domains, the server blocks cross site requests to prevent attacks.

  • Content Negotiation: This is like negotiating the menu, client says what it can handle, server picks.
  1. Accept: Specifies response formats, e.g., Accept: application/json, text/xml. For example in API, if you GET /posts and set Accept: application/json, the server sends JSON; otherwise, maybe HTML. If unsupported, it might return 406 Not Acceptable. This makes APIs flexible for different clients (web vs. mobile).

  2. Accept-Language: User's language prefs, e.g., Accept-Language: en-US, hi;q=0.5 (English first, Hindi second with lower priority). Servers can localize responses, like sending Hindi error messages for prasunchakra.com users in India. q values (0-1) weight preferences.

  3. Accept-Encoding: Compression support, e.g., Accept-Encoding: gzip, deflate, br (Brotli). Servers compress data to speed up transfers. For large JSON from prasunchakra.com's /posts endpoint, gzip shrinks it 70%, saving bandwidth on slow connections.

  • Security & State: Crucial for protected APIs.
  1. Authorization: Like showing ID, e.g., Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... (a JWT token). For example to create a new post, you need this after login. Types include Basic (username:password base64'd—unsafe), Bearer (tokens), or API keys. Without it you get 401 Unauthorized.

  2. Cookie: Client stored data from previous responses, e.g., Cookie: sessionId=abc123; theme=dark. For example it might track logged in state.

  3. Conditional/Caching: For efficiency, avoiding unnecessary data.
    If-Modified-Since: A date, e.g., If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT. Server checks if resource changed if not, sends 304 Not Modified (no body). Great for static images it saves redownloading.

  4. Cache-Control: Client side rules, e.g., Cache-Control: no-cache (always check server). Or max-age=0 for fresh data. In APIs, this tells proxies or browsers how to cache requests.

Response Headers: The Server’s Reply

After processing, the server sends these back with the status code (like 200 Which we have discussed in the next section) and body. They guide the client on what to do with the data to render it, to cache it, or secure it. In REST, good headers make apps faster and safer.

  • The Content Details: Basics on what's in the body.
  1. Content-Type: Describes the body, e.g., Content-Type: application/json; charset=utf-8 this tells the client to parse as JSON. If mismatched (say, sending HTML as JSON), the app crashes. Multipart types handle files too.

  2. Content-Length: Body size in bytes, e.g., Content-Length: 1024. Helps clients know when transfer ends, especially for streaming. If without it we use chunked encoding.

  3. Content-Encoding: If compressed, e.g., Content-Encoding: gzip. Client decompresses before using. For big responses this pairs with Accept-Encoding to cut load times.

  • Caching & Freshness: These prevent refetching unchanged data, huge for speed.
  1. ETag: A hash or version, e.g., ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4". Client sends If-None-Match next time and if matches, 304 Not Modified. If content hasn't changed, skip download.

  2. Last-Modified: Timestamp, e.g., Last-Modified: Tue, 29 Oct 2024 12:00:00 GMT. Pairs with If-Modified-Since. Simpler than ETag but less precise for dynamic data.

  3. Expires: Absolute staleness date, e.g., Expires: Wed, 21 Oct 2025 07:28:00 GMT. Browser won't refetch until then. Good for static assets.

  4. Cache-Control: Most flexible, e.g., Cache-Control: public, max-age=3600 (cacheable by anyone for 1 hour). Or private (user-specific), no-store (no caching). Overrides Expires. In APIs, use must-revalidate for fresh checks after stale.

  • State & Server Info: Managing ongoing sessions and info.
  1. Set-Cookie: Server sets client cookies, e.g., Set-Cookie: sessionId=abc123; Max-Age=3600; Secure; HttpOnly. This starts a session. Flags: Secure (HTTPS only), HttpOnly (no JS access), SameSite (anti-CSRF).

  2. Server: Server software, e.g., Server: nginx/1.24.0. Gives hints but often hidden (e.g., removed or faked) to avoid targeted attacks.

Status Codes: The Server's Quick Feedback

When the server processes your request, it starts the response with a status code. It's the first thing in the HTTP response line, like "HTTP/1.1 200 OK." Codes are grouped into ranges (1xx to 5xx), and good API docs always explain what each means for their endpoints. In tools like Postman or browser consoles, you'll see them as 200 is green, 404 is red. Let's break them down with more context.

  • 1XX: "Hold on..." (Informational) These are rare, interim codes saying the request is being handled but not done yet. Clients usually don't need to do much,they're automatic.
  1. 100 Continue: Server says, "What you've sent so far is fine, send the rest." Useful for big uploads, like POSTing a large image to say /uploads endpoint. Client sends the first chunk, gets 100 and then continues. Prevents wasting bandwidth on invalid starts.

  2. 101 Switching Protocols: Server agrees to change protocols, like upgrading from HTTP to WebSockets. For example, if you have real-time comments, a GET /comments with Upgrade: websocket header might trigger 101, switching to bidirectional chat.

  • 2XX: "Got it!" (Success) Everything worked and the request was valid, server did its job. These are what you aim for in happy paths.
  1. 200 OK: The default success for most requests, like GET /posts returning blog data or fetching a user profile sends 200 with JSON in the body. Always include a body if there's data; empty is fine for simple confirms.

  2. 201 Created: For POSTs that make new stuff, like POST /posts creating a blog entry. Server responds with 201 and often a Location header pointing to the new resource (e.g., Location: /posts/456) this confirms the post is live and gives the URL to view it.

  3. 204 No Content: Success, but no body needed—like DELETE /comments/123. For example, removing a spam comment sends 204; client knows it's gone without extra data. Efficient for actions without results.

  4. 206 Partial Content: When you request ranges, like video streaming. If website has a media API, GET /videos/bigfile with Range: bytes=500-999 gets 206 and just that chunk. Headers like Content-Range tell the byte range. Great for resuming downloads on flaky connections.

  • 3XX: "Go over there" (Redirection) The resource moved or can be found elsewhere, client should follow or cache. Servers use these for SEO, load balancing, or versioning.
  1. 301 / 308: Permanent moves: 301 Moved Permanently says "Use this new URL forever" (with Location header). For example if you rename /blog to /posts, 301 redirects old links. 308 is similar but preserves method/body for POSTs. Caches well, but update your links to avoid chains.

  2. 304 Not Modified: Ties to caching headers (from earlier). If client sends If-None-Match with an ETag, and nothing changed, server sends 304—no body, use your cache. For example static CSS, this saves reloads on repeat visits.

  • 4XX: "You messed up" (Client Error) Client's fault like bad input, no auth, or wrong URL.
  1. 400 Bad Request: Generic for malformed requests, like missing params or invalid JSON in POST. For example if you POST /users with broken email format, 400 with details helps debug.

  2. 401 / 403: You aren't logged in or you don't have permission: 401 Unauthorized means "Log in first". 403 Forbidden is "You're logged in but not allowed," like accessing admin without rights. 401 is used for missing creds and 403 for denied access.

  3. 404 Not Found: Classic, the resource doesn't exist. GET /posts/999 on some website if ID's wrong.

  4. 429 Too Many Requests: Rate limiting, you're hitting too fast. Headers like Retry-After: 60 say wait 1 minute. For public API, this prevents abuse.

  • 5XX: "I messed up" (Server Error) Server's problem like bug, overload, or downstream issue. Clients can retry later.
  1. 500 Internal Error: Catch all for crashes, like unhandled exceptions in backend code. Body might say "Something went wrong" for users, but logs have stack traces.

  2. 502 / 504: Gateway/Timeout issues. 502 Bad Gateway means a proxy or upstream server failed e.g. load balancer can't reach the app server. 504 Gateway Timeout is when it takes too long. Common in microservices check connections.

  3. 507 Insufficient Storage: Server's disk is full, can't save your POST. Rare, but for websites with heavy user uploads, this means scale storage ASAP.

Status codes are your API's language for feedback, use them right, and your REST service becomes user friendly.

That wraps up Episode 3 on APIs and architectures, starting with REST. We've gone from tiers and basics to URLs, methods, headers, and now status codes—building a solid foundation for how frontends talk to backends. But REST isn't the only game in town. In the next episode, we'll pick up alternatives like GraphQL (for flexible queries) and gRPC (for high-performance microservices), comparing them to REST with examples.

Top comments (0)