DEV Community

Cover image for Day 79 of #100DaysOfCode — Flask Routes, Requests, and Responses
M Saad Ahmad
M Saad Ahmad

Posted on

Day 79 of #100DaysOfCode — Flask Routes, Requests, and Responses

Yesterday, I got Flask running and understood the basic structure. Today, on day 79, I delved deeper into the three things at the core of every Flask application: routes, the request object, and responses. This is the layer where every HTTP interaction happens, and getting comfortable here makes everything else in Flask easier.


Routes in Depth

The url_for Function

Hardcoding URLs in templates and redirects is a bad habit. If you rename a route, every hardcoded URL breaks. Flask's url_for generates URLs by function name instead:

from flask import Flask, url_for

app = Flask(__name__)

@app.route('/')
def home():
    return 'Home'

@app.route('/profile/<username>')
def profile(username):
    return f'Profile: {username}'

with app.test_request_context():
    print(url_for('home'))                    # /
    print(url_for('profile', username='haris'))  # /profile/haris
Enter fullscreen mode Exit fullscreen mode

In Django this was {% url 'view_name' %} in templates and reverse() in Python code. Flask's equivalent is url_for() in both places. Same concept, different name.

Route Converters

Flask supports several built-in type converters for URL variables:

@app.route('/post/<int:id>')       # integer
def post(id):
    return f'Post {id}'

@app.route('/price/<float:amount>')  # float
def price(amount):
    return f'Price: {amount}'

@app.route('/files/<path:filepath>')  # path — allows slashes
def files(filepath):
    return f'File: {filepath}'

@app.route('/user/<uuid:user_id>')   # UUID
def user(user_id):
    return f'User: {user_id}'
Enter fullscreen mode Exit fullscreen mode

The path converter is unique, unlike string, which stops at slashes; path captures everything, including slashes. Useful for file path URLs.

Trailing Slashes

Flask has an opinion about trailing slashes:

@app.route('/about/')   # with trailing slash
def about():
    return 'About'
Enter fullscreen mode Exit fullscreen mode

If you define a route with a trailing slash and someone visits without it, Flask redirects them automatically. If you define without a trailing slash and someone visits with it, Flask returns a 404. Be intentional about what you use.

HTTP Methods

from flask import Flask, request

app = Flask(__name__)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return 'Processing login'
    return 'Show login form'
Enter fullscreen mode Exit fullscreen mode

You can also separate methods into different functions using add_url_rule:

@app.get('/items')
def get_items():
    return 'List items'

@app.post('/items')
def create_item():
    return 'Create item'
Enter fullscreen mode Exit fullscreen mode

@app.get() and @app.post() are shorthand decorators added in Flask 2.0. Cleaner than methods=['GET'] when you want to keep handlers separate.


The Request Object

The request object is Flask's way of giving you everything about the incoming HTTP request. Import it from Flask, and it's available inside any route function.

from flask import Flask, request

app = Flask(__name__)
Enter fullscreen mode Exit fullscreen mode

Unlike Django, where request is passed as a parameter to every view function, Flask's request is a context variable; you import it once and use it anywhere inside a request context. This is one of Flask's more distinctive patterns.

Query Parameters

@app.route('/search')
def search():
    keyword = request.args.get('keyword', '')
    location = request.args.get('location', '')
    return f'Searching for {keyword} in {location}'
Enter fullscreen mode Exit fullscreen mode

request.args is a dictionary of query string parameters: the stuff after ? in the URL. request.args.get('key', default) works exactly like a Python dict's .get(). In Django, this was request.GET.get('keyword').

Form Data

@app.route('/submit', methods=['POST'])
def submit():
    name = request.form.get('name')
    email = request.form.get('email')
    return f'Received: {name}, {email}'
Enter fullscreen mode Exit fullscreen mode

request.form contains POST data from HTML forms. In Django, this was request.POST.get('name').

JSON Data

@app.route('/api/data', methods=['POST'])
def receive_data():
    data = request.get_json()
    name = data.get('name')
    return f'Received JSON: {name}'
Enter fullscreen mode Exit fullscreen mode

request.get_json() parses the request body as JSON and returns a Python dictionary. This is what API clients send when they POST JSON instead of form data.

File Uploads

@app.route('/upload', methods=['POST'])
def upload():
    file = request.files.get('avatar')
    if file:
        file.save(f'uploads/{file.filename}')
        return 'File uploaded'
    return 'No file received'
Enter fullscreen mode Exit fullscreen mode

request.files contains uploaded files. Call .save() to write the file to disk.

Request Headers and Cookies

@app.route('/info')
def info():
    user_agent = request.headers.get('User-Agent')
    token = request.headers.get('Authorization')
    session_id = request.cookies.get('session_id')
    return f'Agent: {user_agent}'
Enter fullscreen mode Exit fullscreen mode

request.headers — all HTTP headers as a dictionary.
request.cookies — all cookies sent with the request.

Useful Request Properties

