# FastHTML By Example
This tutorial provides an alternate introduction to FastHTML by building
out example applications. We also illustrate how to use FastHTML
foundations to create custom web apps. Finally, this document serves as
minimal context for a LLM to turn it into a FastHTML assistant.
Let’s get started.
## FastHTML Basics
FastHTML is *just Python*. You can install it with
`pip install python-fasthtml`. Extensions/components built for it can
likewise be distributed via PyPI or as simple Python files.
The core usage of FastHTML is to define routes, and then to define what
to do at each route. This is similar to the
[FastAPI](https://fastapi.tiangolo.com/) web framework (in fact we
implemented much of the functionality to match the FastAPI usage
examples), but where FastAPI focuses on returning JSON data to build
APIs, FastHTML focuses on returning HTML data.
Here’s a simple FastHTML app that returns a “Hello, World” message:
``` python
from fasthtml.common import FastHTML, serve
app = FastHTML()
@app.get("/")
def home():
return "
Hello, World
"
serve()
```
To run this app, place it in a file, say `app.py`, and then run it with
`python app.py`.
INFO: Will watch for changes in these directories: ['/home/jonathan/fasthtml-example']
INFO: Uvicorn running on http://127.0.0.1:5001 (Press CTRL+C to quit)
INFO: Started reloader process [871942] using WatchFiles
INFO: Started server process [871945]
INFO: Waiting for application startup.
INFO: Application startup complete.
If you navigate to in a browser, you’ll see your
“Hello, World”. If you edit the `app.py` file and save it, the server
will reload and you’ll see the updated message when you refresh the page
in your browser.
## Constructing HTML
Notice we wrote some HTML in the previous example. We don’t want to do
that! Some web frameworks require that you learn HTML, CSS, JavaScript
AND some templating language AND python. We want to do as much as
possible with just one language. Fortunately, the Python module
[fastcore.xml](https://fastcore.fast.ai/xml.html) has all we need for
constructing HTML from Python, and FastHTML includes all the tags you
need to get started. For example:
``` python
from fasthtml.common import *
page = Html(
Head(Title('Some page')),
Body(Div('Some text, ', A('A link', href='https://example.com'), Img(src="https://placehold.co/200"), cls='myclass')))
print(to_xml(page))
```
!doctype>
Some page
If that `import *` worries you, you can always import only the tags you
need.
FastHTML is smart enough to know about fastcore.xml, and so you don’t
need to use the `to_xml` function to convert your FT objects to HTML.
You can just return them as you would any other Python object. For
example, if we modify our previous example to use fastcore.xml, we can
return an FT object directly:
``` python
from fasthtml.common import *
app = FastHTML()
@app.get("/")
def home():
page = Html(
Head(Title('Some page')),
Body(Div('Some text, ', A('A link', href='https://example.com'), Img(src="https://placehold.co/200"), cls='myclass')))
return page
serve()
```
This will render the HTML in the browser.
For debugging, you can right-click on the rendered HTML in the browser
and select “Inspect” to see the underlying HTML that was generated.
There you’ll also find the ‘network’ tab, which shows you the requests
that were made to render the page. Refresh and look for the request to
`127.0.0.1` - and you’ll see it’s just a `GET` request to `/`, and the
response body is the HTML you just returned.
> **Live Reloading**
>
> You can also enable [live reloading](../ref/live_reload.ipynb) so you
> don’t have to manually refresh your browser to view updates.
You can also use Starlette’s `TestClient` to try it out in a notebook:
``` python
from starlette.testclient import TestClient
client = TestClient(app)
r = client.get("/")
print(r.text)
```
Some page
FastHTML wraps things in an Html tag if you don’t do it yourself (unless
the request comes from htmx, in which case you get the element
directly). See [FT objects and HTML](#ft-objects-and-html) for more on
creating custom components or adding HTML rendering to existing Python
objects. To give the page a non-default title, return a Title before
your main content:
``` python
app = FastHTML()
@app.get("/")
def home():
return Title("Page Demo"), Div(H1('Hello, World'), P('Some text'), P('Some more text'))
client = TestClient(app)
print(client.get("/").text)
```
!doctype>
Page Demo
Hello, World
Some text
Some more text
We’ll use this pattern often in the examples to follow.
## Defining Routes
The HTTP protocol defines a number of methods (‘verbs’) to send requests
to a server. The most common are GET, POST, PUT, DELETE, and HEAD. We
saw ‘GET’ in action before - when you navigate to a URL, you’re making a
GET request to that URL. We can do different things on a route for
different HTTP methods. For example:
``` python
@app.route("/", methods='get')
def home():
return H1('Hello, World')
@app.route("/", methods=['post', 'put'])
def post_or_put():
return "got a POST or PUT request"
```
This says that when someone navigates to the root URL “/” (i.e. sends a
GET request), they will see the big “Hello, World” heading. When someone
submits a POST or PUT request to the same URL, the server should return
the string “got a post or put request”.
> **Test the POST request**
>
> You can test the POST request with
> `curl -X POST http://127.0.0.1:8000 -d "some data"`. This sends some
> data to the server, you should see the response “got a post or put
> request” printed in the terminal.
There are a few other ways you can specify the route+method - FastHTML
has `.get`, `.post`, etc. as shorthand for
`route(..., methods=['get'])`, etc.
``` python
@app.get("/")
def my_function():
return "Hello World from a GET request"
```
Or you can use the `@rt` decorator without a method but specify the
method with the name of the function. For example:
``` python
rt = app.route
@rt("/")
def post():
return "Hello World from a POST request"
```
``` python
client.post("/").text
```
'Hello World from a POST request'
You’re welcome to pick whichever style you prefer. Using routes lets you
show different content on different pages - ‘/home’, ‘/about’ and so on.
You can also respond differently to different kinds of requests to the
same route, as shown above. You can also pass data via the route:
## `@app.get`
``` python
@app.get("/greet/{nm}")
def greet(nm:str):
return f"Good day to you, {nm}!"
client.get("/greet/Dave").text
```
'Good day to you, Dave!'
## `@rt`
``` python
@rt("/greet/{nm}")
def get(nm:str):
return f"Good day to you, {nm}!"
client.get("/greet/Dave").text
```
'Good day to you, Dave!'
More on this in the [More on Routing and Request
Parameters](#more-on-routing-and-request-parameters) section, which goes
deeper into the different ways to get information from a request.
## Styling Basics
Plain HTML probably isn’t quite what you imagine when you visualize your
beautiful web app. CSS is the go-to language for styling HTML. But
again, we don’t want to learn extra languages unless we absolutely have
to! Fortunately, there are ways to get much more visually appealing
sites by relying on the hard work of others, using existing CSS
libraries. One of our favourites is [PicoCSS](https://picocss.com/). A
common way to add CSS files to web pages is to use a
[``](https://www.w3schools.com/tags/tag_link.asp) tag inside your
[HTML header](https://www.w3schools.com/tags/tag_header.asp), like this:
``` html
...
```
For convenience, FastHTML already defines a Pico component for you with
`picolink`:
``` python
print(to_xml(picolink))
```