In this tutorial we'll look at the simplest routes and route handlers you can create with FastHTML. We'll define the handlers as little functions, and then call them as we would any other Python function. After that, we'll make simple GET requests to a simple index route/handler, a parameterized one, and a parameterized one with a redirect.
from fasthtml.common import *
app,rt = fast_app(pico=False)
We start with a FastHTML app as usual. fast_app
is a convenience wrapper for FastHTML
with some nice defaults.
cli = Client(app) cli
We set up an HTTP client to make requests with. Client
is defined in fasthtml.core
and wraps httpx's AsyncClient
.
@rt def index(): return Titled("My Homepage")
Here's a really simple index route handler, to give us something to request. @rt
is my favorite way to define routes. It makes the index route in particular quick to type out fast because you don't even need a route string.
r = cli.get('/') r
We requested that page and got a successful 200 response with our client!
r.text
The response contains this text. If you look closely, you'll see it's a full HTML page containing <h1>My Homepage</h1>
toward the end.
r.headers
These are the HTTP response headers.
@rt('/pages/{pagename}') def page(pagename:str): return Titled(pagename)
Here you see a route defined with @rt
plus a parameterized route string.
The string value of pagename
from the route turns into the pagename
argument to the page
function.
Then it's used in the function definition any way we want. For simple examples like this, I use Titled
to create quick web pages with the string used twice: as a <title>
and <h1>
element.
heypage = page("HEYHEYHEYHEY") heypage
You can call a route handler function like page("HEYHEYHEYHEY")
manually, like you would any other Python function. This can be good when you want to make sure your function behaves correctly.
to_xml(heypage)
When you pass the returned value into to_xml
, you can see that you only have a subset of an HTML page. When you call a route handler function manually, you're not making a full HTTP request.
rp = cli.get('/pages/HEYHEYHEYHEY') rp
This is much more like typing example.com/pages/HEYHEYHEYHEY
in your browser. You get not just what the function returns, but a whole HTTP request-response round trip.
rp.text
You can inspect the response text like you would the HTTP response of any site. Now you see why I capitalized pagename
and made it so long and obvious. That makes it easy to spot in the 2 places it shows up in the response string: in the title and in the h1 within the main element.
rp.headers
The response has HTTP response headers.
Now imagine the above route had an old location that we needed to redirect from.
@rt('/pageswasherebefore/{pagename}') def old_page(pagename:str): return Redirect(f"/pages/{pagename}")
Here we define a second route with the old URL path string. It returns a redirect pointing to the new location. The redirect uses the Redirect
class from fasthtml.core.
rop = cli.get('/pageswasherebefore/HIHIHIHIHIHIHIHI') rop
We can make a GET request with our HTTP client the way we did before, and assign the returned response object to a variable.
rop.headers
Looking at the HTTP response headers, we now see a location. That's the new location you're getting redirected to, not the old one.
rop.text
And there's no response text content, which matches the length of 0 that we saw in the headers.
rop2 = cli.get('/pageswasherebefore/HIHIHIHIHIHIHIHI', follow_redirects=True) rop2
rop2.headers
Now we see a 200 response, content, and no location!
rop2.text
This looks like the HTTP response for the new route, which is what we want to see.
We've seen how FastHTML makes it easy to create routes and handle HTTP requests. We covered:
@rt
@rt('/pages/{pagename}')
Client
What I love here is how much FastHTML development feels so much like regular Python development. You can call handlers like normal functions to make sure they work as expected, and they still work great as full web endpoints.