Chapter 2: Routing System
Welcome back! In Chapter 1: Application Object (Flask
), we learned how to create the central app
object, the control tower for our Flask application. We even added a simple “Hello, World!” page using @app.route('/')
.
But how did Flask know that visiting the homepage (/
) should run our index()
function? And how can we create more pages, like an “About Us” page at /about
? That’s where the Routing System comes in.
What Problem Does It Solve? The Need for Directions
Imagine you have a website with multiple pages: a homepage, an about page, a contact page, maybe even pages for individual user profiles. When a user types a URL like http://yourwebsite.com/about
into their browser, how does your Flask application know which piece of Python code should handle this request and generate the “About Us” content?
You need a system to map these incoming URLs to the specific Python functions that generate the response for each page. Think of it like a city map’s index:
- URL: The street address you want to find (e.g.,
/about
). - Routing System: The index in the map book.
- View Function: The specific page number in the map book that shows the details for that address.
Flask’s routing system, largely powered by a library called Werkzeug, acts as this index. It lets you define URL patterns (like /
or /about
or /user/<username>
) and connect them to your Python functions (called view functions).
Defining Routes with @app.route()
In Flask, the most common way to define these URL-to-function mappings is using the @app.route()
decorator, which we briefly saw in Chapter 1.
Let’s revisit our hello.py
and add an “About” page.
- We keep the route for the homepage (
/
). - We add a new route for
/about
.
# hello.py
from flask import Flask
# Create the application object from Chapter 1
app = Flask(__name__)
# Route for the homepage
@app.route('/')
def index():
return 'Welcome to the Homepage!'
# NEW: Route for the about page
@app.route('/about')
def about():
return 'This is the About Us page.'
# Code to run the app (from Chapter 1)
if __name__ == '__main__':
app.run(debug=True)
Explanation:
@app.route('/')
: This tells Flask: “If a request comes in for the URL path/
, execute the function directly below (index
).”@app.route('/about')
: This tells Flask: “If a request comes in for the URL path/about
, execute the function directly below (about
).”def index(): ...
anddef about(): ...
: These are our view functions. They contain the Python code that runs for their respective routes and must return the response to send back to the browser.
Running this:
- Save the code as
hello.py
. - Run
python hello.py
in your terminal. - Visit
http://127.0.0.1:5000/
in your browser. You should see “Welcome to the Homepage!”. - Visit
http://127.0.0.1:5000/about
. You should see “This is the About Us page.”.
See? The routing system directed each URL to the correct view function!
Dynamic Routes: Using Variables in URLs
What if you want pages that change based on the URL? For example, a profile page for different users like /user/alice
and /user/bob
. You don’t want to write a new view function for every single user!
Flask allows you to define variable parts in your URL rules using angle brackets < >
.
Let’s create a dynamic route to greet users:
# hello.py (continued)
# ... (keep Flask import, app creation, index, and about routes) ...
# NEW: Dynamic route for user profiles
@app.route('/user/<username>')
def show_user_profile(username):
# The 'username' variable from the URL is passed to the function!
return f'Hello, {username}!'
# ... (keep the if __name__ == '__main__': block) ...
Explanation:
@app.route('/user/<username>')
:- The
/user/
part is fixed. <username>
is a variable placeholder. Flask will match any text here (likealice
,bob
,123
) and capture it.
- The
def show_user_profile(username):
:- Notice the function now accepts an argument named
username
. This must match the variable name used in the angle brackets in the route. - Flask automatically passes the value captured from the URL to this argument.
- Notice the function now accepts an argument named
return f'Hello, {username}!'
: We use an f-string to include the captured username in the response.
Running this:
- Save the updated
hello.py
(make suredebug=True
is still set so the server restarts). - Visit
http://127.0.0.1:5000/user/Alice
. You should see “Hello, Alice!”. - Visit
http://127.0.0.1:5000/user/Bob
. You should see “Hello, Bob!”.
Flask’s routing system matched both URLs to the same rule (/user/<username>
) and passed the different usernames ('Alice'
, 'Bob'
) to the show_user_profile
function.
Specifying Data Types: Converters
By default, variables captured from the URL are treated as strings. But what if you need a number? For example, displaying blog post number 5 at /post/5
. You might want Flask to ensure that only numbers are accepted for that part of the URL.
You can specify a converter inside the angle brackets using <converter:variable_name>
.
Let’s add a route for blog posts using the int
converter:
# hello.py (continued)
# ... (keep previous code) ...
# NEW: Route for displaying a specific blog post by ID
@app.route('/post/<int:post_id>')
def show_post(post_id):
# Flask ensures post_id is an integer and passes it here
# Note: We are just showing the ID, not actually fetching a post
return f'Showing Post Number: {post_id} (Type: {type(post_id).__name__})'
# ... (keep the if __name__ == '__main__': block) ...
Explanation:
@app.route('/post/<int:post_id>')
:<int:post_id>
tells Flask: “Match this part of the URL, but only if it looks like an integer. Convert it to an integer and pass it as thepost_id
variable.”
def show_post(post_id):
: Thepost_id
argument will now receive an actual Pythonint
.
Running this:
- Save the updated
hello.py
. - Visit
http://127.0.0.1:5000/post/123
. You should see “Showing Post Number: 123 (Type: int)”. - Visit
http://127.0.0.1:5000/post/abc
. You’ll get a “Not Found” error! Why? Becauseabc
doesn’t match theint
converter, so Flask doesn’t consider this URL to match the rule.
Common converters include:
string
: (Default) Accepts any text without a slash.int
: Accepts positive integers.float
: Accepts positive floating-point values.path
: Likestring
but also accepts slashes (useful for matching file paths).uuid
: Accepts UUID strings.
Under the Hood: How Does Routing Work?
You don’t need to know the deep internals, but understanding the basics helps.
When you define routes using @app.route()
, Flask doesn’t immediately check URLs. Instead, it builds a map, like pre-compiling that map index we talked about.
- Building the Map:
- When you create your
app = Flask(__name__)
(Chapter 1), Flask initializes an emptyURLMap
object (from the Werkzeug library, stored inapp.url_map
). SeeFlask.__init__
inapp.py
which callssuper().__init__
insansio/app.py
, which creates theself.url_map
. - Each time you use
@app.route('/some/rule', ...)
or directly callapp.add_url_rule(...)
(seesansio/scaffold.py
), Flask creates aRule
object (likeRule('/user/<username>')
) describing the pattern, the allowed HTTP methods (GET, POST, etc.), the endpoint name (usually the function name), and any converters. - This
Rule
object is added to theapp.url_map
.
- When you create your
- Matching a Request:
- When a request like
GET /user/Alice
arrives, Flask’swsgi_app
method (inapp.py
) gets called. - It uses the
app.url_map
and the incoming request environment (URL path, HTTP method) to find a matchingRule
. Werkzeug’sMapAdapter.match()
method (created viaapp.create_url_adapter
which callsurl_map.bind_to_environ
) does the heavy lifting here. - If a match is found for
/user/<username>
,match()
returns the endpoint name (e.g.,'show_user_profile'
) and a dictionary of the extracted variables (e.g.,{'username': 'Alice'}
). These get stored on therequest
object (Chapter 3) asrequest.url_rule
andrequest.view_args
. - If no rule matches, a “Not Found” (404) error is raised.
- When a request like
- Dispatching to the View Function:
- Flask’s
app.dispatch_request()
method (inapp.py
) takes the endpoint name fromrequest.url_rule.endpoint
. - It looks up the actual Python view function associated with that endpoint name in the
app.view_functions
dictionary (which@app.route
also populated). - It calls the view function, passing the extracted variables from
request.view_args
as keyword arguments (e.g.,show_user_profile(username='Alice')
). - The return value of the view function becomes the response.
- Flask’s
Here’s a simplified diagram of the matching process:
sequenceDiagram
participant Browser
participant FlaskApp as app.wsgi_app
participant URLMap as url_map.bind(...).match()
participant ViewFunc as show_user_profile()
Browser->>+FlaskApp: GET /user/Alice
FlaskApp->>+URLMap: Match path '/user/Alice' and method 'GET'?
URLMap-->>-FlaskApp: Match found! Endpoint='show_user_profile', Args={'username': 'Alice'}
FlaskApp->>+ViewFunc: Call show_user_profile(username='Alice')
ViewFunc-->>-FlaskApp: Return 'Hello, Alice!'
FlaskApp-->>-Browser: Send response 'Hello, Alice!'
The key takeaway is that @app.route
builds a map upfront, and Werkzeug efficiently searches this map for each incoming request to find the right function and extract any variable parts.
Conclusion
You’ve learned how Flask’s Routing System acts as a map between URLs and the Python functions (view functions) that handle them.
- We use the
@app.route()
decorator to define URL rules. - We can create static routes (like
/about
) and dynamic routes using variables (/user/<username>
). - Converters (
<int:post_id>
) allow us to specify the expected data type for URL variables, providing automatic validation and conversion. - Under the hood, Flask and Werkzeug build a
URLMap
from these rules and use it to efficiently dispatch incoming requests to the correct view function.
Now that we know how to direct requests to the right functions, what information comes with a request (like form data or query parameters)? And how do we properly format the data we send back? That’s where the Request and Response objects come in.
Let’s dive into Chapter 3: Request and Response Objects.
Generated by AI Codebase Knowledge Builder