CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-starlette

The little ASGI library that shines.

Overview
Eval results
Files

routing.mddocs/

Routing System

Starlette provides a flexible and powerful routing system for mapping URLs to endpoint functions, supporting HTTP routes, WebSocket routes, sub-application mounting, and host-based routing.

Router Class

from starlette.routing import Router, BaseRoute
from starlette.middleware import Middleware
from starlette.types import ASGIApp, Lifespan
from typing import Sequence, Callable, Collection, Any
from starlette.requests import Request
from starlette.responses import Response
from starlette.websockets import WebSocket

class Router:
    """
    Router handles request routing and dispatch.
    
    The Router class manages:
    - Route matching and parameter extraction
    - URL generation from named routes  
    - Middleware application
    - Lifespan event handling
    """
    
    def __init__(
        self,
        routes: Sequence[BaseRoute] | None = None,
        redirect_slashes: bool = True,
        default: ASGIApp | None = None,
        on_startup: Sequence[Callable] | None = None,
        on_shutdown: Sequence[Callable] | None = None,
        lifespan: Lifespan | None = None,
        middleware: Sequence[Middleware] | None = None,
    ) -> None:
        """
        Initialize router.
        
        Args:
            routes: List of route definitions
            redirect_slashes: Automatically redirect /path/ to /path
            default: Default ASGI app when no route matches
            on_startup: Startup event handlers (deprecated)
            on_shutdown: Shutdown event handlers (deprecated)
            lifespan: Lifespan context manager
            middleware: Middleware stack for this router
        """
    
    @property
    def routes(self) -> list[BaseRoute]:
        """List of registered routes."""
    
    def url_path_for(self, name: str, /, **path_params) -> URLPath:
        """Generate URL path for named route."""
    
    def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
        """Mount ASGI application at path."""
    
    def host(self, host: str, app: ASGIApp, name: str | None = None) -> None:
        """Add host-based routing."""
    
    def add_route(
        self,
        path: str,
        endpoint: Callable[[Request], Awaitable[Response] | Response],
        methods: Collection[str] | None = None,
        name: str | None = None,
        include_in_schema: bool = True,
    ) -> None:
        """Add HTTP route."""
    
    def add_websocket_route(
        self,
        path: str,
        endpoint: Callable[[WebSocket], Awaitable[None]],
        name: str | None = None,
    ) -> None:
        """Add WebSocket route."""
    
    def add_event_handler(self, event_type: str, func: Callable[[], Any]) -> None:
        """Add event handler (deprecated)."""
    
    async def startup(self) -> None:
        """Execute startup handlers."""
    
    async def shutdown(self) -> None:
        """Execute shutdown handlers."""
    
    async def lifespan(self, scope, receive, send) -> None:
        """Handle ASGI lifespan protocol."""

Route Classes

HTTP Routes

from starlette.routing import Route, Match
from starlette.middleware import Middleware
from starlette.types import Scope, Receive, Send
from starlette.datastructures import URLPath
from typing import Any, Callable, Collection, Sequence

class Route:
    """
    HTTP route definition.
    
    Maps URL patterns to endpoint functions for HTTP requests.
    """
    
    def __init__(
        self,
        path: str,
        endpoint: Callable[..., Any],
        *,
        methods: Collection[str] | None = None,
        name: str | None = None,
        include_in_schema: bool = True,
        middleware: Sequence[Middleware] | None = None,
    ) -> None:
        """
        Initialize HTTP route.
        
        Args:
            path: URL pattern with optional parameters
            endpoint: Function or class to handle requests
            methods: Allowed HTTP methods (default: ["GET"])
            name: Route name for URL generation
            include_in_schema: Include in OpenAPI schema
            middleware: Route-specific middleware
        """
    
    @property
    def path(self) -> str:
        """Route path pattern."""
    
    @property
    def endpoint(self) -> Callable:
        """Route endpoint function."""
    
    @property
    def methods(self) -> set[str]:
        """Allowed HTTP methods."""
    
    @property
    def name(self) -> str | None:
        """Route name."""
    
    def matches(self, scope: Scope) -> tuple[Match, Scope]:
        """Check if route matches request scope."""
    
    def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:
        """Generate URL path for this route."""
    
    async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:
        """Handle matched request."""

WebSocket Routes

from starlette.routing import WebSocketRoute

