or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdcore-application.mddata-structures.mdexceptions-status.mdindex.mdmiddleware.mdrequests-responses.mdrouting.mdstatic-files.mdtesting.mdwebsockets.md

routing.mddocs/

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.