Request & Response

The request object wraps everything about the current HTTP request, and a view's return value is converted by Flask into a response object. Understanding both ends is the core of writing web applications.

The Request Object

flask.request is a context-safe proxy you import and use directly inside views:

from flask import request

@app.get("/query")
def query():
    q = request.args.get("q", "")                    # Query parameter ?q=...
    page = request.args.get("page", 1, type=int)     # type= converts; falls back to default on failure
    tags = request.args.getlist("tag")               # ?tag=a&tag=b → ["a", "b"]
    return {"q": q, "page": page, "tags": tags}

Common attributes:

AttributeContents
request.argsURL query parameters (MultiDict)
request.formForm data (POST, application/x-www-form-urlencoded / multipart)
request.get_json()JSON request body
request.filesUploaded files
request.headersRequest headers
request.cookiesCookies
request.method / request.path / request.urlMethod and path
request.remote_addrClient IP (needs ProxyFix behind a proxy)

Forms and JSON

@app.post("/submit")
def submit():
    name = request.form.get("name")                 # Form field
    data = request.get_json(silent=True) or {}      # silent=True: returns None instead of raising on non-JSON
    return {"name": name, "data": data}

request.get_json() requires Content-Type: application/json by default, raising 415 otherwise.

File Uploads

import os
from werkzeug.utils import secure_filename

@app.post("/upload")
def upload():
    f = request.files.get("file")
    if f is None or f.filename == "":
        return {"error": "no file"}, 400
    filename = secure_filename(f.filename)          # Sanitizes the filename against traversal
    f.save(os.path.join(app.config["UPLOAD_FOLDER"], filename))
    return {"saved": filename}, 201

Remember to cap upload size: app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 (excess returns 413 automatically).

The Many Forms of Responses

View return values are wrapped by Flask automatically:

@app.get("/examples")
def examples():
    return "plain text"                      # 200, text/html
    return {"ok": True}                      # dict → JSON
    return [1, 2, 3]                         # list → JSON (Flask 2.2+)
    return "Created", 201                    # (body, status)
    return {"ok": True}, 200, {"X-Foo": "1"} # (body, status, headers)
    return render_template("page.html")      # Rendered template

Fine-Grained Control: make_response

When you need headers, cookies, etc., build the response object explicitly:

from flask import make_response

@app.get("/resp")
def resp():
    resp = make_response(render_template("page.html"), 200)
    resp.headers["Cache-Control"] = "no-store"
    resp.set_cookie("sid", "abc", httponly=True, samesite="Lax", max_age=3600)
    return resp

Redirects and File Downloads

from flask import redirect, url_for, send_file

@app.post("/save")
def save():
    ...
    return redirect(url_for("index"))        # Redirect after POST (the PRG pattern)

@app.get("/report")
def report():
    return send_file("reports/latest.pdf", as_attachment=True)

Request Hooks

Insert logic into the request lifecycle:

@app.before_request
def load_user():
    g.user = lookup_user(request.cookies.get("sid"))

@app.after_request
def add_security_headers(resp):
    resp.headers["X-Content-Type-Options"] = "nosniff"
    return resp                              # after_request must return the response object