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
- The client-server model: who asks and who answers
- HTTP: the protocol of the conversation
- HTTP methods: the verbs of the web
- Status codes: the traffic lights of responses
- Anatomy of a URL
- HTML: the bare minimum for this module
- Frontend and backend: two worlds, one border
- JSON: the language of APIs (an old friend)
- What a framework does for you
- 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:
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: valueformat — 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:
GETmust never modify data. A search engine indexing your site will follow every GET link it finds; if/sell/Hamletwere a GET, a robot could accidentally drain your stock.POST,PUTandDELETEcarry 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,
httporhttps(thesis encrypted HTTP; nowadays, alwayshttpsin production). - Host: the server's name. In development it will be
localhostor127.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
?, inkey=valuepairs 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>actionis the path the form submits to;methodis the HTTP verb (getfor searches,postfor creating/modifying).- Each
<input>with anamebecomes a parameter: when searching for "odyssey", the browser requestsGET /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:
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/Hamletto your Python function that knows how to answer. Without a framework, this would be a giantif/elifover 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 OKwith 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:
- Julia looks up Don Quixote's page (it exists).
- Ana adds Faust II with a valid title, price and stock.
- Luis looks up Moby-Dick's page (it isn't in the catalog).
- A script submits a new book with
"price": "twenty euros". - 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:
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:
GET→200 OK(successful read).POST→201 Created(new resource created).GET→404 Not Found(the request is well-formed; the resource doesn't exist — theBookNotFoundErrorcase).POST→400 Bad Request(the client sent invalid data; we validate and explain the reason, and never let it blow up into a 500).PUT→200 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
- Introduction to Python
- Setting Up the Development Environment
- Python Syntax and Basic Data Types
- Variables and Constants
- Basic Input and Output
- Virtual Environments and Package Management
Module 2: Control Structures
Module 3: Functions and Modules
- Defining Functions
- Function Arguments
- Lambda Functions
- Modules and Packages
- Standard Library Overview
Module 4: Data Structures
Module 5: Object-Oriented Programming
Module 6: File Handling
Module 7: Error and Exception Handling
- Introduction to Exceptions
- Handling Exceptions
- Raising Exceptions
- Custom Exceptions
- Best Practices and Error Logging
Module 8: Advanced Topics
- Type Hints
- Decorators
- Generators
- Context Managers
- Concurrency: Threads and Processes
- Asyncio for Asynchronous Programming
Module 9: Testing and Debugging
- Introduction to Testing
- Unit Testing with unittest
- Testing with pytest
- Test-Driven Development
- Debugging Techniques
- Using pdb for Debugging
Module 10: Web Development with Python
- Introduction to Web Development
- Flask Framework Fundamentals
- Building REST APIs with Flask
- Introduction to Django
- Building Web Applications with Django
Module 11: Data Science with Python
- Introduction to Data Science
- NumPy for Numerical Computing
- Pandas for Data Manipulation
- Matplotlib for Data Visualization
- Introduction to Machine Learning with scikit-learn
