→ From Coder to Shipper
From Coder to Shipper · Web fundamentals

Flask / HTTP / curl

The conversation between browser and server, in depth: what HTTP really is, how a request becomes a response, how Flask turns Python functions into web pages — and how to test it all with curl.

← / → to navigate · EN/VI switch below

Big picture

The web is a two-way conversation

HTTP (Hypertext Transfer Protocol) runs the show. Strict turn-taking: the client speaks first, the server answers once, and the round is over.

🌐 Client

Browser, phone app, curl… you. The only side allowed to start a conversation.

① REQUEST GET /about + headers ▶
② RESPONSE 200 OK + HTML ◀

🖥 Server

A program listening on a port (80/443). It never initiates — it only answers.

one request → one response → done · every click, refresh and API call is exactly this

Big picture

Why HTTP exists

Before HTTP, every client–server pair invented its own dialect. HTTP is the one dialect everyone agreed on.

One common language

Billions of clients and servers — built by different people, in different languages — agree on one set of rules. HTTP is that agreement.

Two sides, strict roles

A client asks, a server answers. The roles never swap — that one rule is what makes the web predictable at global scale.

Stateless — no memory

Each exchange is a fresh conversation; the server forgets you the moment the response is sent. Cookies and sessions are tricks that fake memory.

Plain text under the hood

Requests and responses are just formatted text. You can read them with your own eyes (curl -i) — that's what makes HTTP learnable.

Big picture

Client vs server — the only two roles

Every web app you'll ever ship is these two roles talking. This course is about becoming the person who builds the right side.

Client — the asker

Your browser, phone app, or curl. It initiates every conversation, shows you the results, and keeps nothing between visits.

Clients are impatient by design: they ask once, wait, and move on.

Server — the answerer

A program on a machine that waits on a port. It does the real work: reads databases, runs logic, builds the answer.

Servers are patient by design: they wait forever, answering thousands of times per second.

Big picture

What happens when you click a link

Four beats, half a second. Everything in this course is a zoom-in on one of these beats.

  1. 1

    You click

    The browser decides 'I need that address' and builds a request: URL, method (GET), headers — a small text message.

  2. 2

    It travels

    The request rides the network to the right machine, the right port. (TLS encrypts the trip — a later topic.)

  3. 3

    The server works

    The server hands the request to your app. The app does the job: reads data, runs logic, builds a page or JSON.

  4. 4

    You see pixels

    The response rides back. The browser parses the HTML, applies the CSS, runs the JS, paints. Done — until the next click.

01
Part 1

HTTP — the language of the web

The rules for how a client asks and a server answers: anatomy of the request and the response, methods, status codes, headers, redirects.

HTTP · the request

A request is four parts

Everything a client can say, packed into one message. No more, no less.

URL — where

The address being called: host (which server) + path (what on it) + optional query (?sort=new). It's the 'who to' line of the message.

Method — what

The verb: GET, POST, PUT, PATCH, DELETE. It states your intent so the server knows what to do before reading anything else.

Headers — who & how

Metadata lines, one fact per line: who's asking (User-Agent), what format the body is in (Content-Type), who you claim to be (Authorization).

Body — the payload

The actual content being sent: form fields, JSON, a file. Optional — GET has none; POST and PUT usually carry one.

HTTP · on the wire

A real exchange, no browser in sight

This is literally what travels between client and server. Left: the request (method + URL + headers + body). Right: the response (status + headers + body).

request.txt
GET /users/42?sort=new HTTP/1.1
Host: api.shop.com
Content-Type: application/json

{"name": "Ada"}

the client's message — method, URL, who's asking, and the payload

response.txt
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 46

{"id": 42, "name": "Ada", "posts": 3}

the server's reply — what happened (200), in what format, and the data

HTTP · the URL

Anatomy of an address

Every URL is five jobs in one line. Browsers and servers split it apart the same way every time.

