Programming for the Web

A Walking Skeleton Application


Learning Objectives

  • You can create a small FastAPI application and start and stop its local server.
  • You can identify the function selected for a request method and path.
  • You can distinguish a returned browser response from a printed terminal message.

In the game exercises, supplied code called your functions after a player acted. A web application also responds to events. A browser sends a request, the server runs the appropriate code, and the server sends a response back.

We will start with a walking skeleton: a small working application that returns a short text response to the browser.

Python Environment Setup

Create a local folder named web-app and open it in your IDE from Part 5’s programming environment. Use Python 3.11 or newer.

Virtual Environments and requirements.txt

A virtual environment gives this project its own Python environment and installed packages, so other projects can use different versions.

Python’s venv module creates it in the .venv folder. Activating the environment makes this terminal use its Python interpreter and installed commands.

requirements.txt lists the external packages the project needs. For example, fastapi==0.135.2 requests version 0.135.2 of FastAPI. The listed versions keep the examples compatible.

pip installs packages; -r requirements.txt tells it to read this list. Create the environment and install the packages once, then reuse .venv in later chapters.

Inside web-app, create a file named requirements.txt with these contents:

fastapi==0.135.2
uvicorn==0.42.0
jinja2==3.1.6
python-multipart==0.0.32

FastAPI connects incoming requests to Python functions, and Uvicorn runs the server. Jinja renders HTML templates, while python-multipart lets the application receive submitted form fields. Pip also installs the supporting packages they need.

Open a terminal in web-app, beside requirements.txt. Create the environment once, then activate it. In Windows PowerShell:

py -m venv .venv
.\.venv\Scripts\Activate.ps1

On macOS/Linux:

python3 -m venv .venv
source .venv/bin/activate
If venv Is Missing

Some Linux installations require you to install Python’s venv support separately. If creating the environment reports that venv or ensurepip is unavailable, install the package named in the error message. On Ubuntu, the package for the default Python version is python3-venv:

sudo apt install python3-venv

Then run the environment creation and activation commands above again. The FastAPI app subsection of Ubuntu’s tutorial shows the same virtual-environment setup.


If PowerShell Blocks Activation

Open a Command Prompt terminal in web-app and run .venv\Scripts\activate.bat. Continue with the same later commands in that terminal; no PowerShell settings need changing.

Now install the packages using the same command on either operating system:

pip install -r requirements.txt

The terminal prompt may now show (.venv). Activation applies throughout this terminal. In each new terminal, open web-app and repeat the activation line; reuse the existing environment and packages.

Routes and Text Responses

Save this complete application as main.py inside web-app, beside requirements.txt:

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.get("/tasks")
def show_tasks():
print("show_tasks: handling GET /tasks")
return PlainTextResponse("Study tasks")
@app.get("/about")
def about():
print("about: handling GET /about")
return PlainTextResponse("A small study task application written in Python.")

app refers to the application object. The @ notation is called a decorator. Each @app.get line registers the function immediately below it as a route; keep them together when editing these examples.

The browser’s requests and the server’s responses use HTTP, the Hypertext Transfer Protocol. The get in the decorator names the HTTP method GET, which asks for content. The string names the request’s path.

Starting and Stopping the Server

Your folder now contains:

web-app/
.venv/
requirements.txt
main.py

In the activated terminal in web-app, start the server:

uvicorn main:app --reload

This command is the same on Windows, macOS, and Linux. main:app selects the application named app in the current folder’s main.py. Uvicorn uses http://127.0.0.1:8000 by default: 127.0.0.1 refers to this computer, and 8000 is the port where the server waits for requests.

--reload watches Python files in this source folder. When you save a Python change, wait for the server terminal to report that the application has started again, then refresh the browser. Reloading Python does not refresh the browser page automatically.

Leave the terminal running and visit http://127.0.0.1:8000/tasks. You should see Study tasks. Before visiting /about, read the about() function and predict the text the browser will display. Then visit /about and compare the page with your prediction.

The diagram below follows these two requests: each path selects its own route, while the same server keeps running.

