Module 9 ended with an uncomfortable realization: Papyrus works, it's tested, it has a safety net — but it lives on a single computer. If Luis wants to know how many copies of Faust are left, he has to call Ana; if Julia wants to reserve Hamlet, she has to walk to the shop. The solution has had a name for thirty years: turn Papyrus into a web application, a program that runs on a server and that anyone can reach from a browser or a phone. Before you write your first line of Flask (that comes in the next lesson), you need to understand the playing field: how a browser and a server talk to each other, what HTTP is, where HTML fits into all this, and what a framework gives you for free. This lesson is the map; the next four are the journey.

Contents

  1. The client-server model: who asks and who answers
  2. HTTP: the protocol of the conversation
  3. HTTP methods: the verbs of the web
  4. Status codes: the traffic lights of responses
  5. Anatomy of a URL
  6. HTML: the bare minimum for this module
  7. Frontend and backend: two worlds, one border
  8. JSON: the language of APIs (an old friend)
  9. What a framework does for you
  10. The Python landscape: Flask, Django and FastAPI

The client-server model: who asks and who answers

The entire web rests on one simple idea: there are programs that ask (clients) and programs that answer (servers). Luis's browser is a client; the computer where Papyrus will run will be a server. The conversation is always started by the client — the server never calls anyone on its own initiative; it just waits for requests and answers them.

sequenceDiagram
    participant N as Luis's browser (client)
    participant S as Papyrus server
    N->>S: GET /catalog  (HTTP request)
    Note over S: Runs Python code:<br/>load_catalog(), build the page
    S->>N: 200 OK + catalog HTML (response)
    Note over N: The browser renders the page
    N->>S: GET /book/Faust
    S->>N: 200 OK + Faust's page (21.00 EUR, stock 10)

Notice how the roles are cast:

  • The client (a browser, a mobile app, or even a Python script using requests) builds a request, sends it and waits.
  • The server receives the request, runs code — in our case, Python code you have already written: find_book(), sell(), load_catalog() — and returns a response.
  • Each exchange is independent: request → response, and conceptually the connection ends there. This server amnesia (it's called stateless, and we'll come back to it in 10-03) is what allows thousands of clients to talk to the same server without it having to remember each one.

The practical consequence for Papyrus: the papyrus/ package you built in M3–M9 does not change. What you are going to add is a layer on top that translates HTTP requests into calls to your functions, and return values (or exceptions) into HTTP responses.

HTTP: the protocol of the conversation

HTTP (HyperText Transfer Protocol) is the agreed-upon format of those requests and responses. It's text, and it's readable. A real request has three parts — start line, headers and an optional body:

GET /book/Hamlet HTTP/1.1
Host: papyrus.example
Accept: text/html

And the response follows the same structure:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1274

<!DOCTYPE html>
<html>...Hamlet's page...</html>
  • The start line of the request states the method (GET), the path (/book/Hamlet) and the protocol version.
  • The headers are metadata in Key: value format — what format the client accepts, what format the server is sending, how big the body is...
  • The body carries the content: HTML for a web page, JSON for an API, nothing at all for many GET requests.

You will never need to write HTTP by hand — the browser and the framework do it for you — but being able to read it will turn you into someone who debugs web problems in minutes instead of hours.

HTTP methods: the verbs of the web

The method declares the intent of the request. Four cover almost everything, and they map naturally onto the operations Papyrus already knows how to do:

Method Intent Example in Papyrus Modifies data?
GET Read, look up View the catalog, search for The Odyssey No
POST Create something new Add a new book to the catalog Yes
PUT Update something that exists Change Hamlet's stock from 6 to 9 Yes
DELETE Remove Withdraw a discontinued book Yes

Two golden rules that frameworks respect and you must respect too:

  • GET must never modify data. A search engine indexing your site will follow every GET link it finds; if /sell/Hamlet were a GET, a robot could accidentally drain your stock.
  • POST, PUT and DELETE carry sensitive information in the body of the request, not in the URL (URLs end up in logs and browser histories).

Status codes: the traffic lights of responses

Every HTTP response starts with a three-digit code that sums up how things went. The first digit is the family: 2xx success, 4xx the client got it wrong, 5xx the server broke. These five are the ones you'll use constantly:

Code Name When Papyrus will return it
200 OK Success GET /book/Hamlet and Hamlet exists: here's its page
201 Created Created POST of a new book accepted: Faust II is now in the catalog
400 Bad Request Malformed request POST of a book with no title or with price: "free"
404 Not Found Doesn't exist GET /book/Moby-Dick — we don't have it (does BookNotFoundError ring a bell?)
500 Internal Server Error Server error An uncaught exception in our Python code

The 404 row is an important preview: in M7 you designed BookNotFoundError to signal exactly this situation inside Python. In 10-03 you'll see that the exception → status code translation is literal: you catch BookNotFoundError and respond with a 404. Your M7 exception hierarchy was already thinking about the web without knowing it.

