Skip to main content

Routing

Framefox uses the @Route decorator to define URL patterns and map them to controller methods. The routing system supports path parameters, type hints, and automatic dependency injection.

Basic Route Definition

Use the @Route decorator on controller methods to define routes:
from framefox.core.controller.abstract_controller import AbstractController
from framefox.core.routing.decorator.route import Route

class HomeController(AbstractController):
    @Route(path="/", name="home", methods=["GET"])
    async def index(self):
        return self.render("home.html")

Route Parameters

The @Route decorator accepts the following parameters:
path
str
required
The URL path pattern for the route. Can include path parameters in curly braces like /users/{user_id}
name
str
required
A unique identifier for the route. Used for URL generation with generate_url()
methods
list
required
List of HTTP methods accepted by this route (e.g., ["GET"], ["POST"], ["GET", "POST"])
response_model
type
default:"None"
Pydantic model for response validation (optional)
tags
list
default:"[]"
OpenAPI tags for grouping endpoints in API documentation

Path Parameters

Define dynamic URL segments using curly braces in the path and corresponding method parameters:
@Route(path="/users/{user_id}", name="user_detail", methods=["GET"])
async def show_user(self, user_id: int):
    return self.json({"user_id": user_id})

Type Hints for Path Parameters

Framefox automatically converts path parameters based on their type hints:
@Route(path="/posts/{post_id}/comments/{comment_id}", 
       name="comment_detail", 
       methods=["GET"])
async def show_comment(self, post_id: int, comment_id: int):
    return self.json({
        "post_id": post_id,
        "comment_id": comment_id
    })
Supported Types:
  • int - Integer values
  • str - String values (default)
  • float - Floating-point numbers
  • bool - Boolean values

Multiple HTTP Methods

Handle multiple HTTP methods in a single route:
@Route(path="/contact", name="contact", methods=["GET", "POST"])
async def contact(self, request: Request):
    if request.method == "GET":
        return self.render("contact.html")
    else:
        # Handle POST request
        form_data = await request.form()
        # Process form data
        self.flash("success", "Message sent!")
        return self.redirect("/")

Route Naming Conventions

Follow these conventions for consistent route naming:
  • List/Index: resource_list or resource_index (e.g., user_list)
  • Show/Detail: resource_detail or resource_show (e.g., user_detail)
  • Create: resource_create (e.g., user_create)
  • Edit: resource_edit (e.g., user_edit)
  • Delete: resource_delete (e.g., user_delete)
  • API Endpoints: Prefix with api_ (e.g., api_user_list)
class UserController(AbstractController):
    @Route(path="/users", name="user_list", methods=["GET"])
    async def list_users(self):
        # List all users
        pass
    
    @Route(path="/users/{user_id}", name="user_detail", methods=["GET"])
    async def show_user(self, user_id: int):
        # Show single user
        pass
    
    @Route(path="/users/create", name="user_create", methods=["GET", "POST"])
    async def create_user(self):
        # Create new user
        pass
    
    @Route(path="/users/{user_id}/edit", name="user_edit", methods=["GET", "POST"])
    async def edit_user(self, user_id: int):
        # Edit existing user
        pass
    
    @Route(path="/users/{user_id}/delete", name="user_delete", methods=["POST"])
    async def delete_user(self, user_id: int):
        # Delete user
        pass

Dependency Injection in Routes

Framefox automatically injects services into route methods based on type hints. The framework distinguishes between different parameter types:

Automatic Service Injection

Services registered in the container are automatically injected:
from app.services.user_service import UserService
from app.services.email_service import EmailService

@Route(path="/users/{user_id}/notify", name="user_notify", methods=["POST"])
async def notify_user(
    self, 
    user_id: int,
    user_service: UserService,
    email_service: EmailService
):
    user = user_service.find(user_id)
    email_service.send(user.email, "Notification", "You have a new message")
    return self.json({"status": "sent"})

FastAPI Native Types

Framefox supports FastAPI’s native types without service injection:
from fastapi import Request, BackgroundTasks

@Route(path="/process", name="process_data", methods=["POST"])
async def process(
    self,
    request: Request,
    background_tasks: BackgroundTasks
):
    data = await request.json()
    background_tasks.add_task(process_in_background, data)
    return self.json({"status": "processing"})
Supported FastAPI Types:
  • Request - HTTP request object
  • Response - HTTP response object
  • BackgroundTasks - Background task scheduler
  • WebSocket - WebSocket connection
  • Cookie, Header, Query, Path, Body, Form, File - Parameter validators

Pydantic Models

Use Pydantic models for request body validation:
from pydantic import BaseModel

class CreateUserRequest(BaseModel):
    name: str
    email: str
    age: int

@Route(path="/api/users", name="api_user_create", methods=["POST"])
async def create_user_api(self, user_data: CreateUserRequest):
    # user_data is automatically validated
    return self.json({
        "message": "User created",
        "data": user_data.dict()
    })

Primitive Types

Primitive types are treated as query parameters:
@Route(path="/search", name="search", methods=["GET"])
async def search(self, query: str, page: int = 1, limit: int = 10):
    # query, page, and limit are extracted from query parameters
    return self.json({
        "query": query,
        "page": page,
        "limit": limit
    })

Response Models

Define response models for automatic OpenAPI documentation:
from pydantic import BaseModel

class UserResponse(BaseModel):
    id: int
    name: str
    email: str

@Route(
    path="/api/users/{user_id}", 
    name="api_user_detail", 
    methods=["GET"],
    response_model=UserResponse
)
async def get_user(self, user_id: int):
    return self.json({
        "id": user_id,
        "name": "John Doe",
        "email": "[email protected]"
    })

API Tags

Group related endpoints using tags for better API documentation:
@Route(
    path="/api/users", 
    name="api_user_list", 
    methods=["GET"],
    tags=["users", "api"]
)
async def list_users_api(self):
    return self.json({"users": []})

URL Generation

Generate URLs from route names using the generate_url() method:
@Route(path="/dashboard", name="dashboard", methods=["GET"])
async def dashboard(self):
    user_url = self.generate_url("user_detail", user_id=123)
    edit_url = self.generate_url("user_edit", user_id=123)
    
    return self.render("dashboard.html", {
        "user_url": user_url,
        "edit_url": edit_url
    })

Best Practices

Choose route names that clearly indicate the resource and action. This makes URL generation and maintenance easier.
Follow REST conventions for resource paths:
  • GET /users - List all users
  • GET /users/{id} - Get single user
  • POST /users - Create user
  • PUT /users/{id} - Update user
  • DELETE /users/{id} - Delete user
Always provide type hints for path parameters and injected services. This enables automatic conversion and validation.
Use different controllers or prefixes for API endpoints vs. web pages. Tag API routes appropriately.

Next Steps

Responses

Learn about different response types

Forms

Handle form submissions and validation

Build docs developers (and LLMs) love