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:

  1. Function Introspection: Robyn analyzes your function signature at registration time
  2. 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 AnnotationReserved NameDescription
Requestrequest, req, rComplete request object
QueryParamsquery_paramsURL query parameters
HeadersheadersRequest headers
PathParamspath_paramsURL path parameters
RequestBodybodyRaw request body
RequestMethodmethodHTTP method (GET, POST, etc.)
RequestURLurlRequest URL information
FormDataform_dataForm-encoded data
RequestFilesfilesUploaded files
RequestIPip_addrClient IP address
RequestIdentityidentityAuthentication 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: