Python Boilerplate - How to use Tailwind CSS and SQLAlchemy in your FastAPI app

Created on Feb 19, 2023

FastAPI has been adapted for building modern Python applications. Today, you’re going to pair this lightweight performant framework with Jinja2 template engine and a modern CSS framework, Tailwind CSS. You will then use SQLAlchemy ORM for the database.

Tailwind CSS is a utility-first CSS framework which means it has a list of utility CSS classes that you can use to style your HTML elements. You can style the elements inside your HTML file by matching and mixing.

This tutorial will build upon the Todo app backend that we’ve built before. We will refine the backend to adapt to our changes to the templates that we use for the frontend. An excellent choice for the templates in FastAPI would be a Jinja2 template engine. So we will use this engine to make FastAPI render our HTML files. The HTML files will have the CSS styles coming from Tailwind CSS.

At the end of this tutorial, you will have a boilerplate that you can use for your FastAPI app set up for Tailwind CSS for the frontend and SQLAlchemy ORM boilerplate for the database.

Prerequisite

To follow along with this tutorial, a basic knowledge of Python will suffice and a revisit to our FastAPI backend todo app is preferred.

Create your FastAPI app

To create a FastAPI app, you can create a main.py file inside your project directory:

mkdir fastapi-tailwindcss-sqlalchemy && cd $_
touch main.py

Install FastAPI and Uvicorn, the ASGI web server:

pip install fastapi uvicorn

Open your main.py file and add the following minimal working script:

from fastapi import FastAPI


app = FastAPI()

@app.get("/")
async def home():
    return {"message": "hello"}

Now, run your app:

uvicorn main:app --reload

Open your localhost with the 8000 port and you’ll see the JSON response that you’ve returned in the home() function.

Create your Jinja2 template

We want to render an HTML template for our app instead of this dummy JSON. Let’s create a new directory, inside our project directory, and cd into it:

mkdir templates && cd $_

Create a new file base.html and add the following:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Todo</title>
</head>
<body>
	<h1>Todo app</h1>
</body>
</html>

To be able to use Jinja2 template with FastAPI, install the jinja2 library:

pip install jinja2

Now, return to the main.py script and see how we can use this template so that once the home() function is invoked, the base.html template will be rendered.

The main.py script now looks like the following:

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates


app = FastAPI()
templates = Jinja2Templates(directory="templates")


@app.get("/")
async def home(request: Request):
    context = {"request": request}
    return templates.TemplateResponse("base.html", context)

We’ve defined the templates object and used it to return the base.html template. The TemplateResponse class has two arguments, the template and the context. The context is a dictionary that has what should be passed to the Jinja2 template. At least, we would need request which is a Request object coming from the fastapi library.

The base.html template should look like the following:

Let’s move to setting up Tailwind CSS for this project to add more life to it.

Set up Tailwind CSS with FastAPI

You have some options to use Tailwind CSS. You can either use the CDN, which is not recommended for production. That’s because every time you run your app, Tailwind will load every CSS utility and that will make your app slow.

You can also use npm to install the framework, but we’re not going through this route as this post is directed to Python developers and I assume you may not have knowledge of npm or you might not have Node.js installed.

So as you might expect, we will use pip to install TailwindCSS framework. We will install pytailwindcss which requires no installation of Node.js and provides a Tailwind CSS CLI that we can use to interact with the framework.

So let’s install the framework:

pip install pytailwindcss

You can now use the tailwindcss command which invokes the Tailwind CSS CLI. You can run it to download the binary:

tailwindcss

After the download is successful, you should see the help output of the tailwindcss command.

Now, make sure you’re inside the fastapi-tailwindcss-sqlalchemy directory which is the project folder. You can create a new Tailwind CSS project with the init subcommand:

tailwindcss init

A Tailwind CSS config file should be created now inside the project directory called tailwind.config.js. Open that file and modify it to the following:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["templates/*.html"],
  theme: {
    extend: {},
  },
  plugins: [],
}

The content object now refers to all HTML files inside the templates directory in our FastAPI project. This is essential to make Tailwind CSS know where our templates are.

You can leave the terminal that has the backend server running and open a new one. This new terminal will have our frontend updated once we have our Tailwind set up.

Create a new folder styles and inside that folder create a new file main.css which should contain the following:

@tailwind base;
@tailwind components;
@tailwind utilities;

