File size: 5,853 Bytes
036b3a6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# 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.