url.txt
https://api.shop.com:8443/users/42?sort=new&page=2

scheme   https          how to talk
host     api.shop.com   which machine answers
port     :8443          which door (80 and 443 are the defaults)
path     /users/42      which resource
query    ?sort=new...   options, as key=value pairs

Most of the time the port is hidden — :80 for http, :443 for https are implied.

HTTP · the response

A response is three parts

Mirror image of the request — but it starts with a verdict: the status code.

Status line

First line: HTTP/1.1 200 OK. The 3-digit code is the headline — success, redirect or error — before a single byte of content.

Headers

Metadata about the content: its format (Content-Type), its size (Content-Length), cache hints. Each line is 'Name: value', like request headers.

Body

The actual content: an HTML page, JSON, an image — or nothing. Everything after the blank line that separates it from the headers.

HTTP · methods

Methods — the verbs of the web

The method is a promise about intent. You and the server can rely on it: GET never changes anything, POST creates, DELETE removes.

MethodIt meansExample
GETRead — never changes anythingGET /users/42
POSTCreate, or submit somethingPOST /users
PUTReplace the whole resourcePUT /users/42
PATCHTweak part of a resourcePATCH /users/42
DELETERemove a resourceDELETE /users/42
HTTP · methods

Choosing the right verb

Pick by intent, not by habit. The method tells caches, browsers and other apps how safe it is to touch your endpoint.

GET is a promise

GET means 'show me', never 'change anything'. Browsers, caches and search engines rely on it — so GET must be safe to repeat.

POST carries things

POST is for sending: forms, new records, file uploads. It has a body, and repeating it may create duplicates — that's allowed.

PUT vs PATCH

PUT replaces the whole resource; PATCH tweaks part of it. In practice: most apps just use POST for 'write' and keep it simple.

DELETE deletes

DELETE removes a resource. The response is a status code (200 or 204), not a page — the client checks the code, not the pixels.

HTTP · status codes

Status codes — the server's one-line answer

Every response starts with a 3-digit number. The first digit tells the whole story at a glance.

2xxIt meansKey codes
2xxSuccess — it worked200 OK · 201 Created
3xxRedirect — go look somewhere else301 moved · 302 see other
4xxClient error — your side's fault (politely put)400 bad · 401 not authed · 403 denied · 404 not found
5xxServer error — our side's fault500 internal error
HTTP · status codes

The nine codes you'll actually meet

Memorize the pattern, not the list: 2xx done, 3xx go elsewhere, 4xx your problem, 5xx ours.

CodeIt meansWhat you do
200 OKEverything is fine; here's the dataNothing — it worked
201 CreatedA new resource was born (your POST worked)Keep its Location header — the new thing's address
301 Moved PermanentlyThis address changed foreverBrowsers update their records; fix the old link
302 FoundGo look at this other URL — just this onceThe browser follows automatically; you never see it
400 Bad RequestYour request was malformed — I can't even parse itCheck your URL, body, headers
401 UnauthorizedWho are you? I need credentialsLog in / add the Authorization header
403 ForbiddenI know who you are — and noPermissions issue; ask an admin
404 Not FoundNo such thing exists at that addressCheck the URL — a typo, or it was deleted
500 Internal ErrorOur side crashed — not your faultCheck the server logs; the error page points at the line
HTTP · the classic mix-up

401 vs 403 — identity, then permission

The most confused pair in HTTP. 401 is an identity problem; 403 is a permission problem.

401 — who are you?

You didn't show credentials, or they're wrong. The server is asking you to identify yourself.

Fix: log in, add the token. After that, the door may open.

403 — I know you, and no

Credentials were fine — but that account doesn't have permission for this resource.

Fix: nobody's code is wrong; it's a permissions problem.

HTTP · headers

The four headers that answer the common questions

There are ~100 headers in total. These four answer the questions that come up most often.

Host

Which server you mean — mandatory, because virtual hosts put many sites on one IP.

Content-Type