And a note of professional attitude: a 500 is always your fault (or your code's). If a user can trigger a 500 by typing something odd into a form, you're missing validation — it should be a 400 with a helpful message.

Anatomy of a URL

The URL is the full address of a resource. Let's dissect a future Papyrus URL:

https://papyrus.example:443/books/search?title=odyssey&max=10#results
└─┬─┘   └──────┬───────┘└┬┘└─────┬─────┘└─────────┬──────────┘└──┬───┘
scheme        host      port    path         query string    fragment
  • Scheme: the protocol, http or https (the s is encrypted HTTP; nowadays, always https in production).
  • Host: the server's name. In development it will be localhost or 127.0.0.1 — your own machine.
  • Port: the server's "door". Omitted when it's the standard one (80 for http, 443 for https). Flask's development server uses 5000 and Django's uses 8000; you'll see them a thousand times.
  • Path: which resource is being requested. This is what your Flask/Django routes will map to functions.
  • Query string: optional parameters after ?, in key=value pairs separated by &. It's where the search terms of a GET form travel.
  • Fragment: after #, a position within the page; it never reaches the server.

HTML: the bare minimum for this module

Let's be honest: this is not an HTML course, and it won't try to be. But Flask and Django templates generate HTML, so you need to read it and write the basics. HTML is a markup language: you wrap content in tags that declare what each piece is.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Papyrus — Bookshop</title>
</head>
<body>
    <h1>Papyrus Catalog</h1>
    <p>The timeless classics, right in your neighborhood.</p>
    <ul>
        <li>The Odyssey — 12.50 EUR</li>
        <li>Hamlet — 9.95 EUR</li>
    </ul>
    <a href="/book/Faust">See Faust's page</a>
</body>
</html>

Line by line: <!DOCTYPE html> declares modern HTML; <head> holds metadata that isn't rendered (character encoding, the tab title); <body> is what's visible; <h1> is a heading, <p> a paragraph, <ul>/<li> a list, <a href="..."> a link. Tags open as <p> and close as </p>.

The piece you'll use most is the form, because it's how the user sends data to the server:

<form action="/search" method="get">
    <label for="title">Search for a book:</label>
    <input type="text" id="title" name="title">
    <button type="submit">Search</button>
</form>
  • action is the path the form submits to; method is the HTTP verb (get for searches, post for creating/modifying).
  • Each <input> with a name becomes a parameter: when searching for "odyssey", the browser requests GET /search?title=odyssey. There's the query string from before.
  • <button type="submit"> triggers the submission.

With headings, paragraphs, lists, tables (<table>, <tr>, <td>), links and forms you have 95% of what this module needs.

Frontend and backend: two worlds, one border

Frontend Backend
Where it runs In the user's browser On the server
Typical languages HTML, CSS, JavaScript Python (our thing!), and many others
Responsibility Presentation and interaction Business logic, data, security
In Papyrus The pages Luis and Julia will see find_book(), sell(), the catalog

The border between the two is exactly HTTP: the frontend asks, the backend answers. This is a Python course, so we'll live in the backend; the frontend we write will be modest HTML generated by templates, enough to make Papyrus usable. One rule you must never forget: all frontend validation is courtesy, not security. Anyone can send HTTP requests without going through your form (you'll do it yourself with curl in 10-03), so the backend must always validate, whether or not it trusts the page it served.

JSON: the language of APIs (an old friend)

When the client isn't a browser that wants HTML but a program that wants data — Julia's future mobile app, one of Luis's scripts — the response travels as JSON. And here there's nothing new to learn: it's exactly the format from M6, the same one as data/catalog.json:

{"title": "The Odyssey", "price": 12.5, "stock": 4}

json.dumps() turned dictionaries into JSON text; asdict() (M5) turned your Book dataclasses into dictionaries. The chain Book → asdict() → JSON → HTTP response is the backbone of lesson 10-03, and you already know two of its three links. A web API (Application Programming Interface) is nothing more than a set of URLs designed for programs: same HTTP requests, same structure, but JSON instead of HTML in the body.

What a framework does for you

You could write a web server with Python's standard library (http.server exists), just as you could write your own tests without pytest. Nobody does, for the same reason: a web framework solves out of the box the problems every web application shares.

  • Routing: mapping GET /book/Hamlet to your Python function that knows how to answer. Without a framework, this would be a giant if/elif over the path.
  • Templates: generating HTML by mixing a fixed structure with variable data (the catalog, a search result), without concatenating strings by hand.
  • Forms: reading submitted data, converting types, validating.
  • Security: protection against classic attacks (malicious HTML injection, request forgery) you didn't even know existed — and which we'll discuss in 10-05.
  • Errors: turning exceptions into reasonable 404/500 responses instead of letting the server fall over.

The framework takes care of the HTTP plumbing; you write only what makes your application unique. In Papyrus, that unique part is already written and tested: it's the papyrus/ package.

The Python landscape: Flask, Django and FastAPI

Python has three dominant frameworks, and this module will teach you the first two:

