Registering Routes and Handlers
In this tutorial, you will build a structured API for a Task Manager application. You will learn how to define URL rules, handle specific HTTP methods, manage errors gracefully, and organize your code using Blueprints.
Prerequisites
To follow this tutorial, you need the vik-advani-flask-9fcd34c codebase installed in your environment. You should be familiar with basic Python decorators.
Step 1: Initialize the Application
First, create the central application object. In this codebase, the Flask class (found in src/flask/app.py) inherits from App and Scaffold, providing the registry for all your routes.
from flask import Flask
app = Flask(__name__)
The import_name (here __name__) tells the Scaffold where the application is located, which is used to resolve resources like templates and static files relative to the root_path.
Step 2: Register Basic Routes
Use the @app.route decorator to map a URL to a view function. By default, routes respond to GET requests.
@app.route("/")
def index():
return "Welcome to the Task API"
@app.route("/info")
def info():
return {"version": "1.0.0", "status": "active"}
The Scaffold.route method internally calls add_url_rule. When you return a dictionary from a view, the Flask.make_response method automatically converts it into a JSON response using the configured json_provider_class.
Step 3: Use Method Shortcuts
For cleaner code, use the method-specific shortcuts provided by the Scaffold class, such as @app.get, @app.post, and @app.delete.
tasks = []
@app.get("/tasks")
def get_tasks():
return {"tasks": tasks}
@app.post("/tasks")
def add_task():
# In a real app, you would parse request data here
tasks.append("New Task")
return {"message": "Task added"}, 201
These shortcuts are wrappers around Scaffold.route that automatically set the methods argument (e.g., methods=["POST"]).
Step 4: Implement Request Hooks
You can execute code before or after every request using lifecycle hooks. This is useful for logging, database connections, or authentication.
@app.before_request
def log_request():
app.logger.info(f"Handling request to {app.name}")
@app.after_request
def add_header(response):
response.headers["X-Content-Type-Options"] = "nosniff"
return response
Scaffold.before_request registers a function to run before the view. If it returns a value, that value is treated as the response, and the view function is never called. Scaffold.after_request functions receive and must return a response object.
Step 5: Handle Errors
Use the @app.errorhandler decorator to define custom responses for HTTP errors or specific Python exceptions.
@app.errorhandler(404)
def not_found(error):
return {"error": "Resource not found"}, 404
@app.errorhandler(Exception)
def handle_unexpected_error(error):
app.logger.error(f"An error occurred: {error}")
return {"error": "Internal server error"}, 500
The Scaffold.register_error_handler method stores these in error_handler_spec. When an exception occurs during full_dispatch_request, the app searches this specification to find the most appropriate handler.
Step 6: Modularize with Blueprints
As your application grows, use Blueprint to group related routes. You register them to the main app using App.register_blueprint.
from flask import Blueprint
# Define the blueprint (usually in a separate file)
admin_bp = Blueprint("admin", __name__, url_prefix="/admin")
@admin_bp.route("/stats")
def stats():
return {"users": 10, "uptime": "24h"}
# Register it to the main app
app.register_blueprint(admin_bp)
Blueprints also inherit from Scaffold, so they have their own route, before_request, and errorhandler methods. When registered, their routes are prefixed with the url_prefix.
Important: The Setup Constraint
In this codebase, you must register all routes, handlers, and blueprints before the application starts handling requests.
# This will work
app.add_url_rule("/late-route", endpoint="late", view_func=lambda: "OK")
# If the app had already handled a request, this would raise an AssertionError
# via Scaffold._check_setup_finished
The App._check_setup_finished method ensures that the application configuration remains consistent once it begins processing traffic.
Complete Result
Your final application structure combines these elements into a cohesive API:
from flask import Flask, Blueprint
app = Flask(__name__)
admin_bp = Blueprint("admin", __name__, url_prefix="/admin")
@app.before_request
def setup():
app.logger.debug("Initializing request")
@app.get("/")
def home():
return "Task Manager"
@admin_bp.get("/dashboard")
def dashboard():
return "Admin Dashboard"
@app.errorhandler(404)
def error_404(e):
return "Not Found", 404
app.register_blueprint(admin_bp)
if __name__ == "__main__":
app.run()
By following this pattern, you leverage the inheritance hierarchy of Scaffold -> App -> Flask to build a robust, maintainable application. For next steps, explore how to use url_for to generate dynamic links to these registered endpoints.