The format of the body: text/html, application/json… The server's first clue about how to read your body.

User-Agent

A business card: which browser, app or curl is asking. Used for analytics and compatibility.

Authorization

Your credentials, usually a Bearer token. This is how APIs let you in without a login page.

HTTP · redirects

The redirect dance

A redirect isn't an error — it's a signpost. You never write it, you just watch it happen.

  1. 1

    You ask for the old address

    GET /old-page. The server knows it moved — but it doesn't send the page, it sends a signpost.

  2. 2

    The server points

    301 + a Location header: /new-page. One tiny response, no content — just 'go there instead'.

  3. 3

    The browser obeys automatically

    It re-requests the new URL, gets 200, and shows the page. From your seat, it felt like one click.

HTTP · the server's job

What a web server actually does

Strip away the marketing: a web server is a program looping through four steps, thousands of times per second.

  1. 1

    Listen on a port

    It waits on a port (80 for HTTP, 443 for HTTPS) for incoming connections — a phone left off the hook.

  2. 2

    Parse the request

    It decodes the bytes into method, URL, headers, body — the four parts from earlier.

  3. 3

    Run your code

    It hands the request to your application (Flask, in this course), which decides what to do — usually: read some data, build a page.

  4. 4

    Send the response

    It wraps the result in status + headers + body and ships it back. Then back to step 1.

HTTP · ports

A port is a door number

One machine runs many services; the port picks the door. Production answers at 80/443; your Flask dev server usually opens 5000.

ops@vps — zsh
$ ss -ltn | grep -E ':(80|443|5000) '
LISTEN 0 511 *:80 *:*
LISTEN 0 511 *:443 *:*
LISTEN 0 128 127.0.0.1:5000 0.0.0.0:*
$ curl -i http://127.0.0.1:5000/ | head -1
HTTP/1.1 200 OK
80 and 443 open to the world; 5000 only to this machine (127.0.0.1)
02
Part 2

Flask — your Python web app

The web server speaks bytes; you think in Python. Flask is the thin layer in between — a route is a promise: 'when this URL + method arrives, run this function'.

Flask · what it is

Flask, in four sentences

A framework is scaffolding you don't have to build. Flask is the lightest popular scaffolding for Python web apps.

A micro-framework

Deliberately small: routing, request parsing, responses. Databases, admin panels, auth — you bolt on only what you need.

Python in, HTTP out

You write plain Python functions; Flask handles the ugly part — bytes, headers, the network. A thin layer, not a platform.

A route = a promise

Each @app.route registers one promise: 'when this method hits this URL, run this function'. The rest is bookkeeping.

The dev server is free

flask run gives you a working server for development. It's not production-grade — that's what nginx is for (next topic).

Flask · first app

A whole website, 8 lines

This is a complete, runnable web app. Two routes: one for humans (HTML), one for programs (JSON).

app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/")
def home():
    return "<h1>Hello, shipper!</h1>"

@app.route("/api/hello")
def api_hello():
    return jsonify(name="shipper", status="alive")

Run it with flask run, open http://127.0.0.1:5000 — that's the entire server side of a web app.

Flask · running it

Two terminals, zero browsers

That's the whole loop: Python on your machine answering HTTP on a port. You can test it before a human ever sees it.

ops@vps — zsh
$ flask run
* Running on http://127.0.0.1:5000
$ curl -i http://127.0.0.1:5000/
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
<h1>Hello, shipper!</h1>
$ curl http://127.0.0.1:5000/api/hello
{"name":"shipper","status":"alive"}
Flask · routes up close

Routes up close

Once you know the promise, routes are just sign-up forms. Four details worth knowing.

The decorator is the sign-up

@app.route("/about") registers the function below for GET /about. The function's name can be anything — Flask doesn't care.

Methods are opt-in

Routes default to GET (and HEAD). A write endpoint declares it: @app.route("/users", methods=["POST"]). Wrong method → 405.

