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!".

Python — the simplest Flask app
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

Python — dynamic URLs with 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.

Python — type converters in routes
# 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:

Python — GET and POST on the same route
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> '''

Reading Form Data and JSON

When a user submits a form or an API sends data, Flask gives you two easy ways to read it:

Python — reading form data and JSON
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.

Python — url_for in action
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.

Python — redirects and error handlers
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.

Python — creating a blueprint (users/routes.py)
# 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'})
Python — registering blueprints in app.py
# 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 TypeExampleWhen to Use
Static/aboutFixed 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_usersLarge 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.