FastHTML, a modern Python web framework introduced in July 2024, is a simple yet powerful framework that allows developers to build web applications using pure Python. (No need for complex templating engines). FastHTML is lightweight and easy to work with, making it an excellent choice for small projects like a blogging site. In this guide, we’ll build a very simple blogging application that allows users to create and view blog posts, using FastHTML and SQLite for the backend, and Uvicorn to run the application locally at http://127.0.0.1:8000
.
Project Overview
We’ll follow these steps:
- Set up a virtual environment.
- Set up the project structure.
- Set up the SQLite database.
- Define the blog post model and templates.
- Write the FastHTML application.
- Run the application using Uvicorn.
Step 1: Set Up a Virtual Environment
The first step is to create a virtual environment to isolate the project’s dependencies.
Create a project directory:
mkdir fasthtml_blog
cd fasthtml_blog
Create a virtual environment:
On macOS/Linux:
python3 -m venv venv
source venv/bin/activate
On Windows:
python -m venv venv
venv\Scripts\activate
With your virtual environment activated, you’re now ready to install the necessary packages.
pip install python-fasthtml uvicorn
Step 2: Set Up the Project Structure
Next, we’ll create the directory structure for the blogging application. We’ll need an app/
directory to hold the application files and an SQLite database file for storing blog posts.
We can use a Python script to automatically generate the required directories and files.
Python Script to Create Project Structure
Create a file called create_directory.py and add the following code.
# static file | create_directory.py
import os
# Define the structure
directories = [
'app'
]
files = [
'app/__init__.py',
'app/main.py',
'app/models.py',
'app/templates.py',
'app/database.py'
]
# Create directories
for directory in directories:
os.makedirs(directory, exist_ok=True)
# Create empty files
for file in files:
with open(file, 'w') as f:
pass
print("Directories and files created successfully.")
Run this script to set up the project structure. It will create the necessary directories and empty files (main.py
, models.py
, templates.py
, database_py and __init__.py
).
Your project should now look like this:
fasthtml_blog/
│
├── app/
│ ├── __init__.py
│ ├── database.py
│ ├── main.py
│ ├── models.py
│ ├── templates.py
└── venv/
└── create_directory.py
Step 3: Set Up the SQLite Database
Now, let’s set up the SQLite database to store blog posts. We’ll create a database.py
file inside the app/
directory that manages the connection to the SQLite database and creates the necessary table.
app/database.py
import sqlite3
from contextlib import contextmanager
DATABASE_NAME = 'blog.db'
def create_tables():
with sqlite3.connect(DATABASE_NAME) as conn:
conn.execute('''
CREATE TABLE IF NOT EXISTS blog_posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL
)
''')
@contextmanager
def get_db():
conn = sqlite3.connect(DATABASE_NAME)
try:
yield conn
finally:
conn.commit()
conn.close()
create_tables()
creates theblog_posts
table if it doesn’t already exist.get_db()
is a context manager that provides a connection to the SQLite database.
Step 4: Define the Blog Post Model and Templates
Now that the database is set up, we need a model to represent blog posts and functions to interact with the database. In the models.py
file, we’ll define a BlogPost
class and functions to fetch all posts and create new ones.
app/models.py
from app.database import get_db
class BlogPost:
def __init__(self, id: int, title: str, content: str):
self.id = id
self.title = title
self.content = content
def get_all_posts():
with get_db() as conn:
cursor = conn.execute('SELECT id, title, content FROM blog_posts')
rows = cursor.fetchall()
return [BlogPost(id=row[0], title=row[1], content=row[2]) for row in rows]
def create_post(title: str, content: str):
with get_db() as conn:
conn.execute('INSERT INTO blog_posts (title, content) VALUES (?, ?)', (title, content))
get_all_posts()
fetches all the blog posts from the database.create_post()
inserts a new blog post into the database.
HTML Templates in Python
Next, we’ll create two basic templates for rendering the list of blog posts and a form to create new posts. These will be defined in the templates.py
file.
app/templates.py
def blog_list_template(posts):
return f"""
<html>
<head><title>Blog Posts</title></head>
<body>
<h1>All Blog Posts</h1>
<ul>
{''.join([f'<li>{post.title}</li>' for post in posts])}
</ul>
<a href="/create">Create a new post</a>
</body>
</html>
"""
def create_post_template():
return """
<html>
<head><title>Create Post</title></head>
<body>
<h1>Create a New Blog Post</h1>
<form action="/create" method="post">
<label for="title">Title:</label><br>
<input type="text" id="title" name="title"><br>
<label for="content">Content:</label><br>
<textarea id="content" name="content"></textarea><br>
<input type="submit" value="Submit">
</form>
<a href="/">Back to posts</a>
</body>
</html>
"""
blog_list_template()
renders a list of all blog posts.create_post_template()
provides a form to create a new blog post.
Step 5: Write the FastHTML Application
Now, we’ll tie everything together in the main.py
file. This is where we define the routes and handle requests.
app/main.py
from fasthtml import FastHTML
from app.templates import blog_list_template, create_post_template
from app.models import get_all_posts, create_post
from app.database import create_tables
app = FastHTML()
# Create the database tables before starting the app
create_tables()
@app.route("/")
def index():
posts = get_all_posts()
return blog_list_template(posts)
@app.route("/create", methods=["GET"])
def create_post_get():
return create_post_template()
# Define this as an async function to handle form data properly
@app.route("/create", methods=["POST"])
async def create_post_post(request):
form_data = await request.form() # Await for the form data
title = form_data.get("title")
content = form_data.get("content")
create_post(title, content)
return f"<h2>Post '{title}' created! <a href='/'>Go back</a></h2>"
This file defines three main routes:
- GET
/
: Displays the list of all blog posts. - GET
/create
: Displays a form to create a new blog post. - POST
/create
: Handles form submission to create a new post.
Step 6: Run the Application Using Uvicorn
FastHTML doesn’t automatically start a server. Instead, we’ll use Uvicorn, an ASGI server, to serve the application.
Run the Application
To run the application with Uvicorn, use the following command:
uvicorn app.main:app --reload
Step 7: Test the Blogging Site
- View all posts: Navigate to
http://127.0.0.1:8000/
to see the list of blog posts. - Create a new post: Go to
http://127.0.0.1:8000/create
to add a new blog post. After submitting the form, you’ll be redirected with a success message.
To change the port number from the default 8000
when running your FastHTML app using Uvicorn, you can specify the desired port using the --port
option in the Uvicorn command.
For example, if you want to run your app on port 5000
instead of 8000
, you can use this command:
uvicorn app.main:app --reload --port 5000
In this command:
--port 5000
sets the port to5000
(or any other port number you prefer).
Full Command Breakdown:
app.main:app
: This points Uvicorn to theapp
object in themain.py
file inside theapp/
directory.--reload
: This enables automatic reloading of the server whenever you make code changes.--port 5000
: This specifies the port number to run the application on.
You can replace 5000
with any other port number that you want your application to run on. After running this, your app will be accessible at http://127.0.0.1:5000/
(or the port you specified).
Thank you for following along with this tutorial. We hope you found it helpful and informative. If you have any questions, or if you would like to suggest new Python code examples or topics for future tutorials/articles, please feel free to join and comment. Your feedback and suggestions are always welcome!
You can find the same tutorial on Medium.com.