@app.route('/details')
def details():
    print(request.method)       # GET, POST, PUT, etc.
    print(request.url)          # full URL including query string
    print(request.base_url)     # URL without query string
    print(request.path)         # path only, e.g. /details
    print(request.host)         # hostname, e.g. 127.0.0.1:5000
    print(request.is_json)      # True if Content-Type is application/json
    print(request.remote_addr)  # client IP address
    return 'Check the console'
Enter fullscreen mode Exit fullscreen mode

Responses in Depth

Return a String

The simplest response: Flask wraps it in a 200 OK response automatically:

@app.route('/')
def home():
    return 'Hello'
Enter fullscreen mode Exit fullscreen mode

Return with a Status Code

@app.route('/not-found')
def not_found():
    return 'Page not found', 404

@app.route('/created')
def created():
    return 'Resource created', 201
Enter fullscreen mode Exit fullscreen mode

A tuple of (body, status_code), clean and readable.

Return with Headers

@app.route('/download')
def download():
    return 'file content', 200, {
        'Content-Disposition': 'attachment; filename=file.txt',
        'Content-Type': 'text/plain'
    }
Enter fullscreen mode Exit fullscreen mode

A tuple of (body, status_code, headers_dict).

make_response

When you need more control:

from flask import make_response

@app.route('/custom')
def custom():
    response = make_response('Custom response', 200)
    response.headers['X-Custom'] = 'value'
    response.set_cookie('user', 'haris')
    return response
Enter fullscreen mode Exit fullscreen mode

make_response gives you a proper response object to manipulate before returning.

Redirects

from flask import redirect, url_for

@app.route('/old-page')
def old_page():
    return redirect(url_for('home'))

@app.route('/external')
def external():
    return redirect('https://google.com')
Enter fullscreen mode Exit fullscreen mode

redirect() combined with url_for() is the Flask equivalent of Django's redirect('view_name').

JSON Responses

from flask import jsonify

@app.route('/api/user')
def api_user():
    user = {
        'id': 1,
        'username': 'haris',
        'role': 'developer'
    }
    return jsonify(user)
Enter fullscreen mode Exit fullscreen mode

jsonify converts a Python dictionary to a JSON response with the correct Content-Type: application/json header set automatically.

Abort — Early Exit with an Error

from flask import abort

@app.route('/admin')
def admin():
    if not is_admin():
        abort(403)
    return 'Admin panel'
Enter fullscreen mode Exit fullscreen mode

abort() immediately stops processing and returns an error response. 403 for forbidden, 404 for not found, 500 for server error. In Django you used get_object_or_404() which does something similar under the hood.


Error Handlers

Flask lets you register custom handlers for HTTP errors:

@app.errorhandler(404)
def page_not_found(error):
    return 'Custom 404 page', 404

@app.errorhandler(403)
def forbidden(error):
    return 'You do not have permission', 403

@app.errorhandler(500)
def server_error(error):
    return 'Something went wrong', 500
Enter fullscreen mode Exit fullscreen mode

These catch errors across the entire app. In Django, this was done with custom 404.html and 500.html template files. Flask gives you full control via Python functions.


Before and After Request Hooks

Flask lets you run code before or after every request:

@app.before_request
def before():
    print(f'Request incoming: {request.path}')

@app.after_request
def after(response):
    response.headers['X-Frame-Options'] = 'DENY'
    return response

@app.teardown_request
def teardown(error=None):
    # runs after request regardless of exceptions
    pass
Enter fullscreen mode Exit fullscreen mode
  • @app.before_request — runs before the view function. Useful for authentication checks and logging.
  • @app.after_request — runs after the view, receives the response object. Useful for adding headers.
  • @app.teardown_request — runs at the end regardless of whether an exception occurred. Useful for cleanup, like closing database connections.

In Django, this was middleware. Flask's hooks are simpler for basic cases, though Blueprints (day 84) have their own version of these hooks too.


Putting It Together — A Small Example

from flask import Flask, request, jsonify, redirect, url_for, abort

app = Flask(__name__)

users = {'haris': {'role': 'developer', 'location': 'Karachi'}}

@app.route('/users')
def list_users():
    return jsonify(list(users.keys()))

@app.route('/users/<username>')
def get_user(username):
    user = users.get(username)
    if not user:
        abort(404)
    return jsonify({'username': username, **user})

@app.route('/users', methods=['POST'])
def create_user():
    data = request.get_json()
    if not data or 'username' not in data:
        return jsonify({'error': 'username required'}), 400
    users[data['username']] = {'role': data.get('role', ''), 'location': data.get('location', '')}
    return jsonify({'created': data['username']}), 201

@app.errorhandler(404)
def not_found(e):
    return jsonify({'error': 'not found'}), 404

if __name__ == '__main__':
    app.run(debug=True)
Enter fullscreen mode Exit fullscreen mode

A small in-memory API with list, detail, create, and a custom error handler, using everything from today in one place.


Wrapping Up

Routes, requests, and responses are the complete picture of how Flask handles HTTP. Everything else: templates, databases, auth, sits on top of this foundation. The patterns here are identical to Django conceptually, just lighter and more explicit.

Thanks for reading. Feel free to share your thoughts!

Top comments (0)