from fasthtml.common import *
Routes
Behaviour in FastHTML apps is defined by routes. The syntax is largely the same as the wonderful FastAPI (which is what you should be using instead of this if you’re creating a JSON service. FastHTML is mainly for making HTML web apps, not APIs).
We haven’t yet written complete documentation of all of FastHTML’s routing features – until we add that, the best place to see all the available functionality is to look over the tests
Note that you need to include the types of your parameters, so that FastHTML
knows what to pass to your function. Here, we’re just expecting a string:
= FastHTML()
app
@app.get('/user/{nm}')
def get_nm(nm:str): return f"Good day to you, {nm}!"
Normally you’d save this into a file such as main.py, and then run it in uvicorn
using:
uvicorn main:app
However, for testing, we can use Starlette’s TestClient
to try it out:
from starlette.testclient import TestClient
= TestClient(app)
client = client.get('/user/Jeremy')
r r
<Response [200 OK]>
TestClient uses httpx
behind the scenes, so it returns a httpx.Response
, which has a text
attribute with our response body:
r.text
'Good day to you, Jeremy!'
In the previous example, the function name (get_nm
) didn’t actually matter – we could have just called it _
, for instance, since we never actually call it directly. It’s just called through HTTP. In fact, we often do call our functions _
when using this style of route, since that’s one less thing we have to worry about, naming.
An alternative approach to creating a route is to use app.route
instead, in which case, you make the function name the HTTP method you want. Since this is such a common pattern, you might like to give a shorter name to app.route
– we normally use rt
:
= app.route
rt
@rt('/')
def post(): return "Going postal!"
'/').text client.post(
'Going postal!'
Route-specific functionality
FastHTML supports custom decorators for adding specific functionality to routes. This allows you to implement authentication, authorization, middleware, or other custom behaviors for individual routes.
Here’s an example of a basic authentication decorator:
from functools import wraps
def basic_auth(f):
@wraps(f)
async def wrapper(req, *args, **kwargs):
= req.headers.get("Authorization")
token if token == 'abc123':
return await f(req, *args, **kwargs)
return Response('Not Authorized', status_code=401)
return wrapper
@app.get("/protected")
@basic_auth
async def protected(req):
return "Protected Content"
'/protected', headers={'Authorization': 'abc123'}).text client.get(
'Protected Content'
The decorator intercepts the request before the route function executes. If the decorator allows the request to proceed, it calls the original route function, passing along the request and any other arguments.
One of the key advantages of this approach is the ability to apply different behaviors to different routes. You can also stack multiple decorators on a single route for combined functionality.
def app_beforeware():
print('App level beforeware')
= FastHTML(before=Beforeware(app_beforeware))
app = TestClient(app)
client
def route_beforeware(f):
@wraps(f)
async def decorator(*args, **kwargs):
print('Route level beforeware')
return await f(*args, **kwargs)
return decorator
def second_route_beforeware(f):
@wraps(f)
async def decorator(*args, **kwargs):
print('Second route level beforeware')
return await f(*args, **kwargs)
return decorator
@app.get("/users")
@route_beforeware
@second_route_beforeware
async def users():
return "Users Page"
'/users').text client.get(
App level beforeware
Route level beforeware
Second route level beforeware
'Users Page'
This flexiblity allows for granular control over route behaviour, enabling you to tailor each endpoint’s functionality as needed. While app-level beforeware remains useful for global operations, decorators provide a powerful tool for route-specific customization.
Combining Routes
Sometimes a FastHTML project can grow so weildy that putting all the routes into main.py
becomes unweildy. Or, we install a FastHTML- or Starlette-based package that requires us to add routes.
First let’s create a books.py
module, that represents all the user-related views:
# books.py
= fast_app()
books_app, rt
= ['A Guide to FastHTML', 'FastHTML Cookbook', 'FastHTML in 24 Hours']
books
@rt("/", name="list")
def get():
return Titled("Books", *[P(book) for book in books])
Let’s mount it in our main module:
from books import app as books_app
1= fast_app(routes=[Mount("/books", books_app, name="books")])
app, rt
@rt("/")
def get():
return Titled("Dashboard",
2="/books")("Books")),
P(A(href
Hr(),3=uri("books:list"))("Books")),
P(A(link
)
serve()
- 1
-
We use
starlette.Mount
to add the route to our routes list. We provide the name ofbooks
to make discovery and management of the links easier. More on that in items 2 and 3 of this annotations list - 2
- This example link to the books list view is hand-crafted. Obvious in purpose, it makes changing link patterns in the future harder
- 3
- This example link uses the named URL route for the books. The advantage of this approach is it makes management of large numbers of link items easier.