class WebSocketRoute:
    """
    WebSocket route definition.
    
    Maps URL patterns to WebSocket endpoint functions.
    """
    
    def __init__(
        self,
        path: str,
        endpoint: Callable,
        name: str | None = None,
        middleware: Sequence[Middleware] | None = None,
    ) -> None:
        """
        Initialize WebSocket route.
        
        Args:
            path: URL pattern with optional parameters
            endpoint: Function or class to handle WebSocket
            name: Route name for URL generation  
            middleware: Route-specific middleware
        """
    
    @property
    def path(self) -> str:
        """Route path pattern."""
    
    @property
    def endpoint(self) -> Callable:
        """WebSocket endpoint function."""
    
    @property
    def name(self) -> str | None:
        """Route name."""
    
    def matches(self, scope) -> tuple[Match, dict]:
        """Check if route matches WebSocket scope."""
    
    def url_path_for(self, name: str, /, **path_params) -> URLPath:
        """Generate URL path for this route."""
    
    async def handle(self, scope, receive, send) -> None:
        """Handle matched WebSocket connection."""

Mount Points

from starlette.routing import Mount

class Mount:
    """
    Mount point for sub-applications.
    
    Mounts an ASGI application at a path prefix.
    """
    
    def __init__(
        self,
        path: str,
        app: ASGIApp | None = None,
        routes: Sequence[BaseRoute] | None = None,
        name: str | None = None,
        middleware: Sequence[Middleware] | None = None,
    ) -> None:
        """
        Initialize mount point.
        
        Args:
            path: Mount path prefix
            app: ASGI application to mount
            routes: Routes to mount (alternative to app)
            name: Mount name for URL generation
            middleware: Mount-specific middleware
        """
    
    @property
    def path(self) -> str:
        """Mount path prefix."""
    
    @property
    def app(self) -> ASGIApp:
        """Mounted ASGI application."""
    
    @property
    def name(self) -> str | None:
        """Mount name."""
    
    def matches(self, scope) -> tuple[Match, dict]:
        """Check if mount matches request scope."""
    
    def url_path_for(self, name: str, /, **path_params) -> URLPath:
        """Generate URL path within mount."""
    
    async def handle(self, scope, receive, send) -> None:
        """Handle request to mounted application."""

Host-based Routing

from starlette.routing import Host

class Host:
    """
    Host-based routing.
    
    Routes requests based on the Host header.
    """
    
    def __init__(
        self,
        host: str,
        app: ASGIApp,
        name: str | None = None,
    ) -> None:
        """
        Initialize host route.
        
        Args:
            host: Host pattern (supports wildcards)
            app: ASGI application for this host
            name: Host route name
        """
    
    @property
    def host(self) -> str:
        """Host pattern."""
    
    @property
    def app(self) -> ASGIApp:
        """Application for this host."""
    
    @property
    def name(self) -> str | None:
        """Host route name."""
    
    def matches(self, scope) -> tuple[Match, dict]:
        """Check if host matches request."""
    
    def url_path_for(self, name: str, /, **path_params) -> URLPath:
        """Generate URL path for host route."""
    
    async def handle(self, scope, receive, send) -> None:
        """Handle request for this host."""

Basic Routing

Simple Routes

from starlette.applications import Starlette
from starlette.responses import JSONResponse, PlainTextResponse
from starlette.routing import Route

# Simple function endpoints
async def homepage(request):
    return PlainTextResponse("Hello, world!")

async def about(request):
    return JSONResponse({"page": "about"})

# Route definitions
routes = [
    Route("/", homepage, name="home"),
    Route("/about", about, name="about"),
]

app = Starlette(routes=routes)

HTTP Methods

from starlette.responses import JSONResponse
from starlette.routing import Route

# Multiple HTTP methods
async def user_handler(request):
    if request.method == "GET":
        return JSONResponse({"user": "data"})
    elif request.method == "POST":
        data = await request.json()
        return JSONResponse({"created": data})
    elif request.method == "DELETE":
        return JSONResponse({"deleted": True})

routes = [
    Route("/users", user_handler, methods=["GET", "POST", "DELETE"]),
]

# Method-specific routes
async def get_users(request):
    return JSONResponse({"users": []})

async def create_user(request):
    data = await request.json()
    return JSONResponse({"created": data}, status_code=201)

routes = [
    Route("/users", get_users, methods=["GET"]),
    Route("/users", create_user, methods=["POST"]),
]

