Flask Routing and View Functions

Module 23: Web Frameworks II (Python) - Monday, Lecture 2

Understanding Routing in Web Applications

Before diving into Flask's routing system, let's understand what routing means in web applications:

Routing is the mechanism that maps URL patterns to the code that handles them. When a user visits a particular URL, the routing system determines which part of your application should process the request and generate a response.

graph LR A[Client Browser] -->|"Request /products"| B[Web Server] B --> C[Routing System] C -->|"Match: /products"| D[Product List Handler] D --> E[Generate Response] E --> F[Return HTML/JSON/etc.] F --> A

Think of routing as a mail sorting system in a large office building. When mail arrives (a request), the sorting system (router) examines the address (URL) and delivers it to the correct department (view function).

Flask's Routing System

Flask implements routing through decorators, providing an elegant and readable way to connect URLs to Python functions.

The core of Flask's routing is the @app.route() decorator, which associates a URL pattern with a function:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
    return 'Welcome to the Home Page!'

@app.route('/about')
def about():
    return 'About Us Page'

In this example, visiting http://localhost:5000/ executes the home() function, while http://localhost:5000/about triggers the about() function.

URL Patterns and Route Parameters

Flask's routing system supports dynamic URL components using special syntax in route patterns:

Simple Variables

@app.route('/user/')
def show_user_profile(username):
    return f'User Profile: {username}'

When you visit /user/john, Flask calls show_user_profile('john').

Type Converters

Flask provides converters to automatically validate and convert URL parameters:

@app.route('/post/')
def show_post(post_id):
    # post_id will be an integer
    return f'Post #{post_id}'
    
@app.route('/path/')
def show_subpath(subpath):
    # Matches any path, including slashes
    return f'Subpath: {subpath}'
Converter Description Example
string Default, accepts any text without slashes /user/
int Accepts positive integers /post/
float Accepts positive floating-point values /metric/
path Like string but accepts slashes /files/
uuid Accepts UUID strings /document/

HTTP Methods in Flask Routes

By default, routes only respond to GET requests. To handle other HTTP methods, use the methods parameter:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # Process the login form
        username = request.form['username']
        password = request.form['password']
        # Authenticate user
        return f'Logging in {username}...'
    else:
        # Show login form
        return '''
            <form method="post">
                <input type="text" name="username">
                <input type="password" name="password">
                <input type="submit" value="Login">
            </form>
        '''
flowchart TD A[Client] -->|"GET /login"| B[Flask App] B -->|"Executes login() function"| C{Check request.method} C -->|"method == GET"| D[Return Login Form] D --> A A -->|"POST /login with form data"| B B -->|"Executes login() function"| C C -->|"method == POST"| E[Process Login Data] E --> F[Return Result] F --> A

Real-World Context: RESTful API Design

In modern web development, different HTTP methods have specific meanings according to RESTful principles:

  • GET: Retrieve information (read-only)
  • POST: Create new resources
  • PUT: Update existing resources (complete replacement)
  • PATCH: Partially update resources
  • DELETE: Remove resources

Flask makes it easy to implement these RESTful principles through its routing system.

View Functions in Detail

View functions are the core of your Flask application—they contain the logic that processes requests and generates responses.

Anatomy of a View Function

from flask import render_template, request, redirect, url_for

@app.route('/product/')
def product_detail(product_id):
    # 1. Get data (e.g., from a database)
    product = get_product_from_database(product_id)
    
    # 2. Handle errors or special cases
    if not product:
        return "Product not found", 404
    
    # 3. Process data if needed
    similar_products = find_similar_products(product)
    
    # 4. Return a response (often a rendered template)
    return render_template('product_detail.html', 
                           product=product,
                           similar_products=similar_products)

Key components of view functions include:

Response Types in Flask

Flask view functions can return various types of responses:

Simple String Responses

@app.route('/hello')
def hello():
    return 'Hello, World!'

HTML Responses

@app.route('/html-example')
def html_example():
    return '''
        <html>
            <body>
                <h1>HTML Example</h1>
                <p>This is a paragraph.</p>
            </body>
        </html>
    '''

Template Rendering

@app.route('/dashboard')
def dashboard():
    user = get_current_user()
    stats = get_user_stats(user.id)
    return render_template('dashboard.html', user=user, stats=stats)

JSON Responses

from flask import jsonify

@app.route('/api/products')
def product_list_api():
    products = get_all_products()
    return jsonify({
        'products': [p.to_dict() for p in products],
        'count': len(products)
    })

Redirects

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

@app.route('/new-page')
def new_page():
    return 'This is the new page'

Custom Response Codes

@app.route('/resource-not-found')
def not_found_example():
    return 'The requested resource was not found', 404

@app.route('/created')
def created_example():
    return 'Resource created successfully', 201

