Routes and Views: URL Routing in Flask
Welcome to Routes and Views! Think of routes as street addresses in a city - they tell Flask exactly where to send visitors when they type URLs into their browsers.
What are Routes?
Routes are the connection points between URLs and Python functions. When someone visits a URL, Flask matches it to a route and runs the corresponding function.
Basic Routing
from flask import Flask
app = Flask(__name__)
# Simple routes
@app.route('/')
def home():
return "Welcome home!"
@app.route('/about')
def about():
return "About us"
@app.route('/contact')
def contact():
return "Contact us"
if __name__ == '__main__':
app.run(debug=True)
Route Variables
Routes can capture values from URLs:
from flask import Flask
app = Flask(__name__)
# String variables (default)
@app.route('/user/<name>')
def greet_user(name):
return f"Hello, {name}!"
# Integer variables
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f"Showing post #{post_id}"
# Float variables
@app.route('/price/<float:amount>')
def show_price(amount):
return f"Price: ${amount:.2f}"
# UUID variables
@app.route('/item/<uuid:item_id>')
def show_item(item_id):
return f"Item ID: {item_id}"
# Path variables (includes slashes)
@app.route('/files/<path:file_path>')
def show_file(file_path):
return f"File path: {file_path}"
if __name__ == '__main__':
app.run(debug=True)
Route converters:
string- Text (default, no slashes)int- Integersfloat- Floating point numberspath- Text including slashesuuid- UUID strings
HTTP Methods
Different HTTP methods for different operations:
from flask import Flask, request
app = Flask(__name__)
# Default is GET
@app.route('/get-only')
def get_only():
return "This accepts GET requests"
# Multiple methods
@app.route('/data', methods=['GET', 'POST'])
def handle_data():
if request.method == 'GET':
return "Getting data"
elif request.method == 'POST':
return "Posting data"
# All methods
@app.route('/everything', methods=['GET', 'POST', 'PUT', 'DELETE'])
def handle_everything():
return f"Method: {request.method}"
# RESTful routes
@app.route('/users', methods=['GET'])
def list_users():
return "List all users"
@app.route('/users', methods=['POST'])
def create_user():
return "Create new user"
@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
return f"Get user {user_id}"
@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
return f"Update user {user_id}"
@app.route('/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
return f"Delete user {user_id}"
if __name__ == '__main__':
app.run(debug=True)
Request Object
Access request data with the request object:
from flask import Flask, request
app = Flask(__name__)
@app.route('/info')
def request_info():
return f"""
Method: {request.method}
URL: {request.url}
Headers: {dict(request.headers)}
Args: {dict(request.args)}
Form: {dict(request.form)}
JSON: {request.get_json(silent=True)}
"""
@app.route('/search')
def search():
# GET parameters
query = request.args.get('q', '')
limit = request.args.get('limit', '10')
return f"Searching for '{query}' with limit {limit}"
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return """
<form method="POST">
Username: <input name="username"><br>
Password: <input name="password" type="password"><br>
<input type="submit" value="Login">
</form>
"""
else:
username = request.form.get('username')
password = request.form.get('password')
return f"Login attempt: {username}"
@app.route('/api/data', methods=['POST'])
def api_data():
if request.is_json:
data = request.get_json()
return f"Received JSON: {data}"
else:
return "Expected JSON data", 400
if __name__ == '__main__':
app.run(debug=True)
URL Building
Generate URLs programmatically:
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def home():
return "Home page"
@app.route('/user/<name>')
def user_profile(name):
return f"Profile for {name}"
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f"Post {post_id}"
@app.route('/links')
def show_links():
home_url = url_for('home')
user_url = url_for('user_profile', name='alice')
post_url = url_for('show_post', post_id=123)
return f"""
<h1>Useful Links</h1>
<ul>
<li><a href="{home_url}">Home</a></li>
<li><a href="{user_url}">Alice's Profile</a></li>
<li><a href="{post_url}">Post #123</a></li>
</ul>
"""
if __name__ == '__main__':
app.run(debug=True)
Route Decorators
Create reusable route decorators:
from flask import Flask, request, g
from functools import wraps
app = Flask(__name__)
# Authentication decorator
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user_id' not in request.cookies:
return "Please log in first", 401
return f(*args, **kwargs)
return decorated_function
# Logging decorator
def log_request(f):
@wraps(f)
def decorated_function(*args, **kwargs):
print(f"Request to {request.path} from {request.remote_addr}")
return f(*args, **kwargs)
return decorated_function
# API response decorator
def json_response(f):
@wraps(f)
def decorated_function(*args, **kwargs):
result = f(*args, **kwargs)
if isinstance(result, dict):
from flask import jsonify
return jsonify(result)
return result
return decorated_function
@app.route('/')
@log_request
def home():
return "Welcome!"
@app.route('/profile')
@login_required
@log_request
def profile():
return "Your profile"
@app.route('/api/users')
@json_response
def api_users():
return {
'users': [
{'id': 1, 'name': 'Alice'},
{'id': 2, 'name': 'Bob'}
]
}
if __name__ == '__main__':
app.run(debug=True)
Advanced Routing
Route Groups and Blueprints
from flask import Flask, Blueprint
app = Flask(__name__)
# Create blueprint for blog
blog_bp = Blueprint('blog', __name__, url_prefix='/blog')
@blog_bp.route('/')
def blog_home():
return "Blog home"
@blog_bp.route('/post/<int:post_id>')
def blog_post(post_id):
return f"Blog post {post_id}"
# Create blueprint for API
api_bp = Blueprint('api', __name__, url_prefix='/api/v1')
@api_bp.route('/users')
def api_users():
return "API users"
@api_bp.route('/posts')
def api_posts():
return "API posts"
# Register blueprints
app.register_blueprint(blog_bp)
app.register_blueprint(api_bp)
if __name__ == '__main__':
app.run(debug=True)
Custom Route Converters
from flask import Flask
from werkzeug.routing import BaseConverter
class ListConverter(BaseConverter):
def to_python(self, value):
return value.split(',')
def to_url(self, values):
return ','.join(str(v) for v in values)
app = Blueprint('api', __name__)
app.url_map.converters['list'] = ListConverter
@app.route('/users/<list:user_ids>')
def get_users(user_ids):
return f"Users: {user_ids}"
# Usage: /users/1,2,3,4
Route Redirects
from flask import Flask, redirect, url_for, abort
app = Flask(__name__)
@app.route('/')
def home():
return "Home page"
@app.route('/old-url')
def old_url():
return redirect(url_for('home'))
@app.route('/user/<int:user_id>')
def user_profile(user_id):
if user_id > 1000:
abort(404) # User not found
return f"User {user_id}"
@app.route('/admin')
def admin():
# Check if user is admin
is_admin = False # In real app, check session/cookies
if not is_admin:
return redirect(url_for('home'))
return "Admin panel"
if __name__ == '__main__':
app.run(debug=True)
Practical Examples
Example 1: Blog Application
from flask import Flask, request, redirect, url_for
app = Flask(__name__)
# Mock database
posts = [
{'id': 1, 'title': 'First Post', 'content': 'Hello world!'},
{'id': 2, 'title': 'Second Post', 'content': 'Flask is awesome!'},
]
@app.route('/')
def home():
html = "<h1>My Blog</h1><ul>"
for post in posts:
html += f"<li><a href='/post/{post['id']}'>{post['title']}</a></li>"
html += "</ul><a href='/new'>Create new post</a>"
return html
@app.route('/post/<int:post_id>')
def show_post(post_id):
post = next((p for p in posts if p['id'] == post_id), None)
if not post:
return "Post not found", 404
return f"""
<h1>{post['title']}</h1>
<p>{post['content']}</p>
<a href='/'>Back to home</a>
"""
@app.route('/new', methods=['GET', 'POST'])
def new_post():
if request.method == 'GET':
return """
<h1>Create New Post</h1>
<form method="POST">
Title: <input name="title" required><br>
Content: <textarea name="content" required></textarea><br>
<input type="submit" value="Create Post">
</form>
"""
else:
title = request.form.get('title')
content = request.form.get('content')
new_post = {
'id': len(posts) + 1,
'title': title,
'content': content
}
posts.append(new_post)
return redirect(url_for('show_post', post_id=new_post['id']))
if __name__ == '__main__':
app.run(debug=True)
Example 2: REST API
from flask import Flask, jsonify, request, abort
app = Flask(__name__)
# Mock database
todos = [
{'id': 1, 'title': 'Learn Flask', 'completed': False},
{'id': 2, 'title': 'Build API', 'completed': True},
]
@app.route('/api/todos', methods=['GET'])
def get_todos():
return jsonify(todos)
@app.route('/api/todos/<int:todo_id>', methods=['GET'])
def get_todo(todo_id):
todo = next((t for t in todos if t['id'] == todo_id), None)
if not todo:
abort(404)
return jsonify(todo)
@app.route('/api/todos', methods=['POST'])
def create_todo():
if not request.is_json:
return jsonify({'error': 'Request must be JSON'}), 400
data = request.get_json()
if 'title' not in data:
return jsonify({'error': 'Title is required'}), 400
new_todo = {
'id': len(todos) + 1,
'title': data['title'],
'completed': data.get('completed', False)
}
todos.append(new_todo)
return jsonify(new_todo), 201
@app.route('/api/todos/<int:todo_id>', methods=['PUT'])
def update_todo(todo_id):
todo = next((t for t in todos if t['id'] == todo_id), None)
if not todo:
abort(404)
if not request.is_json:
return jsonify({'error': 'Request must be JSON'}), 400
data = request.get_json()
todo.update(data)
return jsonify(todo)
@app.route('/api/todos/<int:todo_id>', methods=['DELETE'])
def delete_todo(todo_id):
global todos
todos = [t for t in todos if t['id'] != todo_id]
return '', 204
if __name__ == '__main__':
app.run(debug=True)
Example 3: E-commerce Routes
from flask import Flask, request, session, redirect, url_for
app = Flask(__name__)
app.secret_key = 'your-secret-key'
# Mock data
products = [
{'id': 1, 'name': 'Laptop', 'price': 999.99},
{'id': 2, 'name': 'Mouse', 'price': 29.99},
]
@app.route('/')
def home():
return "Welcome to our store!"
@app.route('/products')
def list_products():
html = "<h1>Our Products</h1><ul>"
for product in products:
html += f"""
<li>
{product['name']} - ${product['price']}
<a href="/product/{product['id']}">View</a>
<a href="/cart/add/{product['id']}">Add to Cart</a>
</li>
"""
html += "</ul><a href='/cart'>View Cart</a>"
return html
@app.route('/product/<int:product_id>')
def show_product(product_id):
product = next((p for p in products if p['id'] == product_id), None)
if not product:
return "Product not found", 404
return f"""
<h1>{product['name']}</h1>
<p>Price: ${product['price']}</p>
<a href="/cart/add/{product['id']}">Add to Cart</a>
<a href="/products">Back to products</a>
"""
@app.route('/cart')
def view_cart():
cart = session.get('cart', [])
if not cart:
return "Your cart is empty <a href='/products'>Shop now</a>"
total = 0
html = "<h1>Your Cart</h1><ul>"
for item in cart:
product = next((p for p in products if p['id'] == item['id']), None)
if product:
html += f"<li>{product['name']} - ${product['price']}</li>"
total += product['price']
html += f"</ul><p>Total: ${total:.2f}</p>"
return html
@app.route('/cart/add/<int:product_id>')
def add_to_cart(product_id):
product = next((p for p in products if p['id'] == product_id), None)
if not product:
return "Product not found", 404
cart = session.get('cart', [])
cart.append({'id': product_id})
session['cart'] = cart
return redirect(url_for('view_cart'))
if __name__ == '__main__':
app.run(debug=True)
Best Practices
1. Use Descriptive Route Names
# Good
@app.route('/users/<int:user_id>/posts/<int:post_id>')
def show_user_post(user_id, post_id):
pass
# Bad
@app.route('/u/<uid>/p/<pid>')
def sup(uid, pid):
pass
2. Validate Route Parameters
@app.route('/user/<int:user_id>')
def show_user(user_id):
if user_id <= 0:
abort(400) # Bad request
# ... rest of function
3. Use URL Building
# Good
url_for('show_user', user_id=123)
# Bad
'/user/123'
4. Handle Errors Properly
@app.errorhandler(404)
def not_found(error):
return "Page not found", 404
@app.errorhandler(500)
def internal_error(error):
return "Internal server error", 500
5. Use Blueprints for Organization
# For large apps, use blueprints
user_bp = Blueprint('users', __name__, url_prefix='/users')
app.register_blueprint(user_bp)
Practice Exercises
Exercise 1: URL Shortener
Create a URL shortener with routes:
POST /shorten- Accept long URL, return short codeGET /<short_code>- Redirect to original URLGET /stats/<short_code>- Show click statistics
Exercise 2: Social Media API
Build a social media API with:
GET /posts- List all postsPOST /posts- Create new postGET /posts/<id>- Get specific postPOST /posts/<id>/like- Like a postGET /users/<username>- User profile
Exercise 3: File Manager
Create a file manager with:
GET /files- List files in directoryGET /files/<path>- Download filePOST /files- Upload fileDELETE /files/<path>- Delete file
Exercise 4: Quiz Application
Build a quiz app with:
GET /quiz- Start new quizPOST /quiz/<quiz_id>/answer- Submit answerGET /quiz/<quiz_id>/result- Show resultsGET /leaderboard- Show top scores
Summary
Routes and views are Flaskβs URL routing system:
Basic Routes:
@app.route('/')
def home():
return "Hello!"
@app.route('/user/<name>')
def greet(name):
return f"Hello, {name}!"
HTTP Methods:
@app.route('/data', methods=['GET', 'POST'])
def handle_data():
if request.method == 'GET':
return "Getting data"
else:
return "Posting data"
Request Data:
query = request.args.get('q') # GET parameters
data = request.form.get('field') # Form data
json_data = request.get_json() # JSON data
URL Building:
url_for('route_name', param=value)
Key Concepts:
- Route variables with converters (
int,float,path) - HTTP methods for different operations
- Request object for accessing data
- URL building with
url_for() - Blueprints for organization
Next: Templates - creating dynamic HTML with Jinja2! π¨