Getting Started
First Application
Flask is a microframework — it provides the essentials and lets you add what you need. debug=True enables auto-reload and detailed error pages.
# install Flask
pip install flask
# app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
return render_template("index.html")
if __name__ == "__main__":
app.run(debug=True)Running the Development Server
Never use the built-in server in production. FLASK_APP points to the module containing the app instance. Use --host=0.0.0.0 to make it reachable on the network.
# Set the entry point and enable debug mode
export FLASK_APP=app.py
export FLASK_ENV=development
flask run
# Override host and port
flask run --host=0.0.0.0 --port=5000
# Or run the module directly
python app.pyVirtual Environment
A virtual environment isolates project dependencies from the system Python. requirements.txt records exact versions so others can reproduce the environment with pip install -r requirements.txt.
# Create an isolated environment
python -m venv venv
# Activate (Linux/macOS)
source venv/bin/activate
# Activate (Windows PowerShell)
venv\Scripts\Activate.ps1
# Install dependencies
pip install flask
pip freeze > requirements.txtProject Structure
Flask does not enforce a layout, but separating static files, templates, and config is conventional. The instance/ folder holds deployment-specific secrets that should not be version-controlled.
myapp/
app.py # application factory or instance
config.py # configuration classes
requirements.txt # pinned dependencies
instance/
config.py # secret instance config (not in VCS)
static/ # CSS, JS, images
css/style.css
templates/ # Jinja2 templates
base.html
index.html
models.py # database models
views.py # route handlersApplication Factory Pattern
The factory pattern defers app creation until a function is called, which is essential for multiple instances, testing, and extensions. Flask's --app option accepts a callable that returns an app.
from flask import Flask
def create_app(config_name="default"):
app = Flask(__name__)
app.config.from_object(f"config.{config_name.title()}Config")
from .views import bp as views_bp
app.register_blueprint(views_bp)
@app.route("/health")
def health():
return {"status": "ok"}
return app
# Run with: flask --app myapp create_app run
# or export FLASK_APP=myapp:create_appMinimum Application Anatomy
Passing __name__ tells Flask where to find static files and templates. You can override folder locations with constructor arguments if your layout differs from the defaults.
from flask import Flask
app = Flask(
__name__,
static_folder="static",
template_folder="templates",
static_url_path="/static",
)
# __name__ lets Flask locate resources relative to the module.
# Route decorators map URL patterns to view functions.
@app.route("/")
def index():
return "Hello, Flask!"Routing
Variable Rules
Sections marked with <converter:varname> capture URL segments. Built-in converters: string (default), int, float, path (accepts slashes), uuid. The value is passed as a function argument.
from flask import Flask
app = Flask(__name__)
@app.route("/user/<username>")
def show_user(username):
return f"User: {username}"
@app.route("/post/<int:post_id>")
def show_post(post_id):
return f"Post #{post_id}"
@app.route("/path/<path:subpath>")
def show_path(subpath):
return f"Path: {subpath}"URL Converters
Converters validate and cast URL variables. Register a custom BaseConverter subclass on app.url_map.converters to enforce a regex pattern, such as product codes.
@app.route("/item/<int:item_id>")
def item_int(item_id):
return f"Integer ID: {item_id}"
@app.route("/weight/<float:kg>")
def weight(kg):
return f"{kg} kg"
@app.route("/uid/<uuid:token>")
def by_token(token):
return str(token)
# Custom converter
from werkzeug.routing import BaseConverter
class RegexConverter(BaseConverter):
def __init__(self, url_map, *items):
super().__init__(url_map)
self.regex = items[0]
app.url_map.converters["re"] = RegexConverter
@app.route("/code/<re:[a-z]{3}-[0-9]{4}:code>")
def code(code):
return codeUnique URLs and Redirection
Trailing slashes matter. A route ending in '/' will redirect the non-slash form; a route without '/' returns 404 for the slashed form. Pick one convention per route and stay consistent.
@app.route("/projects/")
def projects():
return "Projects page"
# /projects/ -> 200 OK
# /projects -> 301 redirect to /projects/
@app.route("/about")
def about():
return "About page"
# /about -> 200 OK
# /about/ -> 404 Not FoundHTTP Methods
Use the methods argument to accept multiple verbs, or the @app.get / @app.post shortcuts (Flask 2.0+) for single-method routes. GET is the default.
from flask import request, render_template
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
return do_login()
return render_template("login.html")
# Method-specific shortcuts
@app.get("/profile")
def profile_get():
return render_template("profile.html")
@app.post("/profile")
def profile_post():
return update_profile()URL Building with url_for
url_for generates URLs from the endpoint name (the view function name by default). It survives URL changes and produces absolute URLs when _external=True is passed.
from flask import Flask, url_for
app = Flask(__name__)
@app.route("/")
def index():
# builds '/user/alice' instead of hardcoding
return f'<a href="{url_for("show_user", username="alice")}">Alice</a>'
@app.route("/user/<username>")
def show_user(username):
return f"Hello {username}"
with app.test_request_context():
print(url_for("show_user", username="bob")) # /user/bobMultiple Rules for One View
add_url_rule registers a URL without a decorator. By giving each rule a distinct endpoint but the same view function with defaults, one handler serves multiple URL shapes.
from flask import Flask
app = Flask(__name__)
def show(id=None, name=None):
if id is not None:
return f"By id: {id}"
return f"By name: {name}"
app.add_url_rule("/by-id/<int:id>", "by_id", show, defaults={"name": None})
app.add_url_rule("/by-name/<name>", "by_name", show, defaults={"id": None})
# Two endpoints share one function with different defaults.Request and Response
The Request Object
request is a thread-local proxy bound to the current request context. It exposes the URL, method, headers, body, and client info without being passed explicitly.
from flask import Flask, request
app = Flask(__name__)
@app.route("/info")
def info():
return {
"url": request.url,
"method": request.method,
"remote_addr": request.remote_addr,
"user_agent": str(request.user_agent),
"headers": dict(request.headers),
}Making a Response
make_response gives full control over status, headers, and body. For simple cases, return a string, a dict (JSON), or a tuple of (body, status, headers).
from flask import Flask, make_response
app = Flask(__name__)
@app.route("/")
def index():
resp = make_response("Hello, World!")
resp.status_code = 201
resp.headers["X-Custom"] = "yes"
resp.set_cookie("visited", "1")
return resp
# Returning a tuple: (body, status, headers)
@app.route("/quick")
def quick():
return "OK", 202, {"X-Quick": "1"}Response Objects
Use Response when you need a custom mimetype (e.g., XML, CSV) or headers. Setting Content-Disposition to attachment forces a download in the browser.
from flask import Response
@app.route("/xml")
def xml():
return Response("<msg>hi</msg>", mimetype="application/xml")
@app.route("/text")
def text():
return Response("plain text", mimetype="text/plain")
@app.route("/csv")
def csv():
body = "a,b,c\n1,2,3\n"
headers = {"Content-Disposition": "attachment; filename=data.csv"}
return Response(body, mimetype="text/csv", headers=headers)Custom Status and Headers
Return a (body, status, headers) tuple for concise custom responses. Use 204 No Content for successful responses with no body, and standard HTTP status codes for semantics.
@app.route("/created")
def created():
return "done", 201, {"Location": "/resource/1"}
@app.route("/no-content")
def no_content():
return "", 204
@app.route("/teapot")
def teapot():
return "I'm a teapot", 418Streaming Responses
A generator return value streams data to the client. Useful for large files (avoids loading the whole file into memory) and Server-Sent Events. Set an appropriate mimetype.