Routing
HTTP Routing
Starlette has a simple but capable request routing system. A routing table is defined as a list of routes, and passed when instantiating the application.
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
async def homepage(request):
return PlainTextResponse("Homepage")
async def about(request):
return PlainTextResponse("About")
routes = [
Route("/", endpoint=homepage),
Route("/about", endpoint=about),
]
app = Starlette(routes=routes)
The endpoint argument can be one of:
- A regular function or async function, which accepts a single
requestargument and which should return a response. - A class that implements the ASGI interface, such as Starlette's HTTPEndpoint.
Path Parameters
Paths can use URI templating style to capture path components.
Route('/users/{username}', user)
/.
You can use convertors to modify what is captured. The available convertors are:
strreturns a string, and is the default.intreturns a Python integer.floatreturns a Python float.uuidreturn a Pythonuuid.UUIDinstance.pathreturns the rest of the path, including any additional/characters.
Convertors are used by prefixing them with a colon, like so:
Route('/users/{user_id:int}', user)
Route('/floating-point/{number:float}', floating_point)
Route('/uploaded/{rest_of_path:path}', uploaded)
If you need a different converter that is not defined, you can create your own.
See below an example on how to create a datetime convertor, and how to register it:
from datetime import datetime
from starlette.convertors import Convertor, register_url_convertor
class DateTimeConvertor(Convertor):
regex = "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?"
def convert(self, value: str) -> datetime:
return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S")
def to_string(self, value: datetime) -> str:
return value.strftime("%Y-%m-%dT%H:%M:%S")
register_url_convertor("datetime", DateTimeConvertor())
After registering it, you'll be able to use it as:
Route('/history/{date:datetime}', history)
Path parameters are made available in the request, as the request.path_params
dictionary.
async def user(request):
user_id = request.path_params['user_id']
...
Handling HTTP methods
Routes can also specify which HTTP methods are handled by an endpoint:
Route('/users/{user_id:int}', user, methods=["GET", "POST"])
By default function endpoints will only accept GET requests, unless specified.
Submounting routes
In large applications you might find that you want to break out parts of the routing table, based on a common path prefix.
routes = [
Route('/', homepage),
Mount('/users', routes=[
Route('/', users, methods=['GET', 'POST']),
Route('/{username}', user),
])
]
This style allows you to define different subsets of the routing table in different parts of your project.
from myproject import users, auth
routes = [
Route('/', homepage),
Mount('/users', routes=users.routes),
Mount('/auth', routes=auth.routes),
]
You can also use mounting to include sub-applications within your Starlette application. For example...
# This is a standalone static files server:
app = StaticFiles(directory="static")
# This is a static files server mounted within a Starlette application,
# underneath the "/static" path.
routes = [
...
Mount("/static", app=StaticFiles(directory="static"), name="static")
]
app = Starlette(routes=routes)
Reverse URL lookups
You'll often want to be able to generate the URL for a particular route, such as in cases where you need to return a redirect response.
- Signature:
url_for(name, **path_params) -> URL
routes = [
Route("/", homepage, name="homepage")
]
# We can use the following to return a URL...
url = request.url_for("homepage")
URL lookups can include path parameters...
routes = [
Route("/users/{username}", user, name="user_detail")
]
# We can use the following to return a URL...
url = request.url_for("user_detail", username=...)
If a Mount includes a name, then submounts should use a {prefix}:{name}
style for reverse URL lookups.
routes = [
Mount("/users", name="users", routes=[
Route("/", user, name="user_list"),
Route("/{username}", user, name="user_detail")
])
]
# We can use the following to return URLs...
url = request.url_for("users:user_list")
url = request.url_for("users:user_detail", username=...)
Mounted applications may include a path=... parameter.
routes = [
...
Mount("/static", app=StaticFiles(directory="static"), name="static")
]
# We can use the following to return URLs...
url = request.url_for("static", path="/css/base.css")
For cases where there is no request instance, you can make reverse lookups
against the application, although these will only return the URL path.
url = app.url_path_for("user_detail", username=...)
Host-based routing
If you want to use different routes for the same path based on the Host header.
Note that port is removed from the Host header when matching.
For example, Host (host='example.org:3600', ...) will be processed
even if the Host header contains or does not contain a port other than 3600
(example.org:5600, example.org).
Therefore, you can specify the port if you need it for use in url_for.
There are several ways to connect host-based routes to your application
site = Router() # Use eg. `@site.route()` to configure this.
api = Router() # Use eg. `@api.route()` to configure this.
news = Router() # Use eg. `@news.route()` to configure this.
routes = [
Host('api.example.org', api, name="site_api")
]
app = Starlette(routes=routes)
app.host('www.example.org', site, name="main_site")
news_host = Host('news.example.org', news)
app.router.routes.append(news_host)
URL lookups can include host parameters just like path parameters
routes = [
Host("{subdomain}.example.org", name="sub", app=Router(routes=[
Mount("/users", name="users", routes=[
Route("/", user, name="user_list"),
Route("/{username}", user, name="user_detail")
])
]))
]
...
url = request.url_for("sub:users:user_detail", username=..., subdomain=...)
url = request.url_for("sub:users:user_list", subdomain=...)
Route priority
Incoming paths are matched against each Route in order.
In cases where more that one route could match an incoming path, you should take care to ensure that more specific routes are listed before general cases.
For example:
# Don't do this: `/users/me` will never match incoming requests.
routes = [
Route('/users/{username}', user),
Route('/users/me', current_user),
]
# Do this: `/users/me` is tested first.
routes = [
Route('/users/me', current_user),
Route('/users/{username}', user),
]
Working with Router instances
If you're working at a low-level you might want to use a plain Router
instance, rather that creating a Starlette application. This gives you
a lightweight ASGI application that just provides the application routing,
without wrapping it up in any middleware.
app = Router(routes=[
Route('/', homepage),
Mount('/users', routes=[
Route('/', users, methods=['GET', 'POST']),
Route('/{username}', user),
])
])
WebSocket Routing
When working with WebSocket endpoints, you should use WebSocketRoute
instead of the usual Route.
Path parameters, and reverse URL lookups for WebSocketRoute work the the same
as HTTP Route, which can be found in the HTTP Route section above.
from starlette.applications import Starlette
from starlette.routing import WebSocketRoute
async def websocket_index(websocket):
await websocket.accept()
await websocket.send_text("Hello, websocket!")
await websocket.close()
async def websocket_user(websocket):
name = websocket.path_params["name"]
await websocket.accept()
await websocket.send_text(f"Hello, {name}")
await websocket.close()
routes = [
WebSocketRoute("/", endpoint=websocket_index),
WebSocketRoute("/{name}", endpoint=websocket_user),
]
app = Starlette(routes=routes)
The endpoint argument can be one of:
- An async function, which accepts a single
websocketargument. - A class that implements the ASGI interface, such as Starlette's WebSocketEndpoint.