Path Parameters

Parameter Types

from starlette.routing import Route
from starlette.responses import JSONResponse

# String parameters (default)
async def user_profile(request):
    user_id = request.path_params["user_id"]  # str
    return JSONResponse({"user_id": user_id})

# Integer parameters  
async def post_detail(request):
    post_id = request.path_params["post_id"]  # int
    return JSONResponse({"post_id": post_id})

# Float parameters
async def coordinates(request):
    lat = request.path_params["lat"]  # float
    lng = request.path_params["lng"]  # float
    return JSONResponse({"lat": lat, "lng": lng})

# UUID parameters
async def resource_by_uuid(request):
    resource_id = request.path_params["resource_id"]  # UUID
    return JSONResponse({"id": str(resource_id)})

# Path parameters (matches any path including /)
async def file_handler(request):
    file_path = request.path_params["file_path"]  # str (can contain /)
    return JSONResponse({"path": file_path})

routes = [
    Route("/users/{user_id}", user_profile),           # String
    Route("/posts/{post_id:int}", post_detail),        # Integer
    Route("/map/{lat:float}/{lng:float}", coordinates), # Float
    Route("/resources/{resource_id:uuid}", resource_by_uuid), # UUID
    Route("/files/{file_path:path}", file_handler),    # Path
]

Complex Path Patterns

# Multiple parameters
async def user_post(request):
    user_id = request.path_params["user_id"]
    post_id = request.path_params["post_id"]
    return JSONResponse({
        "user_id": int(user_id),
        "post_id": int(post_id)
    })

# Optional parameters with query strings
async def search_posts(request):
    user_id = request.path_params.get("user_id")
    query = request.query_params.get("q", "")
    return JSONResponse({
        "user_id": int(user_id) if user_id else None,
        "query": query
    })

routes = [
    Route("/users/{user_id:int}/posts/{post_id:int}", user_post),
    Route("/users/{user_id:int}/posts", search_posts),
    Route("/posts", search_posts),  # user_id will be None
]

WebSocket Routing

from starlette.routing import WebSocketRoute
from starlette.websockets import WebSocket

# Simple WebSocket endpoint
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    await websocket.send_text("Hello WebSocket!")
    await websocket.close()

# WebSocket with path parameters
async def user_websocket(websocket: WebSocket):
    user_id = websocket.path_params["user_id"]
    await websocket.accept()
    await websocket.send_json({
        "type": "connected",
        "user_id": int(user_id)
    })
    
    try:
        while True:
            data = await websocket.receive_json()
            # Echo data back
            await websocket.send_json({
                "type": "echo",
                "data": data,
                "user_id": int(user_id)
            })
    except WebSocketDisconnect:
        print(f"User {user_id} disconnected")

routes = [
    WebSocketRoute("/ws", websocket_endpoint, name="websocket"),
    WebSocketRoute("/ws/user/{user_id:int}", user_websocket, name="user_ws"),
]

Sub-application Mounting

from starlette.applications import Starlette
from starlette.routing import Route, Mount
from starlette.staticfiles import StaticFiles

# API sub-application
async def api_root(request):
    return JSONResponse({"api": "v1"})

async def api_users(request):
    return JSONResponse({"users": []})

api_routes = [
    Route("/", api_root),
    Route("/users", api_users),
]

api_app = Starlette(routes=api_routes)

# Admin sub-application  
async def admin_dashboard(request):
    return JSONResponse({"page": "dashboard"})

admin_routes = [
    Route("/", admin_dashboard),
]

admin_app = Starlette(routes=admin_routes)

# Main application
routes = [
    Route("/", homepage),
    Mount("/api/v1", api_app, name="api"),
    Mount("/admin", admin_app, name="admin"),
    Mount("/static", StaticFiles(directory="static"), name="static"),
]

app = Starlette(routes=routes)

# URLs will be:
# / -> homepage
# /api/v1/ -> api_root  
# /api/v1/users -> api_users
# /admin/ -> admin_dashboard
# /static/* -> static files

Host-based Routing

from starlette.routing import Host

# Different apps for different hosts
main_app = Starlette(routes=[
    Route("/", main_homepage),
])

api_app = Starlette(routes=[
    Route("/", api_root),
    Route("/users", api_users),
])

admin_app = Starlette(routes=[
    Route("/", admin_dashboard),
])