The return value is the response

Return a string → HTML page. Return jsonify(...) → JSON. Return (body, 404) → you control the status code.

URLs can carry values

/users/<int:uid> binds the 42 in /users/42 to the argument uid — typed, right in the address.

Flask · responses

Three ways to answer

Same app, three return styles. The return value decides what the client receives — and at which status code.

responses.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/")
def page():
    return "<h1>Hello, shipper!</h1>"   # -> text/html

@app.route("/api/hello")
def api():
    return jsonify(name="shipper")      # -> application/json

@app.route("/missing")
def gone():
    return "not here", 404               # -> you pick the status

One rule to remember: whatever the function returns, Flask wraps into status + headers + body.

Flask · two audiences

HTML for eyes, JSON for machines

One app usually does both: HTML routes for people, /api routes for programs.

HTML — for eyes

The browser parses it, styles it, paints it. A human is the end consumer; the page is the product.

Test it with a browser click. If the pixels are right, it works.

JSON — for programs

A machine reads it: another app, a phone, a frontend framework. Keys and values, no decoration.

Test it with curl -i. If the code is 200 and the JSON parses, it works.

Flask · reading input

Four doors the client can knock on

Clients send data in different shapes; Flask exposes each one in a different place. Learn where to look and the syntax follows.

input.py
from flask import request

@app.route("/users/<int:uid>")       # 1. URL parameter
def show_user(uid):
    return jsonify(uid=uid)

@app.route("/search")
def search():
    q    = request.args.get("q")    # 2. query string: /search?q=shoes
    name = request.form.get("name") # 3. form field (login, checkout...)
    body = request.json              # 4. JSON body (from an API client)

One app, four ways in. <uid> comes from the URL; the rest live on the request object.

Flask · input channels

Where each kind of data lands

Matching the channel to the job is the whole design decision behind every API.

ChannelLands in FlaskExampleUse for
URL parameter<int:uid> in the route/users/42Which resource — part of the address
Query stringrequest.args/search?q=shoesFilters, options, pagination
Form bodyrequest.form<form> postsLogin, checkout — the classic browser form
JSON bodyrequest.jsoncurl -d '{...}'APIs talking to APIs — the modern default
Flask · query strings

Reading the query string, safely

Query values arrive as strings — even '42'. Set defaults, and convert types yourself.

query.py
@app.route("/search")
def search():
    q = request.args.get("q", "")              # ?q=shoes -> "shoes", else ""
    page = request.args.get("page", 1, type=int)  # ?page=2 -> 2 (an int)
    if not q:
        return "nothing to search", 400
    return jsonify(q=q, page=page)

get(key, default, type=...) is the 90% solution for query strings.

Flask · the machinery

Inside a Flask request

Four stops between 'bytes arrive' and 'bytes leave'. Knowing the route makes debugging 10x faster.

  1. 1

    The server receives

    The web server (or Flask's dev server) gets the raw HTTP bytes and hands them to Flask.

  2. 2

    Flask matches the route

    It walks your registered promises: method + URL. No match? Instant 404 — your code never runs.

  3. 3

    Your function runs

    The view function reads the input (args, form, json), does the work — often: fetch data — and returns a value.

  4. 4

    Flask wraps and sends

    The return value becomes status + headers + body, and rides back to the client. The round trip is over.

Flask · debugging

Errors are just status codes

Flask's dev server shows the full traceback on 500s — the last line usually names the exact bug. Fix, save, curl again.

ops@vps — zsh
$ curl -i http://127.0.0.1:5000/typo
HTTP/1.1 404 NOT FOUND
not found
$ curl -i http://127.0.0.1:5000/broken
HTTP/1.1 500 INTERNAL SERVER ERROR
Traceback (most recent call last):
File "app.py", line 12, in order_total
NameError: name 'price' is not defined
read the LAST line of a traceback - it names the bug
Flask · errors

Returning errors on purpose

A correct app fails politely: the right code tells the client exactly what to try next.

You pick the code

return 'not found', 404 — the second value sets the status. Stop answering 200 for everything.

400 vs 404

400: the request itself was malformed. 404: the request was fine, the thing doesn't exist.

APIs read codes, not pages

An API client branches on the status code. Your 4xx and 5xx are instructions, not decorations.

03
Part 3

curl — the developer's browser

A JSON API is invisible to a browser's way of seeing. curl is the terminal's HTTP client: it performs a request and prints the raw response.

curl · why it exists

Why developers live in curl

curl is the browser's blunt, honest cousin: it shows the plumbing the browser hides.

Browsers hide the plumbing

A browser shows you the painted result and swallows the status code, headers and redirects. curl shows every byte.

APIs aren't for browsers

A JSON endpoint opened in a browser is a dead end. curl is the native habitat of an API.

You control the request

Pick the method, the headers, the body, the exact URL. What the browser does for you on a click, you do deliberately.

It scripts

curl lives in shell loops, Makefiles and CI pipelines. If a test can be typed, it can be automated.

curl · in action

One app, four answers

Success, data, filtered data, failure. This is what testing an API feels like.

ops@vps — zsh
$ curl -i http://127.0.0.1:5000/
HTTP/1.1 200 OK
<h1>Hello, shipper!</h1>
$ curl http://127.0.0.1:5000/api/hello
{"name":"shipper","status":"alive"}
$ curl http://127.0.0.1:5000/search?q=shoes
{"q":"shoes","page":1}
$ curl -i http://127.0.0.1:5000/nope
HTTP/1.1 404 NOT FOUND
not found
curl · the toolbox

Six flags, 95% of API testing

curl's default is: GET a URL and print the body. The flags bend it into a full API client.

FlagWhat it doesExample
-X POSTSwitch the methodcurl -X POST http://…/users
-H 'K: V'Add a header-H 'Content-Type: application/json'
-d dataAttach a body-d '{"name":"Ada"}'
-iShow status line + headerscurl -i http://…/
-vVerbose: the whole connection storycurl -v http://…/
-o fileSave the body to a file instead of the screencurl -o page.html http://…/
curl · the classic one-liner

Create a record from the terminal

Method + header + body + URL. This one pattern covers most of the 'send data to my API' work.

post.sh
# the classic "create a record" one-liner
curl -X POST \
  -H 'Content-Type: application/json' \
  -d '{"name": "Ada"}' \
  http://127.0.0.1:5000/users

# ...and check what the server actually said
curl -i -X POST \
  -H 'Content-Type: application/json' \
  -d '{"name": "Ada"}' \
  http://127.0.0.1:5000/users

With -i you should see HTTP/1.1 201 CREATED — proof the record was born.

curl · under the hood

-v: watch the conversation happen

Lines starting with > are yours; lines starting with < are the server's. This is the whole protocol, live.

ops@vps — zsh
$ curl -v http://127.0.0.1:5000/ 2>&1 | head -12
* Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 port 5000
> GET / HTTP/1.1
> Host: 127.0.0.1:5000
> User-Agent: curl/8.5.0
> Accept: */*
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
<h1>Hello, shipper!</h1>
you typed one command; curl did the whole dance above
Big picture

The whole stack in one picture

Three roles, one pipeline — and the next topic (nginx) plugs in right here.

🌐 Clients

Browser for humans, curl for you. They start every conversation.

request ▶ ◀ response

🚪 nginx — front door

Next topic. Terminates HTTPS, serves static files, forwards real requests onward.

request ▶ ◀ response

🐍 Flask app

Your code: routes, data, decisions. Builds the response and sends it back.

clients talk to the door · the door talks to the app · the app talks to the world — all in plain HTTP

Request. Response. Repeat.

Every web app you'll ever ship is a loop: listen, parse, run, answer. Flask writes the answers; curl proves they arrive.