Building a Simple Blogging Site with Python, FastHTML, and Uvicorn

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:

  1. Set up a virtual environment.
  2. Set up the project structure.
  3. Set up the SQLite database.
  4. Define the blog post model and templates.
  5. Write the FastHTML application.
  6. 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.pymodels.pytemplates.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 the blog_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:

    1. GET /: Displays the list of all blog posts.
    2. GET /create: Displays a form to create a new blog post.
    3. 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

    1. View all posts: Navigate to http://127.0.0.1:8000/ to see the list of blog posts.
    2. 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 to 5000 (or any other port number you prefer).

    Full Command Breakdown:

    • app.main:app: This points Uvicorn to the app object in the main.py file inside the app/ 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.

    Leave a Reply