0
# Routing System
1
2
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.
3
4
## Router Class
5
6
```python { .api }
7
from starlette.routing import Router, BaseRoute
8
from starlette.middleware import Middleware
9
from starlette.types import ASGIApp, Lifespan
10
from typing import Sequence, Callable, Collection, Any
11
from starlette.requests import Request
12
from starlette.responses import Response
13
from starlette.websockets import WebSocket
14
15
class Router:
16
"""
17
Router handles request routing and dispatch.
18
19
The Router class manages:
20
- Route matching and parameter extraction
21
- URL generation from named routes
22
- Middleware application
23
- Lifespan event handling
24
"""
25
26
def __init__(
27
self,
28
routes: Sequence[BaseRoute] | None = None,
29
redirect_slashes: bool = True,
30
default: ASGIApp | None = None,
31
on_startup: Sequence[Callable] | None = None,
32
on_shutdown: Sequence[Callable] | None = None,
33
lifespan: Lifespan | None = None,
34
middleware: Sequence[Middleware] | None = None,
35
) -> None:
36
"""
37
Initialize router.
38
39
Args:
40
routes: List of route definitions
41
redirect_slashes: Automatically redirect /path/ to /path
42
default: Default ASGI app when no route matches
43
on_startup: Startup event handlers (deprecated)
44
on_shutdown: Shutdown event handlers (deprecated)
45
lifespan: Lifespan context manager
46
middleware: Middleware stack for this router
47
"""
48
49
@property
50
def routes(self) -> list[BaseRoute]:
51
"""List of registered routes."""
52
53
def url_path_for(self, name: str, /, **path_params) -> URLPath:
54
"""Generate URL path for named route."""
55
56
def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
57
"""Mount ASGI application at path."""
58
59
def host(self, host: str, app: ASGIApp, name: str | None = None) -> None:
60
"""Add host-based routing."""
61
62
def add_route(
63
self,
64
path: str,
65
endpoint: Callable[[Request], Awaitable[Response] | Response],
66
methods: Collection[str] | None = None,
67
name: str | None = None,
68
include_in_schema: bool = True,
69
) -> None:
70
"""Add HTTP route."""
71
72
def add_websocket_route(
73
self,
74
path: str,
75
endpoint: Callable[[WebSocket], Awaitable[None]],
76
name: str | None = None,
77
) -> None:
78
"""Add WebSocket route."""
79
80
def add_event_handler(self, event_type: str, func: Callable[[], Any]) -> None:
81
"""Add event handler (deprecated)."""
82
83
async def startup(self) -> None:
84
"""Execute startup handlers."""
85
86
async def shutdown(self) -> None:
87
"""Execute shutdown handlers."""
88
89
async def lifespan(self, scope, receive, send) -> None:
90
"""Handle ASGI lifespan protocol."""
91
```
92
93
## Route Classes
94
95
### HTTP Routes
96
97
```python { .api }
98
from starlette.routing import Route, Match
99
from starlette.middleware import Middleware
100
from starlette.types import Scope, Receive, Send
101
from starlette.datastructures import URLPath
102
from typing import Any, Callable, Collection, Sequence
103
104
class Route:
105
"""
106
HTTP route definition.
107
108
Maps URL patterns to endpoint functions for HTTP requests.
109
"""
110
111
def __init__(
112
self,
113
path: str,
114
endpoint: Callable[..., Any],
115
*,
116
methods: Collection[str] | None = None,
117
name: str | None = None,
118
include_in_schema: bool = True,
119
middleware: Sequence[Middleware] | None = None,
120
) -> None:
121
"""
122
Initialize HTTP route.
123
124
Args:
125
path: URL pattern with optional parameters
126
endpoint: Function or class to handle requests
127
methods: Allowed HTTP methods (default: ["GET"])
128
name: Route name for URL generation
129
include_in_schema: Include in OpenAPI schema
130
middleware: Route-specific middleware
131
"""
132
133
@property
134
def path(self) -> str:
135
"""Route path pattern."""
136
137
@property
138
def endpoint(self) -> Callable:
139
"""Route endpoint function."""
140
141
@property
142
def methods(self) -> set[str]:
143
"""Allowed HTTP methods."""
144
145
@property
146
def name(self) -> str | None:
147
"""Route name."""
148
149
def matches(self, scope: Scope) -> tuple[Match, Scope]:
150
"""Check if route matches request scope."""
151
152
def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:
153
"""Generate URL path for this route."""
154
155
async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:
156
"""Handle matched request."""
157
```
158
159
### WebSocket Routes
160
161
```python { .api }
162
from starlette.routing import WebSocketRoute
163
164
class WebSocketRoute:
165
"""
166
WebSocket route definition.
167
168
Maps URL patterns to WebSocket endpoint functions.
169
"""
170
171
def __init__(
172
self,
173
path: str,
174
endpoint: Callable,
175
name: str | None = None,
176
middleware: Sequence[Middleware] | None = None,
177
) -> None:
178
"""
179
Initialize WebSocket route.
180
181
Args:
182
path: URL pattern with optional parameters
183
endpoint: Function or class to handle WebSocket
184
name: Route name for URL generation
185
middleware: Route-specific middleware
186
"""
187
188
@property
189
def path(self) -> str:
190
"""Route path pattern."""
191
192
@property
193
def endpoint(self) -> Callable:
194
"""WebSocket endpoint function."""
195
196
@property
197
def name(self) -> str | None:
198
"""Route name."""
199
200
def matches(self, scope) -> tuple[Match, dict]:
201
"""Check if route matches WebSocket scope."""
202
203
def url_path_for(self, name: str, /, **path_params) -> URLPath:
204
"""Generate URL path for this route."""
205
206
async def handle(self, scope, receive, send) -> None:
207
"""Handle matched WebSocket connection."""
208
```
209
210
### Mount Points
211
212
```python { .api }
213
from starlette.routing import Mount
214
215
class Mount:
216
"""
217
Mount point for sub-applications.
218
219
Mounts an ASGI application at a path prefix.
220
"""
221
222
def __init__(
223
self,
224
path: str,
225
app: ASGIApp | None = None,
226
routes: Sequence[BaseRoute] | None = None,
227
name: str | None = None,
228
middleware: Sequence[Middleware] | None = None,
229
) -> None:
230
"""
231
Initialize mount point.
232
233
Args:
234
path: Mount path prefix
235
app: ASGI application to mount
236
routes: Routes to mount (alternative to app)
237
name: Mount name for URL generation
238
middleware: Mount-specific middleware
239
"""
240
241
@property
242
def path(self) -> str:
243
"""Mount path prefix."""
244
245
@property
246
def app(self) -> ASGIApp:
247
"""Mounted ASGI application."""
248
249
@property
250
def name(self) -> str | None:
251
"""Mount name."""
252
253
def matches(self, scope) -> tuple[Match, dict]:
254
"""Check if mount matches request scope."""
255
256
def url_path_for(self, name: str, /, **path_params) -> URLPath:
257
"""Generate URL path within mount."""
258
259
async def handle(self, scope, receive, send) -> None:
260
"""Handle request to mounted application."""
261
```
262
263
### Host-based Routing
264
265
```python { .api }
266
from starlette.routing import Host
267
268
class Host:
269
"""
270
Host-based routing.
271
272
Routes requests based on the Host header.
273
"""
274
275
def __init__(
276
self,
277
host: str,
278
app: ASGIApp,
279
name: str | None = None,
280
) -> None:
281
"""
282
Initialize host route.
283
284
Args:
285
host: Host pattern (supports wildcards)
286
app: ASGI application for this host
287
name: Host route name
288
"""
289
290
@property
291
def host(self) -> str:
292
"""Host pattern."""
293
294
@property
295
def app(self) -> ASGIApp:
296
"""Application for this host."""
297
298
@property
299
def name(self) -> str | None:
300
"""Host route name."""
301
302
def matches(self, scope) -> tuple[Match, dict]:
303
"""Check if host matches request."""
304
305
def url_path_for(self, name: str, /, **path_params) -> URLPath:
306
"""Generate URL path for host route."""
307
308
async def handle(self, scope, receive, send) -> None:
309
"""Handle request for this host."""
310
```
311
312
## Basic Routing
313
314
### Simple Routes
315
316
```python { .api }
317
from starlette.applications import Starlette
318
from starlette.responses import JSONResponse, PlainTextResponse
319
from starlette.routing import Route
320
321
# Simple function endpoints
322
async def homepage(request):
323
return PlainTextResponse("Hello, world!")
324
325
async def about(request):
326
return JSONResponse({"page": "about"})
327
328
# Route definitions
329
routes = [
330
Route("/", homepage, name="home"),
331
Route("/about", about, name="about"),
332
]
333
334
app = Starlette(routes=routes)
335
```
336
337
### HTTP Methods
338
339
```python { .api }
340
from starlette.responses import JSONResponse
341
from starlette.routing import Route
342
343
# Multiple HTTP methods
344
async def user_handler(request):
345
if request.method == "GET":
346
return JSONResponse({"user": "data"})
347
elif request.method == "POST":
348
data = await request.json()
349
return JSONResponse({"created": data})
350
elif request.method == "DELETE":
351
return JSONResponse({"deleted": True})
352
353
routes = [
354
Route("/users", user_handler, methods=["GET", "POST", "DELETE"]),
355
]
356
357
# Method-specific routes
358
async def get_users(request):
359
return JSONResponse({"users": []})
360
361
async def create_user(request):
362
data = await request.json()
363
return JSONResponse({"created": data}, status_code=201)
364
365
routes = [
366
Route("/users", get_users, methods=["GET"]),
367
Route("/users", create_user, methods=["POST"]),
368
]
369
```
370
371
## Path Parameters
372
373
### Parameter Types
374
375
```python { .api }
376
from starlette.routing import Route
377
from starlette.responses import JSONResponse
378
379
# String parameters (default)
380
async def user_profile(request):
381
user_id = request.path_params["user_id"] # str
382
return JSONResponse({"user_id": user_id})
383
384
# Integer parameters
385
async def post_detail(request):
386
post_id = request.path_params["post_id"] # int
387
return JSONResponse({"post_id": post_id})
388
389
# Float parameters
390
async def coordinates(request):
391
lat = request.path_params["lat"] # float
392
lng = request.path_params["lng"] # float
393
return JSONResponse({"lat": lat, "lng": lng})
394
395
# UUID parameters
396
async def resource_by_uuid(request):
397
resource_id = request.path_params["resource_id"] # UUID
398
return JSONResponse({"id": str(resource_id)})
399
400
# Path parameters (matches any path including /)
401
async def file_handler(request):
402
file_path = request.path_params["file_path"] # str (can contain /)
403
return JSONResponse({"path": file_path})
404
405
routes = [
406
Route("/users/{user_id}", user_profile), # String
407
Route("/posts/{post_id:int}", post_detail), # Integer
408
Route("/map/{lat:float}/{lng:float}", coordinates), # Float
409
Route("/resources/{resource_id:uuid}", resource_by_uuid), # UUID
410
Route("/files/{file_path:path}", file_handler), # Path
411
]
412
```
413
414
### Complex Path Patterns
415
416
```python { .api }
417
# Multiple parameters
418
async def user_post(request):
419
user_id = request.path_params["user_id"]
420
post_id = request.path_params["post_id"]
421
return JSONResponse({
422
"user_id": int(user_id),
423
"post_id": int(post_id)
424
})
425
426
# Optional parameters with query strings
427
async def search_posts(request):
428
user_id = request.path_params.get("user_id")
429
query = request.query_params.get("q", "")
430
return JSONResponse({
431
"user_id": int(user_id) if user_id else None,
432
"query": query
433
})
434
435
routes = [
436
Route("/users/{user_id:int}/posts/{post_id:int}", user_post),
437
Route("/users/{user_id:int}/posts", search_posts),
438
Route("/posts", search_posts), # user_id will be None
439
]
440
```
441
442
## WebSocket Routing
443
444
```python { .api }
445
from starlette.routing import WebSocketRoute
446
from starlette.websockets import WebSocket
447
448
# Simple WebSocket endpoint
449
async def websocket_endpoint(websocket: WebSocket):
450
await websocket.accept()
451
await websocket.send_text("Hello WebSocket!")
452
await websocket.close()
453
454
# WebSocket with path parameters
455
async def user_websocket(websocket: WebSocket):
456
user_id = websocket.path_params["user_id"]
457
await websocket.accept()
458
await websocket.send_json({
459
"type": "connected",
460
"user_id": int(user_id)
461
})
462
463
try:
464
while True:
465
data = await websocket.receive_json()
466
# Echo data back
467
await websocket.send_json({
468
"type": "echo",
469
"data": data,
470
"user_id": int(user_id)
471
})
472
except WebSocketDisconnect:
473
print(f"User {user_id} disconnected")
474
475
routes = [
476
WebSocketRoute("/ws", websocket_endpoint, name="websocket"),
477
WebSocketRoute("/ws/user/{user_id:int}", user_websocket, name="user_ws"),
478
]
479
```
480
481
## Sub-application Mounting
482
483
```python { .api }
484
from starlette.applications import Starlette
485
from starlette.routing import Route, Mount
486
from starlette.staticfiles import StaticFiles
487
488
# API sub-application
489
async def api_root(request):
490
return JSONResponse({"api": "v1"})
491
492
async def api_users(request):
493
return JSONResponse({"users": []})
494
495
api_routes = [
496
Route("/", api_root),
497
Route("/users", api_users),
498
]
499
500
api_app = Starlette(routes=api_routes)
501
502
# Admin sub-application
503
async def admin_dashboard(request):
504
return JSONResponse({"page": "dashboard"})
505
506
admin_routes = [
507
Route("/", admin_dashboard),
508
]
509
510
admin_app = Starlette(routes=admin_routes)
511
512
# Main application
513
routes = [
514
Route("/", homepage),
515
Mount("/api/v1", api_app, name="api"),
516
Mount("/admin", admin_app, name="admin"),
517
Mount("/static", StaticFiles(directory="static"), name="static"),
518
]
519
520
app = Starlette(routes=routes)
521
522
# URLs will be:
523
# / -> homepage
524
# /api/v1/ -> api_root
525
# /api/v1/users -> api_users
526
# /admin/ -> admin_dashboard
527
# /static/* -> static files
528
```
529
530
## Host-based Routing
531
532
```python { .api }
533
from starlette.routing import Host
534
535
# Different apps for different hosts
536
main_app = Starlette(routes=[
537
Route("/", main_homepage),
538
])
539
540
api_app = Starlette(routes=[
541
Route("/", api_root),
542
Route("/users", api_users),
543
])
544
545
admin_app = Starlette(routes=[
546
Route("/", admin_dashboard),
547
])
548
549
# Host-based routing
550
routes = [
551
Host("api.example.com", api_app, name="api_host"),
552
Host("admin.example.com", admin_app, name="admin_host"),
553
Host("{subdomain}.example.com", main_app, name="subdomain_host"),
554
Host("example.com", main_app, name="main_host"),
555
]
556
557
app = Starlette(routes=routes)
558
559
# Requests route based on Host header:
560
# Host: api.example.com -> api_app
561
# Host: admin.example.com -> admin_app
562
# Host: blog.example.com -> main_app (subdomain match)
563
# Host: example.com -> main_app
564
```
565
566
## URL Generation
567
568
```python { .api }
569
from starlette.routing import Route
570
from starlette.datastructures import URLPath
571
572
# Named routes for URL generation
573
routes = [
574
Route("/", homepage, name="home"),
575
Route("/users/{user_id:int}", user_profile, name="user"),
576
Route("/users/{user_id:int}/posts/{post_id:int}", post_detail, name="post"),
577
]
578
579
app = Starlette(routes=routes)
580
581
# Generate URLs
582
home_url = app.url_path_for("home")
583
# URLPath("/")
584
585
user_url = app.url_path_for("user", user_id=123)
586
# URLPath("/users/123")
587
588
post_url = app.url_path_for("post", user_id=123, post_id=456)
589
# URLPath("/users/123/posts/456")
590
591
# In request handlers
592
async def some_view(request):
593
# Generate absolute URLs
594
home_url = request.url_for("home")
595
user_url = request.url_for("user", user_id=123)
596
597
return JSONResponse({
598
"home": str(home_url), # "http://example.com/"
599
"user": str(user_url), # "http://example.com/users/123"
600
})
601
602
# URL generation with mounts
603
api_routes = [
604
Route("/users/{user_id:int}", api_user_detail, name="user_detail"),
605
]
606
607
routes = [
608
Mount("/api/v1", Starlette(routes=api_routes), name="api"),
609
]
610
611
app = Starlette(routes=routes)
612
613
# Generate mounted URLs
614
api_user_url = app.url_path_for("api:user_detail", user_id=123)
615
# URLPath("/api/v1/users/123")
616
```
617
618
## Route Middleware
619
620
```python { .api }
621
from starlette.middleware import Middleware
622
from starlette.middleware.base import BaseHTTPMiddleware
623
from starlette.routing import Route
624
625
# Route-specific middleware
626
class TimingMiddleware(BaseHTTPMiddleware):
627
async def dispatch(self, request, call_next):
628
start_time = time.time()
629
response = await call_next(request)
630
process_time = time.time() - start_time
631
response.headers["X-Process-Time"] = str(process_time)
632
return response
633
634
class AuthRequiredMiddleware(BaseHTTPMiddleware):
635
async def dispatch(self, request, call_next):
636
if not request.headers.get("authorization"):
637
return JSONResponse(
638
{"error": "Authentication required"},
639
status_code=401
640
)
641
return await call_next(request)
642
643
# Apply middleware to specific routes
644
routes = [
645
Route("/", homepage), # No middleware
646
Route("/api/data", api_data, middleware=[
647
Middleware(TimingMiddleware),
648
]),
649
Route("/admin/users", admin_users, middleware=[
650
Middleware(AuthRequiredMiddleware),
651
Middleware(TimingMiddleware),
652
]),
653
]
654
```
655
656
## Router Utilities
657
658
### Route Matching
659
660
```python { .api }
661
from starlette.routing import Match
662
663
# Route matching enum
664
class Match(Enum):
665
NONE = 0 # No match
666
PARTIAL = 1 # Partial match (for mounts)
667
FULL = 2 # Full match
668
669
# Custom route matching
670
def custom_matches(route, scope):
671
match, params = route.matches(scope)
672
if match == Match.FULL:
673
# Route fully matches
674
return params
675
elif match == Match.PARTIAL:
676
# Partial match (mount point)
677
return params
678
else:
679
# No match
680
return None
681
```
682
683
### Route Conversion Functions
684
685
```python { .api }
686
from starlette.routing import request_response, websocket_session
687
688
# Convert function to ASGI application
689
def sync_endpoint(request):
690
# Sync function automatically wrapped
691
return JSONResponse({"sync": True})
692
693
async def async_endpoint(request):
694
# Async function used directly
695
return JSONResponse({"async": True})
696
697
# Convert WebSocket function
698
@websocket_session
699
async def websocket_endpoint(websocket):
700
await websocket.accept()
701
await websocket.send_text("Hello!")
702
await websocket.close()
703
704
routes = [
705
Route("/sync", sync_endpoint),
706
Route("/async", async_endpoint),
707
WebSocketRoute("/ws", websocket_endpoint),
708
]
709
```
710
711
### Route Exceptions
712
713
```python { .api }
714
from starlette.routing import NoMatchFound
715
716
# Exception raised when URL generation fails
717
try:
718
url = app.url_path_for("nonexistent_route")
719
except NoMatchFound:
720
# Handle missing route
721
url = app.url_path_for("home") # Fallback
722
```
723
724
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.