Flask Django FastAPI
Philosophy Micro: minimal core, you add what you need Batteries included: comes with everything Modern, specialized in APIs
Learning curve Gentle: an app in 5 lines Steeper: structure and conventions Gentle if you've mastered type hints (M8)
Ships with Routes, templates, development server Also: ORM (database), admin, forms, users Automatic validation, automatic documentation
Shines at Small/medium projects, APIs, learning Large applications with many standard pieces High-performance APIs (async, M8)
In this module 10-02 and 10-03 10-04 and 10-05 Only this honest mention

All three install like any other package: pip install flask, pip install django — inside a virtual environment, as you learned in M1. FastAPI earns its mention because it's the third contender and makes heavy use of M8's type hints and asyncio for asynchronous servers; if you ever build a serious API, look into it. But for learning the fundamentals, Flask first (you'll see each piece in isolation) and Django second (you'll see what it's like to have them all integrated) is the classic path, and it's ours.

Common Mistakes and Tips

  • Confusing the URL with the page. The URL identifies the resource; the page is the response. Two requests to the same URL can return different things (stock changes). Think of URLs as questions, not as files.
  • Using GET for operations that modify data. This is the most dangerous conceptual mistake beginners make. If it modifies something: POST, PUT or DELETE. Always.
  • Returning 200 for everything. An API that answers 200 OK with the body {"error": "not found"} is a lying API: automated clients look at the code first. Correct codes from day one.
  • Believing you must master HTML/CSS/JavaScript before moving on. No. The HTML in this lesson is enough for the whole module. The backend is a complete specialty in its own right.
  • Forgetting that the server remembers nothing between requests. Each request arrives an orphan. Mechanisms to "remember" users (sessions, cookies) exist and frameworks provide them ready-made, but the base protocol is amnesiac — and that's a virtue, as you'll see in 10-03.

Exercises

Exercise 1: verbs and codes

For each Papyrus operation, state the appropriate HTTP method and the status code the server should return in the scenario described:

  1. Julia looks up Don Quixote's page (it exists).
  2. Ana adds Faust II with a valid title, price and stock.
  3. Luis looks up Moby-Dick's page (it isn't in the catalog).
  4. A script submits a new book with "price": "twenty euros".
  5. Ana updates Hamlet's stock to 9 units.

Exercise 2: URL dissection

Break down into its parts (scheme, host, port, path, query string) the URL:

http://localhost:5000/search?title=quixote&member=true

What key=value pair will the server receive from a form whose only <input> has name="title" if the user types faust?

Exercise 3: the reservation form

Write the HTML for a form so Julia can reserve a book: a text field for the title, a numeric field (type="number") called units, and a "Reserve" button. A reservation modifies data (it sets stock aside): choose action and method accordingly and justify the choice in an HTML comment (<!-- ... -->).

Solutions

Exercise 1:

  1. GET200 OK (successful read).
  2. POST201 Created (new resource created).
  3. GET404 Not Found (the request is well-formed; the resource doesn't exist — the BookNotFoundError case).
  4. POST400 Bad Request (the client sent invalid data; we validate and explain the reason, and never let it blow up into a 500).
  5. PUT200 OK (update of an existing resource).

Exercise 2: scheme http, host localhost, port 5000 (Flask's development port), path /search, query string title=quixote&member=true (two parameters: title=quixote and member=true). With the input name="title" and the value faust, the server receives title=faust — the resulting URL would be /search?title=faust.

Exercise 3:

<!-- method="post" because reserving modifies stock: operations with
     side effects must never travel in GET (robots follow GET links,
     and GET parameters end up in browser histories and logs). -->
<form action="/reserve" method="post">
    <label for="title">Title:</label>
    <input type="text" id="title" name="title">
    <label for="units">Units:</label>
    <input type="number" id="units" name="units" min="1">
    <button type="submit">Reserve</button>
</form>

Conclusion

You now have the map. The web is a dialogue of HTTP requests and responses between clients that ask and a server that runs code — soon, your code. Methods (GET reads, POST creates, PUT updates, DELETE removes) declare intent; status codes (200, 201, 400, 404, 500) summarize the outcome, and you've already seen that BookNotFoundError and the 404 are destined to meet. You can read a URL piece by piece, write minimal HTML — forms included — and tell the frontend (presentation, in the browser) from the backend (logic and data, in Python). And you know which framework you'll choose for what: Flask to start light, Django when you want the batteries included, FastAPI as a future elective. In the next lesson the theory ends: you'll install Flask in the venv, write a complete application in five lines, discover that @app.route is exactly one of those decorators from 08-02, and put the Papyrus catalog — The Odyssey, Hamlet, Don Quixote and Faust — in a browser for the first time.

Python Programming Course

Module 1: Introduction to Python

Module 2: Control Structures

Module 3: Functions and Modules

Module 4: Data Structures

Module 5: Object-Oriented Programming

Module 6: File Handling

Module 7: Error and Exception Handling

Module 8: Advanced Topics

Module 9: Testing and Debugging

Module 10: Web Development with Python

Module 11: Data Science with Python

Module 12: Final Project

© Copyright 2026. All rights reserved