These are CSS directives that Tailwind CSS uses to understand the CSS utilities that we will use in our templates.

Now, create a new folder static that contains another new folder css. Inside the latter, create a new file main.css. We will configure this file so that FastAPI can read it through its static files and be able to apply it to our templates.

You need now to convert the Tailwind CSS directives inside the styles/main.css file to an actual CSS file converted by Tailwind CSS that resides in static/css path:

tailwindcss -i styles/main.css -o static/css/main.css --watch

The --watch option here will watch any change that could happen to the styles/main.css and convert it to the output file in the static directory.

If you run this command, you should see something like the following:

Rebuilding...

Done in 365ms.

This will rebuild every time the input CSS is changed. In our case, we can close the terminal as we will not add any more Tailwind CSS directives to the styles/main.css file.

To mount the static files in our FastAPI project, add the following two lines to the main.py script:

from fastapi.staticfiles import StaticFiles


app.mount("/static", StaticFiles(directory="static"), name="static")

And to refer to that static file in the template, open the base.html template and add the following line inside the head HTML tag:

<link href="{{ url_for('static', path='/css/main.css') }}" rel="stylesheet">

Let’s open Tailwind CSS documentation and head over to the heading section to test out our configuration. You’ll only need to copy and paste the code in there and customize it to your preference. Open the base.html file and change the h1 HTML tag to have the following classes:

<h1 class="font-medium leading-tight text-5xl mt-0 mb-2 text-blue-600">Todo app</h1>

The template now looks more blue as the following:

Let’s add more to our templates and use a navigation bar from Tailwind.

Create a Tailwind navbar

Open your templates directory and create a new template, navbar.html. Copy the Tailwind CSS code block for your desired navbar and paste it to your file:

<nav class="relative w-full flex flex-wrap items-center justify-between py-3 bg-gray-100 text-gray-500 hover:text-gray-700 focus:text-gray-700 shadow-lg">
  <div class="container-fluid w-full flex flex-wrap items-center justify-between px-6">
    <div class="container-fluid">
      <a class="text-xl text-black font-semibold" href="/">Todo</a>
    </div>
  </div>
</nav>

This simplistic navbar would have just a Todo link to the home page. To be able to use this template, you can include it inside the base template. So add the following line inside the body HTML tag before the h1 tag:

{% include "navbar.html" %}

This Jinja2 statement should include that new navbar to the base template rendered by the home() function:

Also add the following after the h1 HTML tag:

{% block content %}
{% endblock content %}

This will help us make use of template inheritance so that every template we create will extend from this base template. We will notice that every content block inside a new template will be replaced in that content block inside the base template.

Now, we’re ready to add CRUD operations to our app except that we will add the models to be able to deal with our database.

Create your database models with SQLAlchemy

As we discussed earlier in our backend tutorial, you need to create a models.py script. You can add the following to it:

from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.engine import URL
from sqlalchemy.orm import declarative_base, sessionmaker
from decouple import config


url = URL.create(
    drivername="postgresql",
    username=config("DB_USERNAME"),
    password=config("DB_PASSWORD"),
    host="localhost",
    database="mydb",
    port=5432
)

engine = create_engine(url)
Session = sessionmaker(bind=engine)
session = Session()

Base = declarative_base()

class Todo(Base):
    __tablename__ = "todos"

    id = Column(Integer, primary_key=True)
    text = Column(String)
    is_done = Column(Boolean, default=False)


Base.metadata.create_all(engine)

Notice that we’ve used python-decouple here to avoid exposing our database username and password instead of hardcoding them. First, you need to install it:

pip install python-decouple

And then you can import the config file and pass the environment variables to it. In our case, we’ve used DB_USERNAME and DB_PASSWORD. These env vars exist on a .env file. Create that file and pass your credentials to each variable:

DB_USERNAME=<your-username>
DB_PASSWORD=<your-password>

Note: If you want to push these changes to a version source control like Git, you need to exclude the .env file from being exposed to the public. You’ll need to include it in the .gitignore file.

Conclusion

We’ve seen a boilerplate to set up Tailwind CSS python package to our FastAPI. We learned about a basic configuration of Jinja2 template to the app. We’ve also known how to set up a basic Todo model through SQLAlchemy ORM.

Next: You’re now ready to build your CRUD functionalities to this FastAPI app using SQLAlchemy ORM for the database, and Jinja2 and Tailwind CSS for the frontend.