Two GET requests handled by the running Python server.

Leave the server running while editing main.py. Use Ctrl+C to stop it when you finish working.

Following a Request with print

Look at the server terminal, where Uvicorn is running. Python’s print output appears there alongside Uvicorn’s messages.

Opening /tasks and then /about runs the corresponding function and prints these two lines:

show_tasks: handling GET /tasks
about: handling GET /about

Each print runs when its route handles a request. Refreshing /tasks calls show_tasks again and adds another line.

To inspect a value during the request, replace show_tasks in main.py with the following function. Keep its @app.get("/tasks") line:

def show_tasks():
print("request: GET /tasks")
print("entered: show_tasks")
message = "Study tasks"
print("message:", repr(message))
print("outcome: return text response")
return PlainTextResponse(message)

repr(message) displays quotation marks around the string, which also makes surrounding spaces easier to see.

Save main.py, wait for the server’s automatic reload to finish, and visit or refresh http://127.0.0.1:8000/tasks. For that one request, your own printed lines are exactly:

request: GET /tasks
entered: show_tasks
message: 'Study tasks'
outcome: return text response

The browser displays:

Study tasks

The terminal trace shows which function ran, the value of message, and whether execution reached the return. Only return PlainTextResponse(message) supplies the browser response.

Refresh twice to see two more traces. Each request runs the route again.

Practise the Route Notation

In the exercise editor, warm up with small edits to its supplied main.py. Each step supplies its own code.

Match the request’s method and path to a registered function in the next question.

A Page Request

0 / 5 points

These routes belong to a running application:

@app.get("/tasks")
def first_page():
return PlainTextResponse("Study tasks")
@app.get("/about")
def second_page():
return PlainTextResponse("A small study task application written in Python.")

The browser makes a GET request to /about. Which function runs to answer this request?

Add another address using the same decorator-and-function pattern.

A Study Tip Route

0 / 15 points

Add a route to main.py so GET requests to /tip receive the plain text response Break large tasks into small steps..

Keep the two supplied routes unchanged.

Changing a printed label changes the terminal trace; changing the returned message changes the page. Follow the two kinds of output in the next question.

A Request's Output

0 / 5 points

The browser makes one GET /tasks request. These statements run inside the supplied route:

print("Preparing page")
message = "Study tasks"
return PlainTextResponse(message)

Where does each of the two strings appear?

Server Startup Problems and Missing Routes

A response includes a status code. In the server terminal’s request log, the successful requests shown so far have the code 200. Visit /missing and find its request in that log: the running application answers with 404, meaning it could not find that path. A stopped server instead cannot answer the browser at all.

Troubleshooting the Local Server

Use the server terminal and the browser’s response to choose what to check:

ObservationWhat to check
The terminal cannot find uvicornIn this terminal, activate .venv from web-app.
Uvicorn cannot import mainCheck that the terminal is in web-app, beside main.py.
The address is already in useStop the earlier server before starting another.
The browser cannot connectCheck that the server is running and the host and port match.
The server answers with 404Compare the address path with the registered routes.
A saved Python edit does not appearCheck that this server uses --reload, wait for reload to finish, and refresh the browser.

Two Attempts to Open a Page

0 / 5 points

An application has only two registered paths: /tasks and /about.

  1. The browser opens /unknown. The server terminal logs this request with status 404.
  2. The student presses Ctrl+C in that terminal, then opens /tasks. The browser reports a connection error.

Which explanation accounts for both observations?

Check Your Understanding

  1. You installed the packages in .venv yesterday and have now opened a new terminal in web-app. What must you repeat before starting the server, and which setup steps can you reuse?
  2. You rename the function about to information but leave its @app.get("/about") decorator unchanged. Does the page’s address change? Explain what registers the address.
  3. You save a Python change and the server terminal reports that the application has restarted, but the browser still shows the earlier response. What should you do next, and why is the server’s automatic reload insufficient on its own?
  4. You start the server without opening a page. Should the print statements inside show_tasks have run yet? Explain when they run and what happens to the terminal trace when you refresh /tasks twice.