Advanced Routing and Parameter Injection
Robyn's routing system goes far beyond simple URL matching. It includes sophisticated parameter injection, route optimization, and flexible pattern matching that makes building complex APIs effortless.
Understanding Parameter Injection
Robyn automatically analyzes your function signatures and injects the appropriate request components. This eliminates boilerplate code and makes handlers cleaner.
The Injection Engine
The parameter injection system works in two phases:
- Function Introspection: Robyn analyzes your function signature at registration time
- Runtime Injection: For each request, Robyn provides the exact parameters your function needs
Type-Based Injection: Uses type annotations to determine what to inject
Type-Based Parameter Injection
from robyn import Request, QueryParams, Headers
from robyn.types import PathParams, RequestBody, RequestMethod
@app.post("/users/:user_id/posts/:post_id")
async def update_post(
# Robyn automatically injects these based on type annotations
request: Request, # Complete request object
path_params: PathParams, # {"user_id": "123", "post_id": "456"}
query_params: QueryParams, # ?draft=true&tags=python,web
headers: Headers, # All request headers
body: RequestBody, # Raw request body
method: RequestMethod # "POST"
):
user_id = path_params["user_id"]
post_id = path_params["post_id"]
is_draft = query_params.get("draft") == "true"
content_type = headers.get("content-type")
return {
"user_id": user_id,
"post_id": post_id,
"is_draft": is_draft,
"body_size": len(body),
"method": method
}
Name-Based Injection: Uses parameter names to inject components when type annotations aren't available
Name-Based Parameter Injection
# Reserved parameter names that Robyn recognizes
@app.get("/search/:category")
def search_handler(
query_params, # Injected by name
path_params, # Injected by name
headers, # Injected by name
request # Injected by name (full request object)
):
category = path_params["category"]
search_term = query_params.get("q", "")
user_agent = headers.get("user-agent", "")
return {
"category": category,
"search": search_term,
"user_agent": user_agent
}
Complete List of Injectable Types
Type Annotation | Reserved Name | Description |
---|---|---|
Request | request , req , r | Complete request object |
QueryParams | query_params | URL query parameters |
Headers | headers | Request headers |
PathParams | path_params | URL path parameters |
RequestBody | body | Raw request body |
RequestMethod | method | HTTP method (GET, POST, etc.) |
RequestURL | url | Request URL information |
FormData | form_data | Form-encoded data |
RequestFiles | files | Uploaded files |
RequestIP | ip_addr | Client IP address |
RequestIdentity | identity | Authentication identity |
Advanced URL Patterns
Dynamic Route Parameters
Robyn supports multiple types of path parameters with flexible matching patterns.
Dynamic Route Patterns
# Simple parameter
@app.get("/users/:id")
def get_user(path_params):
return {"user_id": path_params["id"]}
# Multiple parameters
@app.get("/users/:user_id/posts/:post_id")
def get_user_post(path_params):
return {
"user_id": path_params["user_id"],
"post_id": path_params["post_id"]
}
# Optional parameters with defaults
@app.get("/posts/:id/:slug?")
def get_post(path_params):
post_id = path_params["id"]
slug = path_params.get("slug", f"post-{post_id}")
return {"id": post_id, "slug": slug}
# Wildcard matching
@app.get("/files/*filepath")
def serve_file(path_params):
filepath = path_params["filepath"]
return {"serving": filepath}
Route Constraints and Validation
While Robyn doesn't have built-in parameter validation, you can implement it in your handlers for type safety.
Parameter Validation
import re
from robyn import HTTPException
@app.get("/users/:user_id")
def get_user(path_params):
user_id = path_params["user_id"]
# Validate that user_id is numeric
if not user_id.isdigit():
raise HTTPException(400, "user_id must be numeric")
user_id = int(user_id)
if user_id <= 0:
raise HTTPException(400, "user_id must be positive")
return {"user_id": user_id}
@app.get("/posts/:slug")
def get_post_by_slug(path_params):
slug = path_params["slug"]
# Validate slug format
if not re.match(r'^[a-z0-9-]+$', slug):
raise HTTPException(400, "Invalid slug format")
return {"slug": slug}
Route Optimization
Const Routes for Static Responses
Use const=True
for responses that never change. These are cached in Rust memory and served without Python execution.
Const Route Optimization
# Perfect for health checks, static configuration
@app.get("/health", const=True)
def health_check():
return {"status": "healthy", "version": "1.0"}
# API metadata that rarely changes
@app.get("/api/info", const=True)
def api_info():
return {
"name": "My API",
"version": "2.1.0",
"documentation": "/docs"
}
# Static configuration endpoints
@app.get("/config/public", const=True)
def public_config():
return {
"max_upload_size": "10MB",
"allowed_origins": ["https://myapp.com"]
}
Route Priority and Ordering
Routes are matched in the order they're registered. More specific routes should be registered before general ones.
Route Ordering Best Practices
# GOOD: Specific routes first
@app.get("/users/profile")
def get_current_user_profile():
return {"profile": "current_user"}
@app.get("/users/settings")
def get_user_settings():
return {"settings": "user_settings"}
@app.get("/users/:id")
def get_user(path_params):
return {"user_id": path_params["id"]}
# BAD: This would never be reached
# @app.get("/users/:id") # Registered first
# @app.get("/users/profile") # Never matched!
Advanced Query Parameter Handling
Query Parameter Parsing
Robyn provides rich query parameter handling with automatic type conversion helpers.
Advanced Query Parameters
@app.get("/search")
def search(query_params):
# Basic parameter access
q = query_params.get("q", "")
# Parameters with defaults
page = int(query_params.get("page", "1"))
limit = int(query_params.get("limit", "10"))
# Boolean parameters
include_deleted = query_params.get("include_deleted", "false").lower() == "true"
# Array parameters (?tags=python&tags=web&tags=api)
tags = query_params.get_list("tags") or []
# Convert to dict for easier processing
all_params = query_params.to_dict()
return {
"query": q,
"page": page,
"limit": limit,
"include_deleted": include_deleted,
"tags": tags,
"all_params": all_params
}
Complex Query String Patterns
Handle complex query patterns like filtering, sorting, and nested parameters.
Complex Query Patterns
@app.get("/api/products")
def get_products(query_params):
# Filtering: ?filter[category]=electronics&filter[price_min]=100
filters = {}
for key, value in query_params.to_dict().items():
if key.startswith("filter[") and key.endswith("]"):
filter_key = key[7:-1] # Remove "filter[" and "]"
filters[filter_key] = value
# Sorting: ?sort=price&order=desc
sort_field = query_params.get("sort", "created_at")
sort_order = query_params.get("order", "asc")
# Pagination: ?page=2&per_page=20
page = int(query_params.get("page", "1"))
per_page = min(int(query_params.get("per_page", "10")), 100) # Cap at 100
# Field selection: ?fields=id,name,price
fields = query_params.get("fields", "").split(",") if query_params.get("fields") else None
return {
"filters": filters,
"sort": {"field": sort_field, "order": sort_order},
"pagination": {"page": page, "per_page": per_page},
"fields": fields
}
SubRouters and Modular Routing
Creating SubRouters
SubRouters help organize large applications by grouping related routes with common prefixes.
SubRouter Organization
from robyn import Robyn, SubRouter
app = Robyn(__file__)
# API v1 routes
api_v1 = SubRouter(__file__, prefix="/api/v1")
@api_v1.get("/users")
def list_users():
return {"users": []}
@api_v1.get("/users/:id")
def get_user(path_params):
return {"user_id": path_params["id"]}
@api_v1.post("/users")
def create_user(body):
return {"created": True, "data": body}
# Admin routes
admin = SubRouter(__file__, prefix="/admin")
@admin.get("/dashboard")
def admin_dashboard():
return {"dashboard": "admin"}
@admin.get("/users")
def admin_users():
return {"admin_users": []}
# Register subrouters
app.include_router(api_v1)
app.include_router(admin)
# Routes are now available at:
# /api/v1/users, /api/v1/users/:id, /admin/dashboard, etc.
SubRouter Middleware
Apply middleware to entire SubRouter groups for common functionality like authentication.
SubRouter Middleware
from robyn import SubRouter
from robyn.authentication import AuthenticationHandler
# Admin routes with authentication
admin = SubRouter(__file__, prefix="/admin")
class AdminAuth(AuthenticationHandler):
def authenticate(self, request):
auth_header = request.headers.get("authorization", "")
if not auth_header.startswith("Bearer "):
return False
token = auth_header[7:] # Remove "Bearer "
return self.validate_admin_token(token)
def validate_admin_token(self, token):
# Your token validation logic
return token == "admin-secret-token"
# Apply authentication to all admin routes
admin.add_auth_handler(AdminAuth())
@admin.get("/users")
def admin_users():
return {"admin_users": ["user1", "user2"]}
@admin.delete("/users/:id")
def delete_user(path_params):
return {"deleted": path_params["id"]}
Route Testing and Debugging
Route Inspection
Debug your routes by inspecting the registered route table.
Route Debugging
from robyn import Robyn
app = Robyn(__file__)
@app.get("/users/:id")
def get_user(path_params):
return {"user": path_params["id"]}
@app.post("/users")
def create_user(body):
return {"created": True}
# Debug: Print all registered routes
if __name__ == "__main__":
print("Registered routes:")
for route in app.get_routes():
print(f"{route.method} {route.path}")
app.start(port=8080)
Route Performance Monitoring
Add timing middleware to monitor route performance.
Performance Monitoring
import time
from robyn import Robyn
app = Robyn(__file__)
@app.before_request
def timing_middleware(request):
request.start_time = time.time()
return request
@app.after_request
def timing_after_middleware(request, response):
duration = time.time() - request.start_time
print(f"{request.method} {request.url.path} - {duration:.3f}s")
response.headers["X-Response-Time"] = f"{duration:.3f}s"
return response
@app.get("/slow")
def slow_endpoint():
time.sleep(0.1) # Simulate work
return {"message": "slow response"}
Best Practices
1. Parameter Injection Patterns
- Use type annotations for better IDE support and self-documenting code
- Inject only what you need to keep handlers focused
- Validate parameters early to provide clear error messages
2. Route Organization
- Group related routes using SubRouters
- Order routes from specific to general to avoid matching issues
- Use consistent naming conventions for path parameters
3. Performance Optimization
- Use const routes for static responses
- Minimize parameter injection in high-traffic endpoints
- Cache expensive computations rather than repeating them
4. Error Handling
- Validate path parameters early in handlers
- Provide meaningful error messages for invalid input
- Use consistent error response formats across your API
What's Next?
Now that you've mastered advanced routing, explore other Robyn features: