Flask, Routing, and Templates
Build server side apps with Flask, manage routes, and render Jinja templates effectively.
Content
Request and response objects
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Flask Request and Response Objects — The Busy Mailroom of Your Web App
"If the route is the address, then the request is the letter and the response is the reply envelope." — Your slightly dramatic CS50 TA
You already learned how to register routes, build URLs with url_for, and organize apps with the application factory. Now let’s meet the runtime duo that actually carries data: the request and response objects. They’re how user input gets into your Python code and how your code talks back to the browser (or API client). This lesson assumes you’ve got basic Python chops (dicts, functions, types) from Python Fundamentals for the Web — we’ll lean on those skills to parse, validate, and shape data cleanly.
Quick map: where this fits
- Routing decides which function runs for a URL. Once matched, Flask gives you the request object.
- Your view function does work and returns data. Flask converts that into a response object and sends it back.
This is the runtime handshake of every web request.
1) The request object — what came in and how to read it
Import it with:
from flask import request
What it is
- A proxy (werkzeug LocalProxy) that points to the Request for the current request context. That means you can call
requestinside route functions without passing it around. - Contains HTTP method, headers, query string, form data, files, JSON body, cookies, and connection info.
Common attributes (micro explanations)
- request.method — GET, POST, PUT, DELETE, etc.
- request.args — ImmutableMultiDict of query params (?q=flask&lang=py). Use
request.args.get('q')orrequest.args.getlist('tag'). - request.form — form fields for POSTed HTML forms.
- request.values — combines args and form (use carefully — be explicit when possible).
- request.files — uploaded files (FileStorage objects). Use
werkzeug.utils.secure_filenamebefore saving. - request.get_json() — parsed JSON body (returns dict or list). Use
force=Truesparingly. - request.headers — dict-like access to headers.
- request.cookies — cookies sent by the client.
- request.path / request.url / request.base_url / request.url_root — helpful for constructing links or redirects.
- request.remote_addr — client IP (not always reliable behind proxies — consider X-Forwarded-For).
Example: read a few things
@app.route('/search')
def search():
q = request.args.get('q', '') # query param
page = int(request.args.get('page', 1))
return render_template('results.html', q=q, page=page)
@app.route('/api/items', methods=['POST'])
def create_item():
data = request.get_json()
name = data.get('name')
# validate and insert to DB
return jsonify(success=True, id=123), 201
Safety notes
- Never trust user input. Cast and validate (ints, emails, lengths). Use schema validators (marshmallow, pydantic) for APIs.
- When handling files, use
secure_filename()and limitMAX_CONTENT_LENGTHin config. - For forms, protect from CSRF (Flask-WTF or other CSRF tokens).
2) The response object — what you send back
Flask view functions can return multiple things:
- A string (body)
- A tuple (body, status), (body, headers), or (body, status, headers)
- A Response object
- Result of
render_template()orjsonify()— both produce Response objects under the hood.
Build or modify a response
from flask import make_response, jsonify, redirect, url_for
@app.route('/hello')
def hello():
html = render_template('hello.html')
resp = make_response(html)
resp.headers['X-Powered-By'] = 'CS50-wizardry'
resp.set_cookie('seen_hello', '1', max_age=60*60*24)
return resp
@app.route('/go')
def go():
return redirect(url_for('hello')) # redirect is a Response
@app.route('/api/status')
def status():
return jsonify(ok=True), 200
Response class and streaming
- Use
flask.Responseif you need fine control (content-type, direct iterator streaming, etc.). - For large downloads or server-sent events, return a generator and wrap with
Response(generator, mimetype='text/plain').
from flask import Response, stream_with_context
@app.route('/stream')
def stream():
def generate():
for i in range(1000):
yield f"data: {i}\n"
return Response(stream_with_context(generate()), mimetype='text/event-stream')
3) Status codes, headers, cookies, and helpers
- Return tuples:
return 'Not found', 404orreturn jsonify(error='bad'), 400 - Use
abort(404)to raise an HTTP exception handled by Flask (customize with errorhandler). - Set headers with
response.headers[...]. - Cookies:
response.set_cookie('name', 'value')andresponse.delete_cookie('name').
Tip: Keep your responses explicit for APIs (JSON + 200/201/204). For HTML, make your templates the source of truth and set caching headers judiciously.
4) Lifecycle hooks — touch the request/response in the pipeline
@app.before_requestruns before each request — good for auth checks or loading user into g.@app.after_requestreceives the Response and can modify headers (useful for CORS, security headers).@app.teardown_requestruns after the response is sent (or on error) for cleanup.
@app.after_request
def add_security_headers(response):
response.headers['X-Frame-Options'] = 'DENY'
return response
5) Request context gotcha
Because Flask uses thread-local proxies, request only exists inside an application context. If you do something offline (outside a request) you must create an app context or test context:
with app.test_request_context('/?q=flask'):
assert request.args.get('q') == 'flask'
Trying to access request outside an active request will raise a RuntimeError. This is by design — it avoids accidentally sharing request data between threads.
Quick reference table
| Action | Request side | Response side |
|---|---|---|
| Get query param | request.args.get('q') | - |
| Get form field | request.form['email'] | - |
| Get JSON | request.get_json() | - |
| Return JSON | - | return jsonify(data), 200 |
| Redirect | - | return redirect(url_for('index')) |
| Set cookie | - | resp.set_cookie('k','v') |
| Modify headers | - | resp.headers['X']='Y' |
Key takeaways
- The request is your read-only snapshot of what the client sent — method, headers, query, form, files, JSON.
- The response is what you build and return — body, status, headers, cookies, streaming.
- Use
make_response,jsonify,redirect, andResponsefor explicit control. - Respect the request context:
requestis a proxy that only works during a request. - Validate everything. You’re the gatekeeper between user data and your database.
"If the request is the question, the response better be the answer — and the best answers are precise, validated, and politely formatted."
If you want, I can add a cheat-sheet (copy-paste-ready snippets) for GET/POST/JSON/file handling + secure file saving and pagination patterns. Want that?
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!