URL Building with url_for()

Instead of hardcoding URLs in your templates or redirects, Flask provides the url_for() function to generate URLs based on function names:

from flask import url_for

@app.route('/')
def index():
    # Generate URLs for other routes
    login_url = url_for('login')
    profile_url = url_for('profile', username='john')
    
    return f'''
        <a href="{login_url}">Login</a>
        <a href="{profile_url}">John's Profile</a>
    '''

@app.route('/login')
def login():
    return 'Login Page'

@app.route('/user/')
def profile(username):
    return f'Profile page for {username}'

The url_for() function has several advantages:

Advanced Routing Patterns

Multiple Routes for One Function

@app.route('/')
@app.route('/home')
@app.route('/index')
def home():
    return 'Welcome to the home page!'

Default Values for Parameters

@app.route('/users/')
@app.route('/users/')
def list_users(page=1):
    # Show page 1 by default
    users = get_users_for_page(page)
    return render_template('users.html', users=users, page=page)

URL Prefixes and Trailing Slashes

Flask treats routes with and without trailing slashes differently by default:

# /about/ and /about are different routes
@app.route('/about/')
def about_with_slash():
    return 'About page (with slash)'

You can configure Flask to be more flexible by setting strict_slashes=False:

@app.route('/contact', strict_slashes=False)
def contact():
    # Matches both /contact and /contact/
    return 'Contact page'

Organizing Routes in Larger Applications

As your application grows, keeping all routes in a single file becomes unwieldy. Flask provides several strategies for organizing routes:

Blueprints

Blueprints are Flask's way of modularizing applications:

# In auth.py
from flask import Blueprint

auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

@auth_bp.route('/login')
def login():
    return 'Login page'

@auth_bp.route('/register')
def register():
    return 'Registration page'

# In main.py
from flask import Flask
from auth import auth_bp

app = Flask(__name__)
app.register_blueprint(auth_bp)

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

With this structure, routes defined in the blueprint will be prefixed with /auth, so login() will be accessible at /auth/login.

graph TD A[Flask Application] --> B[Main Routes] A --> C[Auth Blueprint /auth] A --> D[Admin Blueprint /admin] A --> E[API Blueprint /api] C --> F[/auth/login] C --> G[/auth/register] D --> H[/admin/dashboard] D --> I[/admin/users] E --> J[/api/products] E --> K[/api/orders]

Real-World Example: E-commerce Product Catalog

Let's examine a more comprehensive example of a product catalog for an e-commerce site:

from flask import Flask, render_template, request, redirect, url_for, jsonify
from models import Product, Category, db

app = Flask(__name__)

# Product listing routes
@app.route('/products')
@app.route('/products/page/')
def product_list(page=1):
    # Get query parameters for filtering
    category_id = request.args.get('category', type=int)
    sort_by = request.args.get('sort', 'name')
    
    # Build query
    query = Product.query
    if category_id:
        query = query.filter_by(category_id=category_id)
    
    # Apply sorting
    if sort_by == 'price_low':
        query = query.order_by(Product.price.asc())
    elif sort_by == 'price_high':
        query = query.order_by(Product.price.desc())
    else:  # Default to name
        query = query.order_by(Product.name.asc())
    
    # Paginate results
    pagination = query.paginate(page=page, per_page=12)
    
    # Get categories for sidebar filter
    categories = Category.query.all()
    
    return render_template('products/list.html',
                          products=pagination.items,
                          pagination=pagination,
                          categories=categories,
                          current_category=category_id,
                          current_sort=sort_by)

# Product detail route
@app.route('/product/')
def product_detail(product_id):
    product = Product.query.get_or_404(product_id)
    related_products = Product.query.filter_by(category_id=product.category_id) \
                             .filter(Product.id != product_id) \
                             .limit(4).all()
    
    return render_template('products/detail.html',
                          product=product,
                          related_products=related_products)

# API routes for AJAX interactions
@app.route('/api/products//quick-view')
def product_quick_view(product_id):
    product = Product.query.get_or_404(product_id)
    return jsonify({
        'id': product.id,
        'name': product.name,
        'price': product.price,
        'image_url': product.primary_image_url,
        'short_description': product.short_description,
        'in_stock': product.in_stock
    })

This example demonstrates several key aspects of Flask routing:

Practice Activity

Create a Flask application for a simple blog with the following routes:

  1. Homepage (/) showing the latest posts
  2. Post detail page (/post/<id>) showing a single post
  3. Category page (/category/<name>) showing posts in a category
  4. API endpoint (/api/posts) returning all posts as JSON
  5. Search functionality (/search) accepting a query parameter

Use dummy data for now (no database required). The focus is on implementing the routing structure and view functions.

Bonus challenges: