System Design · REST APIs
What actually happens between the moment an app fires a request and the moment your server sends a response, told from the socket up: ports, listeners, TCP connections, and the raw shape of an HTTP request.
Problem: a controller makes more sense when you can trace the request beneath it. Spend 30 minutes learning the flow or 12 minutes rehearsing it.
Recall first: separate a machine address from a port. HTTP knowledge helps, but this page remains open if it is new.
A client resolves a host, connects to an IP and port, sends an HTTP message, then the server parses, processes, and responds. Spring hides most of that loop, not its consequences.
Closest confusion: an IP identifies a machine; a port identifies the process listening on that machine. A URL path identifies neither.
Failure trace: connection refused means the network reached the machine but no process accepts that port. It is not evidence that a controller returned slowly.
Decision rule: when a request fails, test resolution, reachability, listener, HTTP shape, and application logic in that order.
Version note: the HTTP and TCP concepts are stable; Spring Boot examples were reviewed for 4.x on 2026-08-28.
Mission (20 minutes): run a tiny Boot endpoint, use curl -v to inspect the request/response, then point curl at one wrong port. Work with the controller and your terminal.
Acceptance checks: you can label method, path, headers, status, and body; you can reproduce a refusal; you can move the server port and update the client.
Use a one-route controller. The point is to isolate transport evidence, not build a full API.
Transfer: predict which part changes when a browser calls the same path over HTTPS through a reverse proxy.
Two-minute summary: DNS finds a host, TCP reaches an IP:port, HTTP carries application intent, and the server follows listen, parse, process, respond.
Explain in 60 seconds: why can one IP host multiple services?
Primary question: walk through a request from URL to controller. Follow-ups: what is DNS responsible for, and what distinguishes a 404 from connection refused?
Debug exercise: curl localhost:8080/users refuses while the app log says it started on 8081. Explain the evidence and the minimal fix.
Use the sequence, distinguish transport from application status, and show one concrete diagnostic command. Do not claim a port is part of a URL path.
Timed mock: diagnose a failing local request in 90 seconds with only the URL and startup log.
Learn next: Use trace an http request as the base, then retrieve the foundation concepts without notes in Foundation retrieval review. Interview next: Use trace an http request as the base, then map an entity in Spring Data JPA — From Object to Table.
Continue this path · Switch paths from this topic · Review first: your first endpoint
Stop here before Core concepts. Answer from your own request trace, then continue only after you can diagnose the difference between a network failure and an application response.
/ping endpoint is useful before introducing domain logic.A web backend is just a program that waits at a known port, reads the raw bytes a client sends, decides what they mean, runs your logic, and writes bytes back. Everything a framework like Spring Boot does is automating those four steps, listen, parse, process, respond.
Hand-copyable. Fully derivable from the detailed notes below.
| Port | Use |
|---|---|
| 80 | HTTP |
| 443 | HTTPS (TLS) |
| 8080 / 8081 | local dev servers |
You open the Uber app and, within a second, a map fills with little cars parked around you. Nothing about that is magic, and nothing about it is only front-end. Your phone quietly reached across the internet, found one specific program running on one specific machine in a data center, handed it your coordinates, and got back a list. If any link in that chain were missing, a name that won't resolve, a door with nobody behind it, the screen would just spin. This lesson opens that chain up and looks at each link.
A backend server is a program whose whole life is a loop: wait for a request, answer it, wait for the next one. Strip away every framework and the job is always the same four steps, in order.
Someone has to be waiting for requests at the right port. No listener, no conversation.
Incoming bytes get received and read for their inputs: which path, which query params, what body.
The business logic runs. Look up cars, check a balance, save a record.
An appropriate response is written back to the caller and the connection is closed or reused.
Think of a cashier at a counter. They stand at one register (listen), take your order and read it back (parse), fetch the item from the back (process), and hand it over with a receipt (respond). Then they call "next". Spring Boot is a very fast cashier who never leaves the counter.
Everything that follows is really about step 1 and step 2, because they are the parts that feel invisible until you see them by hand. A framework hides them so well that many people never realize a server is "just" a program holding a port open.
A URL is a set of instructions for finding one resource on one machine and telling it what you want. Here is the exact request the Uber app makes when it asks for nearby cars.
https://api.uber.com/api/v1/cars?lat=12.7&long=88.0&status=AVAILABLE
Read left to right, every piece has a job:
| Piece | Name | What it does |
|---|---|---|
https |
scheme | Use HTTP secured with TLS. Implies port 443 unless one is stated. |
api.uber.com |
host | A human name for a machine. DNS turns it into an IP address. |
/api/v1/cars |
path | Which resource on that machine you want. |
?lat=..&long=.. |
query params | The input to the request: your location and the filter. |
Before any bytes travel, two lookups happen. First, DNS resolution (the internet's phone
book) turns api.uber.com into a numeric IP address. Then your phone opens an HTTPS GET
request to that IP on port 443. Only now does the conversation start.
Query params are how a GET request carries input. They are visible in the URL, get logged, and are cached, so they suit filters and search terms, never passwords. Anything sensitive or large belongs in the request body of a POST.
An IP address finds the machine; a port finds the specific service running on it. Those are two different questions, and confusing them is the root of a lot of "why won't it connect" pain.
You reach a shopping mall using its street address, that is the IP address, like
10.15.20.35. But the mall has many shops. Standing at the entrance shouting "I want faded
blue jeans, torn in a few places" gets you nowhere, because the address only got you to the building.
You still need the right shop. A port number is the shop. One mall (one IP) hosts many
shops (ports 8081, 8082, 8083, 8084), each a different service.
A port is just a 16-bit number, so the range is 0 to 65535. Some are reserved by convention:
80 for HTTP, 443 for HTTPS. Local development servers usually squat on
8080 or 8081 so they don't clash with anything privileged.
When a request "doesn't work," ask which of the two lookups failed. Wrong or unresolvable host is a DNS problem. Right host but nobody home on that port is a listener problem. Separating machine from service turns a vague failure into two testable questions.
From typing a URL to seeing a response, a request passes through a fixed sequence of stages. Naming them makes debugging mechanical instead of mysterious.
Between "IP:port" and "HTTP request" sits a step people forget: the TCP connection. HTTP is a set of rules for what to say; TCP is the reliable pipe you say it through. A connection has to be established, a handshake, before a single byte of your GET line is sent. The next section makes that pipe visible.
Before you trust a server, you can knock on its port and see if anything answers. Two small
tools do this: telnet and nc (netcat). Both open a raw TCP connection to a host
and port.
telnet localhost 8081
# or
nc localhost 8081
If nothing is listening on 8081, you don't get a slow or empty reply, you get connection refused immediately. The machine is up, but that shop is empty. A refused connection is a listener problem, not a network-speed problem, and chasing it as latency wastes hours.
To feel the other side, become the server yourself. Open one terminal and tell netcat to listen:
nc -l 8081
Now that port has ears. In a second terminal, connect to it with telnet localhost 8081. Anything
you type in one window appears in the other. You have hand-built the "listen" step of a backend with one
command.
The same tool plays both roles. nc -l <port> makes you the server waiting for a
request; nc <host> <port> (or telnet) makes you the client sending one. Seeing
both ends by hand demystifies what a framework does: it is netcat with a lot of automation bolted on
top.
An HTTP request is plain text sent over a TCP connection, so you can type a valid one by hand. This is the moment the abstraction cracks open. Connect to a real HTTP server and speak its language directly:
GET http://localhost:8081/greeting HTTP/1.0
You type the request line, then press Enter on a blank line to signal "headers are done". If a real HTTP server is on the other side, it replies with a status line, headers, and a body, all more text. The blank line is not decoration; it is the delimiter that tells the server the request head is finished.
The request line has three fixed parts: the method (GET), the path or URL, and the protocol version (HTTP/1.0). That is genuinely all a minimal GET needs. Everything else, headers, cookies, auth, is optional detail layered on the same text format.
Typing HTTP by hand is educational but tedious. curl does the whole dance, resolve, connect,
send a well-formed request, print the response, in one line:
curl http://localhost:8081/greeting
A working HTTP server must be listening on the right port so it can receive incoming REST API requests and process them. That single sentence is the whole lesson. The framework you will use next, Spring Boot, exists to make each of the four steps effortless.
nc -l 8081 to listen/greeting to a method automaticallyNothing new is happening under Spring Boot. It is the same listen, parse, process, respond loop you just performed with netcat, wrapped so you write only the business logic and let the framework hold the port.
An IP address identifies a machine on the network, the building. A port identifies a specific service running on that machine, the shop inside. One IP can expose many ports, each handled by a different program, which is why a single server can run several services at once.
Listen for requests on the right port, receive and parse the incoming bytes for their inputs, apply business logic, and send back an appropriate response. Every web framework is an automation of these four steps.
DNS resolves api.uber.com to an IP address. Your client opens a TCP connection to that IP on
port 443 (implied by https) and completes the TLS handshake. It then sends an HTTP GET for
the path /api/v1/cars with the query params as input. The server parses it, runs its logic,
and returns a response.
The scheme sets a default port: https implies 443 and http implies 80. Because
the URL starts with https and states no port, the connection goes to 443 unless one is
written explicitly, like :8443.
They open a raw TCP connection to a host and port with nothing layered on top, so you can check whether
anything is listening and even type an HTTP request by hand. nc -l <port> also lets
you play the server. They expose the plumbing a browser or framework normally handles for you.
The machine is reachable but no process is listening on that specific port, the shop is empty. It is a listener or wrong-port problem, not network latency. Check that the service is running and bound to the port you expect.
HTTP is a plain-text protocol carried over a TCP connection. A minimal request is just a request line, method, path, version, followed by a blank line to end the headers. Because it is text, any tool that opens a TCP connection can send a valid request.
curl automates the entire exchange: DNS resolution, opening the connection, building a well-formed HTTP request, and printing the response. telnet only gives you a raw pipe; you must speak HTTP yourself. curl is the convenient shortcut for the same conversation.
No. Query params sit in the URL, so they get logged by servers and proxies and can be cached or shared. They are fine for filters and search terms but wrong for passwords, tokens, or large payloads, which belong in a request body over a POST.
This lesson is about the layer underneath the framework, so the practice is deliberately hands-on-the-wire. Do each before revealing the walkthrough — parse the URL on paper, type the raw request into a real terminal. Feeling the bytes once is what makes the framework's convenience make sense.
A mobile app fires the request below. Without running anything, split it into its parts and say which of the backend's four jobs (listen · receive&parse · process · respond) each part feeds.
https://api.qeats.com:8081/restaurants/search?lat=28.6&lng=77.2&cuisine=biryani
Read it left to right; every component answers a different question.
| Part | Value | What it decides |
|---|---|---|
| scheme | https |
Transport + TLS, and the default port (443) if none is written |
| host | api.qeats.com |
Which machine — DNS resolves it to an IP (the building) |
| port | 8081 |
Which service on that machine (the shop) — overrides the https default
of 443 |
| path | /restaurants/search |
Which endpoint / handler on the server |
| query | lat=28.6, lng=77.2, cuisine=biryani |
The inputs to that handler |
Now map those onto the four jobs:
8081 on that
host, or the connection is refused before anything else can happen.The port here is :8081, not the https default of 443. Written
explicitly, it wins. Drop the :8081 and the very same URL would hit 443. That's the
address-vs-port split: api.qeats.com gets you to the building; :8081 gets
you to the specific shop inside it.
With a server you can reach on localhost:8080 (the Hello World app from the next session works
perfectly), open a raw TCP connection and type an HTTP request by hand — no browser, no curl.
Then interpret two error outcomes.
nc localhost 8080
...then type exactly this. The blank line after the headers is required — it's what tells the server the request is finished:
GET /helloworld?name=Neo HTTP/1.1
Host: localhost
Connection: close
What a healthy reply looks like — a status line, headers, a blank line, then the body:
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
Content-Length: 21
Hello World, I am Neo!
What you just proved: HTTP is nothing but text sent over a TCP connection. You performed, by hand, the request half of the listen → parse → process → respond loop; the server did the other three. The mandatory blank line is the protocol's "end of headers" marker — omit it and the server keeps waiting for more, so nothing ever comes back.
Now read two failures:
nc: connect to localhost port 8080 (tcp) failed: Connection refused — nothing is
listening on 8080. The server isn't running, or it's on a different port. "Refused" is the OS
actively saying no one is home at that shop. Fix: start the app, or point at the right
port.curl "http://localhost:8080/helloworld?name=Neo"
curl composes the identical request text, adds the blank line, and prints the body — but it's the exact same conversation you just had by hand.