Routing Basics
Every page Bookshelf will ever serve — the home page, the book list, a single shelf's contents — starts with the same idea: a URL path mapped to a Python function that decides what to send back. This lesson covers that mapping in depth: registering routes, restricting which HTTP methods they accept, and generating URLs the right way instead of typing paths by hand.
The @app.route Decorator
@app.route() is how a URL path gets connected to a Python function, called a
view function. Whatever that function returns becomes the HTTP response. Here
are several of Bookshelf's earliest routes, each with its own view function:
Example: Bookshelf's First Routes
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return render_template('home.html')
@app.route('/books')
def book_list():
return render_template('books/list.html')
@app.route('/shelves')
def shelf_list():
return render_template('shelves/list.html')
@app.route('/about')
def about():
return 'Bookshelf is a personal reading tracker.'
home, book_list, shelf_list, and about are
ordinary Python functions — the decorator is what makes Flask aware of them and connects
each one to a specific path. render_template() (covered fully in
Jinja2 Templates) renders a Jinja2 template file into
HTML; a view can just as easily return a plain string, as about() does here.
Restricting Which HTTP Methods a Route Accepts
By default, a route only responds to GET requests — a plain page load. Some
routes, like a form for adding a new book to Bookshelf, need to accept a submitted form too,
which arrives as a POST request. The methods argument lists exactly
which HTTP methods a route allows:
@app.route('/books/new', methods=['GET', 'POST'])
def add_book():
return 'Form goes here'
Requesting /books/new with any method not listed — a DELETE
request, for instance — gets a 405 Method Not Allowed response automatically,
with no extra code required. Handling GET and POST differently
inside a single view function — showing an empty form on GET,
processing submitted data on POST — is covered in full in the next lesson,
Request & Response Objects.
Why Hardcoded URLs Are Fragile
It's tempting to just type /books directly, everywhere Bookshelf needs to link to
the book list — in navigation menus, in redirects, in every template that mentions it. The
problem shows up the moment that path ever changes: renaming /books to
/library means hunting down and fixing every hardcoded reference by hand, and it's
easy to miss one.
Generating URLs with url_for()
Flask's url_for() function solves this by generating a URL from a view function's
name rather than its path string:
from flask import url_for
url_for('book_list') # '/books'
Because url_for('book_list') asks Flask to look up whatever path is currently
registered to the book_list view, changing the route itself is now enough:
@app.route('/library')
def book_list():
return render_template('books/list.html')
Every url_for('book_list') call anywhere in the project — templates,
redirects, other view functions — now automatically resolves to /library,
with zero additional edits required. That single change is exactly what
url_for() buys you.
Using url_for() Inside Templates
The most common place url_for() shows up is directly inside Jinja2 templates, for
every internal link:
<nav>
<a href="{{ url_for('home') }}">Bookshelf</a>
<a href="{{ url_for('book_list') }}">All Books</a>
<a href="{{ url_for('shelf_list') }}">Shelves</a>
<a href="{{ url_for('about') }}">About</a>
</nav>
Every link in that navigation bar stays correct automatically no matter how the underlying paths change, since none of them are typed as literal strings. This pattern comes up constantly once Template Inheritance & Includes puts a shared navigation bar into a base template used by every page on the site.
Flask's "Did You Mean" 404 Behavior
One small but genuinely pleasant built-in touch: during development, if you request a URL that
doesn't exist but is close to one that does — say, /boks instead of
/books — Werkzeug (the toolkit Flask is built on) will sometimes include a
"did you mean" suggestion pointing at the closest registered route, right alongside the normal
404 response. It's not a substitute for proper error pages (covered in
Error Handling), but it's a handy nudge while you're still
actively typing out routes by hand during development.
Trailing Slashes: /books vs /books/
Flask treats a route defined without a trailing slash, like /books,
differently from one defined with one, like /books/. If you register
/books and a visitor requests /books/, Flask responds with a
404 — that exact trailing-slash variant was never registered. Flip it around,
though, and Flask is more forgiving: if you register /books/ (with the slash) and a
visitor requests /books (without it), Flask automatically redirects to the version
with the slash.
/books and /shelves — and stick with it, so visitors and search
engines aren't quietly redirected back and forth between two versions of what's really the same
page.
/books only get you so far — head to Dynamic Routes & Converters to see how a single route handles a specific book, like /books/42.