Understanding FastHTML Routes, Requests, and Redirects

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.

Setup

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.

Defining a Homepage

@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.

Defining a Route With a Parameter

@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.

Calling the Route Handler as a Function

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.

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.

Implementing a Redirect

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.

Making a Request and Inspecting the Redirect's Response

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.

Making a Request that Follows the Redirect

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.

Wrapping Up

We've seen how FastHTML makes it easy to create routes and handle HTTP requests. We covered:

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.