A Web-Application Framework for Python Developers
Introduction to FastHTML with an example

As a Python developer developing web frontends for AI applications, my lack of skills in web frontend technologies was always a problem. In-house front-end developers were only sometimes available, and my knowledge was insufficient for the development.
When the Streamlit package became popular, it gave me the opportunity to develop my applications in Python. However, the applications I developed with it never felt like a real web application, and for more complex applications, Streamlit was no longer the ideal choice.
So, the new framework, “FastHTML,” was welcome news for me, and I had to look at it immediately. In this article and its sequels, I would like to present my first experiences in the form of an introduction together with a sample application. The similarity in name to the FastAPI framework for developing REST interfaces in Python is not accidental but intentional. The developers were strongly inspired by FastAPI when designing the system.
Information about FastHTML
The documentation of FastHTML can be found at https://docs.fastht.ml/, and the source code of the open-source project, together with examples, can be found at https://github.com/AnswerDotAI/fasthtml. The framework is very lightweight and realizes its performance scope by building on well-suited, very lightweight packages. Essential technologies are HTMX, which provides AJAX capabilities, web sockets, and more. The minimalist PicoCSS is used as the CSS framework.
Example application
In the following, I would like to present the essential components of FastHTML using an example application. The sample application realizes a simple user authentication and shows a start page after a successful login. We will look at the code of the sample application step by step.
from fasthtml.common import *
import os
from hmac import compare_digest
from dotenv import load_dotenv
load_dotenv()
userpw = os.environ.get("PW")
The first line imports the FastHTML functions; the other imports are necessary for handling environment variables. To keep the application simple, we store the password used for authentication in an environment variable. To check the password, we use compare_digest.
login_redir = RedirectResponse('/login', status_code=303)
def before(req, sess):
auth = req.scope['auth'] = sess.get('auth', None)
if not auth: return login_redir
bware = Beforeware(before, skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', '/login'])
Before loading each page, the system checks whether the user is logged in; if not, the user is redirected to the login page.
app = FastHTML(
before=bware,
hdrs=( picolink, Style(':root { --pico-font-size: 100%; }'))
)
The FastHTML application is defined, and the previously defined function for user verification is integrated. PicoCSS and, if desired, classes that extend or overwrite the default values are added to the header.
rt = app.route
@rt("/login")
def get():
frm = Form(
Input(id='name', placeholder='Name'),
Input(id='pwd', type='password', placeholder='Password'),
Button('login'),
action='/login', method='post')
return Titled("Login", frm)
@dataclass
class Login: name:str; pwd:str
@rt("/login")
def post(login:Login, sess):
if not login.name or not login.pwd: return login_redir
if not compare_digest(userpw.encode("utf-8"), login.pwd.encode("utf-8")):
return login_redir
sess['auth'] = login.name
return RedirectResponse('/', status_code=303)
@app.get("/logout")
def logout(sess):
del sess['auth']
return login_redir
The functions for the Post and Get calls of the individual routes are defined. In our simple example, these are the routes for login and logout. A simple form with fields for username and password is specified and checks whether the password (“PW”) stored in the environment variable has been entered. The username is arbitrary in our example.
During the logout route, the authentication stored in the session is deleted, and the user is logged out.
# Main page
@rt("/")
def get(auth):
title = f"{auth}' is logged in"
top = Grid(H1(title), Div(A('logout', href='/logout'),
style='text-align: right'))
return Title(title), Main(top, P("Some Content"), cls='container')
serve()
The username and some dummy content are displayed on the route to the main page. The example shows that FastHTML provides a function for each HTML tag that generates this tag in the response. The grid function is used for a simple grid layout. The last line starts the FastHTML Server.
After a successful login, the content of the start page is visible. In our example, it includes a title, a logout option, and the page content. See the following illustration.

The complete source code thus results in the following:
from fasthtml.common import *
import os
from hmac import compare_digest
from dotenv import load_dotenv
load_dotenv()
userpw = os.environ.get("PW")
login_redir = RedirectResponse('/login', status_code=303)
def before(req, sess):
auth = req.scope['auth'] = sess.get('auth', None)
if not auth: return login_redir
bware = Beforeware(before, skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css',
'/login'])
app = FastHTML(
before=bware,
hdrs=( picolink, Style(':root { --pico-font-size: 100%; }'))
)
rt = app.route
@rt("/login")
def get():
frm = Form(
Input(id='name', placeholder='Name'),
Input(id='pwd', type='password', placeholder='Password'),
Button('login'),
action='/login', method='post')
return Titled("Login", frm)
@dataclass
class Login: name:str; pwd:str
@rt("/login")
def post(login:Login, sess):
if not login.name or not login.pwd: return login_redir
if not compare_digest(userpw.encode("utf-8"), login.pwd.encode("utf-8")):
return login_redir
sess['auth'] = login.name
return RedirectResponse('/', status_code=303)
@app.get("/logout")
def logout(sess):
del sess['auth']
return login_redir
# Main page
@rt("/")
def get(auth):
title = f"{auth}' is logged in"
top = Grid(H1(title), Div(A('logout', href='/logout'),
style='text-align: right'))
return Title(title), Main(top, P("Some Content"), cls='container')
serve()
This example shows the basic structure and some basic concepts of FastHTML. If the code is in a file named main.py, you can start with Python main.py.
Visit us at DataDrivenInvestor.com
Subscribe to DDIntel here.
Join our creator ecosystem here.
DDI Official Telegram Channel: https://t.me/+tafUp6ecEys4YjQ1