Spaces:
Sleeping
Sleeping
# Routes | |
<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! --> | |
Behaviour in FastHTML apps is defined by routes. The syntax is largely | |
the same as the wonderful [FastAPI](https://fastapi.tiangolo.com/) | |
(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). | |
<div> | |
> **Unfinished** | |
> | |
> 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](../api/core.html#tests) | |
</div> | |
Note that you need to include the types of your parameters, so that | |
[`FastHTML`](https://www.fastht.ml/docs/api/core.html#fasthtml) knows | |
what to pass to your function. Here, we’re just expecting a string: | |
``` python | |
from fasthtml.common import * | |
``` | |
``` python | |
app = FastHTML() | |
@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: | |
``` python | |
from starlette.testclient import TestClient | |
``` | |
``` python | |
client = TestClient(app) | |
r = client.get('/user/Jeremy') | |
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: | |
``` python | |
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`: | |
``` python | |
rt = app.route | |
@rt('/') | |
def post(): return "Going postal!" | |
client.post('/').text | |
``` | |
'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: | |
``` python | |
from functools import wraps | |
def basic_auth(f): | |
@wraps(f) | |
async def wrapper(req, *args, **kwargs): | |
token = req.headers.get("Authorization") | |
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" | |
client.get('/protected', headers={'Authorization': 'abc123'}).text | |
``` | |
'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. | |
``` python | |
def app_beforeware(): | |
print('App level beforeware') | |
app = FastHTML(before=Beforeware(app_beforeware)) | |
client = TestClient(app) | |
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" | |
client.get('/users').text | |
``` | |
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: | |
``` python | |
# books.py | |
books_app, rt = fast_app() | |
books = ['A Guide to FastHTML', 'FastHTML Cookbook', 'FastHTML in 24 Hours'] | |
@rt("/", name="list") | |
def get(): | |
return Titled("Books", *[P(book) for book in books]) | |
``` | |
Let’s mount it in our main module: | |
``` python | |
from books import books_app | |
app, rt = fast_app(routes=[Mount("/books", books_app, name="books")]) | |
@rt("/") | |
def get(): | |
return Titled("Dashboard", | |
P(A(href="/books")("Books")), | |
Hr(), | |
P(A(link=uri("books:list"))("Books")), | |
) | |
serve() | |
``` | |
Line 3 | |
We use `starlette.Mount` to add the route to our routes list. We provide | |
the name of `books` to make discovery and management of the links | |
easier. More on that in items 2 and 3 of this annotations list | |
Line 8 | |
This example link to the books list view is hand-crafted. Obvious in | |
purpose, it makes changing link patterns in the future harder | |
Line 10 | |
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. | |