Flask is a lightweight Python web framework. It's one of the best tools for building websites and APIs, and it's popular because it's simple, flexible, and easy to learn.
At the heart of every Flask app is routing — the system that decides what happens when someone visits a URL like /about or /users/42. Once you understand routing, you can build almost anything with Flask.
This guide starts from scratch and walks you through everything — basic routes, dynamic URLs, HTTP methods, redirects, and how to keep your code clean using blueprints.
What Is Routing? (Simple Explanation)
Think of a Flask app like a restaurant. When a customer walks in and asks for "Table 5", the waiter knows exactly where to take them. Routing is the same thing for web apps — when a user visits a URL, Flask looks at it and decides which Python function to run.
Each URL in your app maps to a specific Python function. That function is called a view function, and it returns what the user sees — some HTML, some JSON, or anything else.
ℹ️
How it works: User visits https://mysite.com/about → Flask sees /about → finds the matching route → runs that Python function → sends the result back to the browser.
Your First Route
Here's the simplest possible Flask app. It has one route — the homepage at /. When someone visits it, they see "Hello, World!".
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return 'Hello, World!'
@app.route('/about')
def about():
return 'This is the about page.'
@app.route('/contact')
def contact():
return 'Reach us at hello@example.com'
if __name__ == '__main__':
app.run(debug=True)
The @app.route('/') part is called a decorator. It tells Flask: "when someone visits this URL, run the function below it". Each route needs a unique URL and a unique function name.
✅
Run it: save this as app.py, then run python app.py in your terminal. Visit http://127.0.0.1:5000 in your browser to see it working.
Dynamic Routes — URLs That Change
So far, every URL is fixed — /about always shows the same page. But real apps need URLs that can change depending on what the user wants.
Imagine a blog. You don't want to write a separate route for every single post. Instead, you want one route that handles /post/1, /post/2, /post/100, and so on.
Flask handles this with dynamic URL segments — you put angle brackets in the URL, and Flask automatically passes that value into your function.
URL Parameters
from flask import Flask
app = Flask(__name__)
# <username> captures whatever is in the URL
@app.route('/user/<username>')
def show_user(username):
return f'Profile page for: {username}'
# /user/shashank → "Profile page for: shashank"
# /user/alice → "Profile page for: alice"
# You can have multiple parameters in one route
@app.route('/post/<category>/<post_id>')
def show_post(category, post_id):
return f'Category: {category}, Post ID: {post_id}'
# /post/python/42 → "Category: python, Post ID: 42"
Type Converters — Make Sure the Value Is the Right Type
By default, URL parameters are treated as strings. But if you need a number, you can tell Flask that upfront using type converters. This also prevents bad URLs from reaching your function.
# int: — accepts only whole numbers
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f'Post number {post_id}' # post_id is already an int
# /post/42 → works fine, post_id = 42
# /post/hello → 404 error (not a number!)
# float: — accepts decimal numbers like 3.14
@app.route('/price/<float:amount>')
def show_price(amount):
return f'Price: ${amount:.2f}'
# path: — accepts slashes too, good for file paths
@app.route('/files/<path:filename>')
def get_file(filename):
return f'File requested: {filename}'
# /files/docs/guide.pdf → filename = "docs/guide.pdf"
⚠️
Always use type converters for IDs. If your route needs a number (like a user ID or post ID), use <int:id> instead of just <id>. It automatically rejects bad input and saves you from having to validate it yourself.
HTTP Methods — GET, POST, and More
When a browser talks to a server, it doesn't just send the URL — it also sends a method that says what it wants to do. The two most common methods are:
- GET — "I want to read something." Used when you visit a page or click a link.
- POST — "I want to send you data." Used when you submit a form or log in.
GET vs POST in Flask
By default, Flask routes only accept GET requests. To also accept POST (or other methods), you add methods= to your route decorator:
from flask import Flask, request
app = Flask(__name__)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# User submitted the login form
username = request.form['username']
password = request.form['password']
return f'Logging in as {username}...'
else:
# User just visited the page — show the form
return '''
<form method="POST">
<input name="username" placeholder="Username">
<input name="password" type="password" placeholder="Password">
<button type="submit">Log In</button>
</form>
'''
When a user submits a form or an API sends data, Flask gives you two easy ways to read it:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/signup', methods=['POST'])
def signup():
# Read HTML form data (from <form> submission)
name = request.form.get('name', '')
email = request.form.get('email', '')
return f'Welcome, {name}! We emailed {email}.'
@app.route('/api/user', methods=['POST'])
def create_user():
# Read JSON data (from an API call)
data = request.get_json()
name = data.get('name')
email = data.get('email')
# jsonify() converts a dict to a proper JSON response
return jsonify({'message': f'Created user {name}', 'email': email}), 201
ℹ️
Use .get() not []: when reading form or JSON data, always use .get('key', default) instead of ['key']. If the key is missing, .get() returns the default value instead of crashing with a KeyError.
url_for — The Right Way to Link Pages
When you need to link one page to another, you might be tempted to write the URL directly: href="/about". This works, but it's fragile. If you ever change the URL, every link breaks.
url_for() is Flask's solution. Instead of writing the URL, you write the name of the function. Flask figures out the URL for you automatically.
from flask import Flask, url_for, redirect
app = Flask(__name__)
@app.route('/')
def home():
return 'Home page'
@app.route('/user/<int:user_id>')
def profile(user_id):
return f'Profile {user_id}'
@app.route('/go-home')
def go_home():
# url_for('home') returns '/'
return redirect(url_for('home'))
@app.route('/go-to-user')
def go_to_user():
# url_for('profile', user_id=42) returns '/user/42'
return redirect(url_for('profile', user_id=42))
# In a Jinja2 template you use it like this:
# <a href="{{ url_for('profile', user_id=user.id) }}">View Profile</a>
✅
Always use url_for. If you later change /user/<id> to /profile/<id>, every url_for('profile', ...) call automatically updates. Hardcoded URLs would all break.
Redirects and Custom Error Pages
Sometimes you need to send a user to a different page — for example, after they log in, redirect them to the dashboard. Flask makes this easy with redirect().
You can also create custom error pages instead of showing Flask's default ugly error screens.
from flask import Flask, redirect, url_for, abort
app = Flask(__name__)
@app.route('/old-page')
def old_page():
# Send users from old URL to new URL
return redirect(url_for('new_page'))
@app.route('/new-page')
def new_page():
return 'Welcome to the new page!'
@app.route('/admin')
def admin():
is_logged_in = False # pretend we checked the session
if not is_logged_in:
abort(403) # triggers the 403 error handler below
return 'Admin dashboard'
# Custom 404 page — shows when URL doesn't exist
@app.errorhandler(404)
def not_found(error):
return 'Oops! That page does not exist.', 404
# Custom 403 page — shows when user is not allowed
@app.errorhandler(403)
def forbidden(error):
return 'Sorry, you are not allowed to view this page.', 403
Blueprints — Keeping Your Code Clean
When your app gets bigger, putting all your routes in one file becomes messy very quickly. Blueprints let you split your routes into separate files — one file for user routes, one for blog routes, one for the API, and so on.
Think of a blueprint as a mini Flask app. You define routes on it, then register it with the main app. Each blueprint can also have its own URL prefix so all its routes start with the same path.
# users/routes.py — all user-related routes go here
from flask import Blueprint, jsonify
# Create the blueprint with a name and URL prefix
users_bp = Blueprint('users', __name__, url_prefix='/users')
@users_bp.route('/') # actual URL: /users/
def list_users():
return jsonify(['Alice', 'Bob', 'Charlie'])
@users_bp.route('<int:user_id>') # actual URL: /users/42
def get_user(user_id):
return jsonify({'id': user_id, 'name': 'Alice'})
# app.py — the main app, kept clean and simple
from flask import Flask
from users.routes import users_bp
from blog.routes import blog_bp
from api.routes import api_bp
app = Flask(__name__)
# Register each blueprint — clean and easy to read
app.register_blueprint(users_bp) # handles /users/*
app.register_blueprint(blog_bp) # handles /blog/*
app.register_blueprint(api_bp) # handles /api/*
# Your project folder looks like this:
# my_app/
# app.py
# users/routes.py
# blog/routes.py
# api/routes.py
ℹ️
When should you use blueprints? As soon as your app has more than one section — users, blog, API, admin — split them into blueprints. It makes your code much easier to read, test, and maintain.
Route Summary: All Patterns at a Glance
| Route Type | Example | When to Use |
| Static | /about | Fixed pages — home, about, contact |
| Dynamic (string) | /user/<name> | Any text value in the URL |
| Dynamic (int) | /post/<int:id> | Numeric IDs — post ID, user ID |
| Multi-param | /<cat>/<int:id> | Nested resources |
| Blueprint route | /users/ → list_users | Large apps with multiple sections |
⚡ Key Takeaways
- A route maps a URL to a Python function. Use @app.route('/url') to create one.
- Use dynamic routes like <username> to handle URLs that change based on user input.
- Always use type converters like <int:id> for numeric IDs — it validates input automatically.
- Add methods=['GET', 'POST'] to accept form submissions. Use request.form for HTML forms, request.get_json() for API data.
- Use url_for('function_name') to build URLs — never hardcode them. Your links stay correct even when URLs change.
- Use redirect() to send users to another page and abort(404) to trigger error pages.
- Use blueprints to split a big app into sections — one file per feature keeps things clean and easy to manage.
Tags:
Flask
Python
Routing
Web Dev
Blueprints
Beginner
Shashank Shekhar
Founder & Creator — Hoopsiper.com
Full-stack developer and educator. Building Hoopsiper to help developers learn faster through practical, no-fluff coding guides on JavaScript, AI/ML, Python, and modern web development.