# Host-based routing
routes = [
    Host("api.example.com", api_app, name="api_host"),
    Host("admin.example.com", admin_app, name="admin_host"), 
    Host("{subdomain}.example.com", main_app, name="subdomain_host"),
    Host("example.com", main_app, name="main_host"),
]

app = Starlette(routes=routes)

# Requests route based on Host header:
# Host: api.example.com -> api_app
# Host: admin.example.com -> admin_app  
# Host: blog.example.com -> main_app (subdomain match)
# Host: example.com -> main_app

URL Generation

from starlette.routing import Route
from starlette.datastructures import URLPath

# Named routes for URL generation
routes = [
    Route("/", homepage, name="home"),
    Route("/users/{user_id:int}", user_profile, name="user"),
    Route("/users/{user_id:int}/posts/{post_id:int}", post_detail, name="post"),
]

app = Starlette(routes=routes)

# Generate URLs
home_url = app.url_path_for("home")  
# URLPath("/")

user_url = app.url_path_for("user", user_id=123)
# URLPath("/users/123")

post_url = app.url_path_for("post", user_id=123, post_id=456)  
# URLPath("/users/123/posts/456")

# In request handlers
async def some_view(request):
    # Generate absolute URLs
    home_url = request.url_for("home")
    user_url = request.url_for("user", user_id=123)
    
    return JSONResponse({
        "home": str(home_url),      # "http://example.com/"
        "user": str(user_url),      # "http://example.com/users/123"
    })

# URL generation with mounts
api_routes = [
    Route("/users/{user_id:int}", api_user_detail, name="user_detail"),
]

routes = [
    Mount("/api/v1", Starlette(routes=api_routes), name="api"),
]

app = Starlette(routes=routes)

# Generate mounted URLs
api_user_url = app.url_path_for("api:user_detail", user_id=123)
# URLPath("/api/v1/users/123")

Route Middleware

from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.routing import Route

# Route-specific middleware
class TimingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        start_time = time.time()
        response = await call_next(request)
        process_time = time.time() - start_time
        response.headers["X-Process-Time"] = str(process_time)
        return response

class AuthRequiredMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        if not request.headers.get("authorization"):
            return JSONResponse(
                {"error": "Authentication required"}, 
                status_code=401
            )
        return await call_next(request)

# Apply middleware to specific routes
routes = [
    Route("/", homepage),  # No middleware
    Route("/api/data", api_data, middleware=[
        Middleware(TimingMiddleware),
    ]),
    Route("/admin/users", admin_users, middleware=[
        Middleware(AuthRequiredMiddleware),
        Middleware(TimingMiddleware),
    ]),
]

Router Utilities

Route Matching

from starlette.routing import Match

# Route matching enum
class Match(Enum):
    NONE = 0      # No match
    PARTIAL = 1   # Partial match (for mounts)
    FULL = 2      # Full match

# Custom route matching
def custom_matches(route, scope):
    match, params = route.matches(scope)
    if match == Match.FULL:
        # Route fully matches
        return params
    elif match == Match.PARTIAL:
        # Partial match (mount point)
        return params
    else:
        # No match
        return None

Route Conversion Functions

from starlette.routing import request_response, websocket_session

# Convert function to ASGI application
def sync_endpoint(request):
    # Sync function automatically wrapped
    return JSONResponse({"sync": True})

async def async_endpoint(request):
    # Async function used directly
    return JSONResponse({"async": True})

# Convert WebSocket function
@websocket_session
async def websocket_endpoint(websocket):
    await websocket.accept()
    await websocket.send_text("Hello!")
    await websocket.close()

routes = [
    Route("/sync", sync_endpoint),
    Route("/async", async_endpoint),
    WebSocketRoute("/ws", websocket_endpoint),
]

Route Exceptions

from starlette.routing import NoMatchFound

# Exception raised when URL generation fails
try:
    url = app.url_path_for("nonexistent_route")
except NoMatchFound:
    # Handle missing route
    url = app.url_path_for("home")  # Fallback

The routing system provides flexible URL mapping with support for path parameters, multiple HTTP methods, WebSocket connections, sub-application mounting, and host-based routing, making it suitable for complex web applications.

Install with Tessl CLI

npx tessl i tessl/pypi-starlette

docs

authentication.md

core-application.md

data-structures.md

exceptions-status.md

index.md

middleware.md

requests-responses.md

routing.md

static-files.md

testing.md

websockets.md

tile.json