Build a website with Python in 4 stages: isolate the project, define a route, render a template and prepare a production handover. Flask is a suitable small starting point when you want to understand that request-to-page flow. The worked example below displays a fictional repair desk. It does not accept bookings, store personal data or run a live service.
Python handles the application logic on the server. HTML still gives the browser its document structure, while CSS can control appearance. Learning Flask does not remove the need to understand the page the user receives. Start with a clear outcome: a visitor can see the desk’s demonstration information and a reference supplied in the query string, without creating any record.
How do you create a small Python website with Flask?
Create a separate project directory and environment. Flask’s installation guide recommends a virtual environment so one project’s packages do not disturb another project or the operating system. The following macOS/Linux commands are proposed setup instructions; no dependencies were installed for this article:
mkdir repair-desk-demo
cd repair-desk-demo
python3 -m venv .venv
. .venv/bin/activate
python -m pip install Flask
On Windows, the installation guide gives py -3 -m venv .venv and .venv\Scripts\activate for creating and activating the environment. Use the interpreter inside that environment when installing or running the project. Do not respond to an import error by installing packages into an unrelated shared application.
Create app.py. The Flask quickstart explains application construction, URL routing and template rendering. This original example passes a reference into a local template:
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
reference = request.args.get("reference", "SAMPLE-REPAIR")
return render_template("index.html", reference=reference)
The application object is the WSGI application. Its module name helps Flask locate resources. The route decorator connects the root URL to the function, and request.args reads query-string values. Naming the file flask.py would conflict with the library; keep the distinct filename above.
Create a templates directory and place index.html inside it. The template receives the reference as data rather than assembling raw HTML from user input:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Repair desk demonstration</title>
</head>
<body>
<main>
<h1>Repair desk</h1>
<p>Fictional reference: {{ reference }}</p>
<p>This page does not submit a booking or verify a repair.</p>
</main>
</body>
</html>
For an authorised local development session, the command is python -m flask --app app run. Flask’s development server is local by default. Open its reported local address and, for the proposed example, append ?reference=SAMPLE-SECOND to the URL. The expected behaviour is that the displayed reference changes. This is a teaching expectation, not a claim that a live server was started during authoring.
Jinja escapes values in HTML templates, as the quickstart documents. Leave that protection in place. Do not add the safe filter to an untrusted reference just to preserve a visitor’s markup. A query parameter is not a trusted record identifier, and displaying it does not mean the business has received, authenticated or accepted anything.
Make the page useful before adding features
Read the page with the reference missing, with a long reference and with unfamiliar characters. Check whether the title and heading describe the same task and whether the text still makes sense without styling. The viewport declaration helps the browser use the device width, but it does not prove the layout has passed mobile accessibility checks. Inspect the actual result before calling it ready.
When adding CSS later, use the documented static-file mechanism and generate the asset URL with Flask’s url_for helper. Keep presentation changes separate from data collection. A contact form introduces validation, disclosure, retention and abuse-handling decisions that this read-only demonstration deliberately does not implement. Those requirements need a new acceptance record before a submission button is added.
How do you check the example and hand it over safely?
Flask’s testing guide provides a test client that exercises the application without a live server. After installing the project dependencies in your own environment, the following proposed check_app.py checks our own page behaviour:
from app import app
app.config["TESTING"] = True
with app.test_client() as client:
normal = client.get("/")
assert normal.status_code == 200
assert b"SAMPLE-REPAIR" in normal.data
hostile = client.get("/", query_string={"reference": "<script>sample</script>"})
assert b"<script>sample</script>" not in hostile.data
assert b"<script>sample</script>" in hostile.data
missing = client.get("/missing-page")
assert missing.status_code == 404
This check should be run with python check_app.py only after the files and dependencies exist. It does not open a network listener, but it still requires the Flask environment. These assertions are supplied for execution; they were not run here. They test expected application output rather than claiming the framework or deployment has been independently audited.
| Scenario | Expected result | Remaining decision |
|---|---|---|
| Root page without a reference | Fictional default displayed | Suitable visitor-facing wording |
| Reference supplied | Value appears as text | Whether real references should be accepted |
| Markup supplied as a reference | Escaped text, no injected script | Wider input and output review |
| Unknown route | Not found | Helpful production error page |
| Production handover | Dedicated serving configuration | Actual platform and operating owner |
Original contribution: repair-desk boundary record
Write a handover record for SAMPLE-REPAIR with input, display, persistence, authority and owner. For this example, input is an optional query value, display is escaped template text, persistence is absent and authority is absent: the page does not prove a repair exists. That explicit record prevents a demonstration reference becoming a customer status promise.
Now propose a real status lookup on a separate row. It needs an authorised data source, access rules and a response for unavailable records. Add a deletion decision before storing customer information. Keep this row marked unimplemented until those behaviours are demonstrated. The worked distinction is useful when a small prototype attracts requests to become an operational portal.
Flask’s production guidance says to use a dedicated WSGI server or hosting platform, not its development server or debugger. This applies to private operational use too. Select deployment, HTTPS termination, process ownership, configuration storage, logs and recovery deliberately; a local page loading successfully establishes none of them. Do not enable the interactive debugger on an operational service.
Relate the prototype to your web development requirements and web design objectives. To scope the next step, get started with the boundary record and fictional examples rather than live customer data.
By Spout Team. AI-assisted drafting by Codex using the linked primary sources.