Your first Flask app

Routes, view functions, the development server, and how Flask decides which function handles a request.

The smallest app

# app.py
from flask import Flask

app = Flask(__name__)

@app.get("/")
def index():
    return "Hello, world"

@app.get("/health")
def health():
    return {"ok": True}          # dict -> JSON automatically

if __name__ == "__main__":
    app.run(debug=True)
pip install flask
python app.py                 # http://127.0.0.1:5000
flask --app app run --debug    # the CLI way
⚠️
debug=True gives you an interactive traceback in the browser — and lets anyone who finds the page run code on your machine. Development only, never in production.

Routing rules

@app.get("/posts/<int:post_id>")       # converter: int, float, path, uuid
def show_post(post_id):
    return f"Post {post_id}"

@app.route("/submit", methods=["GET", "POST"])
def submit():
    ...

@app.get("/files/<path:subpath>")      # path allows slashes
def serve(subpath):
    ...

# build a URL instead of hardcoding it
from flask import url_for
url_for("show_post", post_id=42)       # /posts/42

Flask matches the most specific rule and raises on ambiguity. Using url_for means renaming a route does not break your templates and links.

Configuration and project layout

myapp/
  app.py
  config.py
  templates/
  static/
  requirements.txt
import os

app.config.update(
    SECRET_KEY=os.environ["SECRET_KEY"],   # required for sessions and flash
    DEBUG=False,
    JSON_SORT_KEYS=False,
)
💡
Read secrets from the environment. A SECRET_KEY committed to Git lets anyone forge session cookies.

FAQ

Flask or FastAPI?
Flask when you want a small, explicit framework and HTML templates; FastAPI when you want typed request models, automatic OpenAPI docs and async-first APIs.
Why does my code change not take effect?
The reloader was off. Run with --debug, or restart the server.

Templates with Jinja2 Forms, sessions and errors

Last refreshed 2026-09-18.