Advanced Routing Patterns
This guide covers advanced routing techniques for complex applications, including middleware integration, conditional routes, and sophisticated URL patterns.
Route Organization
Section titled âRoute OrganizationâModular Controllers
Section titled âModular ControllersâOrganize routes by functionality using separate controller classes:
class UserApiController(AbstractController): @Route("/api/v1/users", "api.v1.user.index", methods=["GET"]) async def index(self): return {"users": []}
@Route("/api/v1/users/{id}", "api.v1.user.show", methods=["GET"]) async def show(self, id: int): return {"user": {}}
# src/controller/web/user_controller.pyclass UserWebController(AbstractController): @Route("/users", "user.index", methods=["GET"]) async def index(self): return self.render("users/index.html")
@Route("/users/{id}", "user.show", methods=["GET"]) async def show(self, id: int): return self.render("users/show.html", {"user_id": id})
Route Prefixes
Section titled âRoute PrefixesâReduce repetition by using class constants for common prefixes:
class UserApiController(AbstractController): BASE_ROUTE = "/api/v1/users"
@Route(f"{BASE_ROUTE}", "api.v1.user.index", methods=["GET"]) async def index(self): return {"users": []}
@Route(f"{BASE_ROUTE}/{{id}}", "api.v1.user.show", methods=["GET"]) async def show(self, id: int): return {"user": {}}
@Route(f"{BASE_ROUTE}/{{id}}/posts", "api.v1.user.posts", methods=["GET"]) async def user_posts(self, id: int): return {"posts": []}
Advanced Routing
Section titled âAdvanced RoutingâConditional Routes
Section titled âConditional RoutesâCreate routes that are only available under certain conditions:
from framefox.core.config.settings import Settings
class DebugController(AbstractController): def __init__(self, settings: Settings): self.settings = settings
@Route("/debug/info", "debug.info", methods=["GET"]) async def debug_info(self): if self.settings.app_env != "development": raise HTTPException(status_code=404) return {"debug": "info", "environment": self.settings.app_env}
Route Middleware
Section titled âRoute MiddlewareâApply middleware to specific routes for authentication, validation, or other cross-cutting concerns:
from framefox.core.security.decorators import RequireAuth
@Route("/profile", "user.profile", methods=["GET"])@RequireAuthasync def profile(self): return self.render("user/profile.html")
@Route("/admin/dashboard", "admin.dashboard", methods=["GET"])@RequireAuth(roles=["admin"])async def admin_dashboard(self): return self.render("admin/dashboard.html")
Complex URL Patterns
Section titled âComplex URL PatternsâUUID Routes for Security
Section titled âUUID Routes for SecurityâUse UUID constraints to prevent ID enumeration attacks:
from uuid import UUID
@Route("/api/resources/{resource_uuid:uuid}", "api.resource.show", methods=["GET"])async def show_resource(self, resource_uuid: UUID): """Only matches valid UUIDs: prevents ID enumeration attacks.""" resource = await self.resource_service.get_by_uuid(resource_uuid) if not resource: return self.json({"error": "Resource not found"}, status=404) return self.json({"resource": resource.to_dict()})
Date-Based URLs
Section titled âDate-Based URLsâCreate SEO-friendly date-based blog URLs:
@Route("/posts/{year:int}/{month:int}/{day:int}/{slug}", "post.by_date", methods=["GET"])async def show_post_by_date(self, year: int, month: int, day: int, slug: str): """Date-based blog post URLs: /posts/2024/03/15/my-blog-post""" # Validate date try: post_date = datetime(year, month, day) except ValueError: return self.json({"error": "Invalid date"}, status=400)
post = await self.post_service.get_by_date_and_slug(post_date, slug) if not post: return self.json({"error": "Post not found"}, status=404)
return self.render("posts/show.html", { "post": post, "canonical_url": self.generate_url("post.by_date", year=year, month=month, day=day, slug=slug) })
Multi-Format Endpoints
Section titled âMulti-Format EndpointsâSupport multiple output formats in a single route:
@Route("/api/data/{format:str}", "api.data.export", methods=["GET"])async def export_data(self, format: str): """Support multiple export formats with validation.""" allowed_formats = ["json", "csv", "xml", "xlsx"]
if format not in allowed_formats: return self.json({ "error": f"Unsupported format. Allowed: {', '.join(allowed_formats)}" }, status=400)
data = await self.data_service.get_export_data()
if format == "json": return self.json({"data": data}) elif format == "csv": csv_content = await self.export_service.to_csv(data) return Response(content=csv_content, media_type="text/csv") elif format == "xml": xml_content = await self.export_service.to_xml(data) return Response(content=xml_content, media_type="application/xml") elif format == "xlsx": xlsx_content = await self.export_service.to_xlsx(data) return Response(content=xlsx_content, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
Geographic and Coordinate Routes
Section titled âGeographic and Coordinate RoutesâHandle geographic data with precise routing patterns:
@Route("/api/coordinates/{lat:float}/{lng:float}", "api.location", methods=["GET"])async def get_location(self, lat: float, lng: float): """Matches decimal coordinates: /api/coordinates/40.7128/-74.0060""" # Validate coordinate ranges if not (-90 <= lat <= 90) or not (-180 <= lng <= 180): return self.json({"error": "Invalid coordinates"}, status=400)
location_data = await self.geo_service.get_location_info(lat, lng) return self.json({"location": location_data})
@Route("/api/places/{country}/{state}/{city}", "api.place.show", methods=["GET"])async def show_place(self, country: str, state: str, city: str): """Hierarchical geographic routing""" place = await self.place_service.find_by_hierarchy(country, state, city) if not place: return self.json({"error": "Place not found"}, status=404)
return self.json({ "place": place.to_dict(), "weather": await self.weather_service.get_current(place.coordinates), "timezone": place.timezone })
File Serving with Path Constraints
Section titled âFile Serving with Path ConstraintsâSecurely serve files using path constraints:
@Route("/files/{filepath:path}", "file.serve", methods=["GET"])async def serve_file(self, filepath: str): """Securely serve files with path validation""" # Security: Prevent directory traversal if '..' in filepath or filepath.startswith('/'): return self.json({"error": "Invalid file path"}, status=400)
# Validate file exists and is allowed full_path = await self.file_service.get_safe_path(filepath) if not full_path: return self.json({"error": "File not found"}, status=404)
return await self.file_service.serve_file(full_path)
Best Practices for Advanced Routes
Section titled âBest Practices for Advanced Routesâ1. Security Considerations
Section titled â1. Security ConsiderationsâAlways validate and sanitize route parameters:
# â
Good - Proper validation@Route("/api/users/{id:int}", "api.user.show", methods=["GET"])async def show_user(self, id: int): if id <= 0: return self.json({"error": "Invalid user ID"}, status=400) # Continue with logic...
# â Bad - No validation@Route("/api/users/{id}", "api.user.show", methods=["GET"])async def show_user(self, id): # Direct use without validation user = await self.user_service.get_by_id(id)
2. Performance Optimization
Section titled â2. Performance OptimizationâUse appropriate HTTP methods and caching strategies:
@Route("/api/users/{id}", "api.user.show", methods=["GET"])async def show_user(self, id: int): # Add caching headers for GET requests response = await self.get_user_response(id) response.headers["Cache-Control"] = "public, max-age=300" return response
3. Error Handling
Section titled â3. Error HandlingâImplement comprehensive error handling:
@Route("/api/complex/{param1}/{param2:int}", "api.complex", methods=["GET"])async def complex_route(self, param1: str, param2: int): try: # Complex business logic result = await self.complex_service.process(param1, param2) return self.json({"result": result}) except ValidationError as e: return self.json({"error": "Validation failed", "details": str(e)}, status=422) except NotFoundError: return self.json({"error": "Resource not found"}, status=404) except Exception as e: logger.error(f"Unexpected error in complex_route: {e}") return self.json({"error": "Internal server error"}, status=500)