Flask Routing: URLs, Requests, and Responses
Routes determine which part of the code should handle a request. The URL is the most visible part, but Flask also considers the HTTP method, such as GET or POST. That is why two requests to the same address can represent different actions.
Think of a route as a contract between the client and the application. When a client requests GET /products/10, the application should understand that product number 10 is being requested. The clearer this contract is, the easier the route is to use, test, and maintain.
Paths, Query Strings, and HTTP Methods
These three pieces serve different purposes:
- A path identifies the target resource.
/productssuits a product collection, while/products/10points to one specific product. - A query string adds optional instructions.
/products?q=keyboardstill requests the collection, but filters it with the keywordkeyboard. - An HTTP method describes the action.
GETreads,POSTcreates,PUTorPATCHupdates, andDELETEremoves data.
Applications do not all need identical URL patterns. What matters is keeping resource names and methods consistent so clients do not have to guess.
Create Several Product Routes
The following example shows list, detail, and create routes in one file:
# File: app.py
from flask import Flask, request
app = Flask(__name__)
@app.get("/products")
def list_products():
keyword = request.args.get("q", "")
return {"keyword": keyword, "items": []}
@app.get("/products/<int:product_id>")
def product_detail(product_id):
return {"id": product_id, "name": "Keyboard"}
@app.post("/products")
def create_product():
return {"message": "Product created"}, 201
Read a query string
The first route receives query strings through request.args. Calling .get("q", "") reads q and falls back to an empty string when the parameter is missing.
Opening /products produces an empty keyword. Opening /products?q=mouse produces this response:
{
"keyword": "mouse",
"items": []
}
Query string values arrive as text. If the URL contains ?page=2, the value is still the string "2" until the application converts and validates it.
Constrain a path parameter
The /products/<int:product_id> route contains a dynamic segment. Flask passes its value to the product_id function parameter, while the int converter accepts numbers only. A request to /products/12 calls product_detail(12), but /products/abc does not match and ends with a 404.
A converter checks the shape of the URL, not whether the record exists. After adding a database, a correctly formatted product ID still needs to be queried and may return 404.
Methods and status codes carry meaning
The @app.post("/products") decorator accepts only POST. Opening that address directly in a browser sends GET, so Flask responds with 405 Method Not Allowed.
The create route returns a tuple containing response data and status 201, which means a resource was created successfully. Flask uses 200 by default for a successful GET request.
Try the Routes from a Terminal
A browser is convenient for GET, while curl makes other methods easy to send:
# File: terminal
curl "http://127.0.0.1:5000/products?q=mouse"
curl "http://127.0.0.1:5000/products/10"
curl -X POST "http://127.0.0.1:5000/products"
Watch the Flask log after each command. It shows the path, method, and returned status code.
Keep URLs Easy to Maintain
Avoid putting every action into a verb-based URL such as /create-product or /delete-product. Resource-based URLs are usually easier to read: use /products and let the HTTP method describe the action.
It is also worth keeping these habits:
- Do not place passwords, tokens, or sensitive data in query strings. URLs commonly appear in browser history and server logs.
- Do not use
GETto modify or delete data. Links can be crawled, prefetched, or opened accidentally. - Distinguish ordinary success, missing data, and invalid input. Returning
200for every outcome makes client decisions harder.
As an exercise, add /categories, /products/<int:product_id>/reviews, and a ?sort=price option. For each one, identify the fixed path, the dynamic segment, and the optional query. Once those roles are clear, larger route collections become much easier to organize.
Related Articles
Keep reading within the same topic.
Flask Jinja Templates: Layouts, Variables, and Loops
Use Jinja templates in Flask to pass data into HTML, build loops and conditionals, and reuse a base layout without repeating markup.
Flask Tutorial: Setup and First Route
Set up Flask in a virtual environment, create app.py, run the development server, and follow a request through your first browser route.
Flask CRUD App: Build with MySQL, Auth, and Caching
Build a Flask CRUD app with MySQL, authentication, testing, caching, environment setup, and deployment-ready project structure.
Flask Tutorial: Build a Python Website from Scratch
Learn how to build a simple Python web application with Flask, routes, templates, form handling, and a clean starter structure for beginners.