Skip to content
OVEX TECH
Education & E-Learning

Build a Dynamic HTML Frontend with FastAPI and Jinja2

Build a Dynamic HTML Frontend with FastAPI and Jinja2

How to Build a Dynamic HTML Frontend with FastAPI and Jinja2

In this tutorial, you’ll learn how to integrate Jinja2 templating into your FastAPI application to serve dynamic HTML pages. Move beyond simple JSON APIs and create user-friendly web interfaces. We’ll cover setting up Jinja2, passing data to templates, using Jinja2’s syntax for logic like loops and conditionals, implementing template inheritance for code reuse, and configuring static file serving for assets like CSS and images.

What You’ll Learn

  • Configure FastAPI to use Jinja2 templates.
  • Create and render HTML templates.
  • Pass dynamic data from your FastAPI backend to templates.
  • Utilize Jinja2’s syntax for loops and conditional statements.
  • Implement template inheritance using a base layout file.
  • Serve static files (CSS, JavaScript, images) with FastAPI.
  • Use `url_for` for generating dynamic URLs in templates.

Prerequisites

  • A working FastAPI application setup (as described in previous tutorials).
  • Basic understanding of HTML and Python.
  • uvicorn installed for development server (optional, `fastapi dev` works too).

Steps to Integrate Jinja2 Templates

1. Project Setup and Imports

First, ensure your FastAPI application is set up. If you installed FastAPI using fastapi[all], Jinja2 is already included. If not, you might need to install it separately with pip install jinja2.

Update your main application file (e.g., main.py) by adding necessary imports:

  1. Import Request from fastapi. Jinja2 templates require the request object.

    from fastapi import Request

  2. Import Jinja2Templates from fastapi.templating.

    from fastapi.templating import Jinja2Templates

  3. Remove the import for HTMLResponse if you were previously using it for raw HTML.

    # Remove: from fastapi.responses import HTMLResponse

2. Create the Templates Directory

By convention, FastAPI looks for templates in a directory named templates at the root of your project. Create this directory if it doesn’t exist.

  1. In your project’s root directory, create a new folder named templates.

    your_project/
    └── templates/
    └── main.py

3. Configure FastAPI to Use Templates

Instantiate the Jinja2Templates class, telling it where to find your template files.

  1. After creating your FastAPI app instance, initialize the templates object.

    app = FastAPI()

    templates = Jinja2Templates(directory='templates')

This templates object will now be used to render your HTML pages.

4. Create Your First HTML Template

Create a simple HTML file inside the templates directory.

  1. Inside the templates folder, create a file named home.html.

    your_project/
    └── templates/
    └── home.html
    └── main.py

  2. Add basic HTML structure to home.html.

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FastAPI Blog</title>
    </head>
    <body>
    <h1>Homepage</h1>
    <p>This is a basic homepage using a template.</p>
    </body>
    </html>

5. Update Your Route to Render the Template

Modify your existing route that previously returned raw HTML to now render the home.html template.

  1. Update the route function in main.py:
    • Add the request: Request parameter to the function signature.
    • Change the return statement to use templates.TemplateResponse.

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

      # Example of your API endpoint (remains the same)
      @app.get("/api/posts")
      def get_posts():
      # ... return JSON posts ...

include_in_schema=False is used here to prevent these HTML-serving routes from appearing in your API documentation (e.g., Swagger UI).

6. Pass Data to Templates

You can pass dynamic data to your templates using a context dictionary as the third argument to TemplateResponse.

  1. Modify your route to include a context dictionary:

    @app.get("/", include_in_schema=False)
    def home(request: Request):
    posts_data = [
    {"id": 1, "title": "First Post", "content": "This is the content of the first post."},
    {"id": 2, "title": "Second Post", "content": "This is the content of the second post."}
    ]
    return templates.TemplateResponse("home.html", {"request": request, "posts": posts_data})

7. Use Jinja2 Syntax in Templates

Jinja2 uses specific syntax for logic and displaying variables:

  • Displaying variables: Use double curly braces {{ variable_name }}.
  • Logic (loops, conditionals): Use curly braces with percent signs {% ... %}.

Update your home.html to display the posts:

  1. Replace the placeholder content in home.html with a Jinja2 loop:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FastAPI Blog</title>
    </head>
    <body>
    <h1>Blog Posts</h1>

    {% for post in posts %}
    <div>
    <h2>{{ post.title }}</h2>
    <p>{{ post.content }}</p>
    </div>
    <hr>
    {% endfor %}
    </body>
    </html>

Expert Note: Jinja2 allows accessing dictionary keys using dot notation (e.g., post.title) for cleaner syntax.

8. Implement Conditional Statements

Use {% if ... %} ... {% else %} ... {% endif %} blocks for conditional logic.

  1. Update the title in home.html to be dynamic:

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% if page_title %}{{ page_title }} - {% endif %}FastAPI Blog</title>
    </head>

  2. Pass the page_title variable from your route:

    @app.get("/", include_in_schema=False)
    def home(request: Request):
    posts_data = [...]
    return templates.TemplateResponse("home.html", {
    "request": request,
    "posts": posts_data,
    "page_title": "Home"
    })

9. Implement Template Inheritance

Template inheritance is crucial for avoiding code duplication. Create a base layout template and extend it in other templates.

  1. Create a layout.html file in the templates directory.

    your_project/
    └── templates/
    ├── home.html
    └── layout.html
    └── main.py

  2. Define a base structure in layout.html with blocks for content that child templates will fill.

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% if page_title %}{{ page_title }} - {% endif %}FastAPI Blog</title>
    {# Link to CSS will be added later #}
    </head>
    <body>
    <header>
    <h1>My Awesome Blog</h1>
    <nav>
    <a href="/">Home</a>
    <a href="/about">About</a>
    </nav>
    </header>

    <main>
    {% block content %}
    {# Child templates will override this block #}
    {% endblock %}
    </main>

    <footer>
    <p>© 2023 My Blog</p>
    </footer>
    </body>
    </html>

  3. Modify home.html to extend layout.html and define the content block.

    {% extends "layout.html" %}

    {% block content %}
    <h1>Blog Posts</h1>

    {% for post in posts %}
    <div>
    <h2>{{ post.title }}</h2>
    <p>{{ post.content }}</p>
    </div>
    <hr>
    {% endfor %}
    {% endblock %}

10. Add Styling with Static Files (CSS & Bootstrap)

To make your site look good, you’ll need to serve static files like CSS. We’ll use Bootstrap for quick styling.

  1. Create a static directory at the root of your project.

    your_project/
    └── static/
    └── css/
    └── main.css
    └── templates/
    ├── home.html
    └── layout.html
    └── main.py

  2. Place your CSS files (e.g., main.css) inside the static/css folder. You can also add folders for js and images.

    Example static/css/main.css:

    body { font-family: sans-serif; margin: 20px; }
    h1 { color: navy; }

  3. Configure FastAPI to serve the static directory.

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

    app = FastAPI()

    # Mount the static files directory
    app.mount("/static", StaticFiles(directory="static"), name="static")

    templates = Jinja2Templates(directory="templates")

    @app.get("/", include_in_schema=False)
    def home(request: Request):
    # ... (route logic as before) ...
    return templates.TemplateResponse("home.html", {"request": request, "posts": posts_data, "page_title": "Home"})

  4. Link to your CSS file in layout.html using the static URL prefix:

    <head>
    ...
    <link rel="stylesheet" href="/static/css/main.css">
    {# For Bootstrap, you might link to a CDN or download it into static/css/ #}
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    </head>

Tip: For Bootstrap, you can either link to a CDN (as shown above) or download the CSS and place it in your static/css folder and link to it locally like /static/css/bootstrap.min.css.

11. Use url_for for Dynamic URLs

Instead of hardcoding URLs (like / or /static/...), use Jinja2’s url_for function for better maintainability.

  1. Update navigation links in layout.html:

    <nav>
    <a href="{{ url_for('home') }}">Home</a>
    <a href="{{ url_for('about') }}">About</a> {# Assumes you'll create an 'about' route named 'about' #}
    </nav>

  2. Explicitly name your routes in main.py to work correctly with url_for.

    @app.get("/", name="home", include_in_schema=False)
    def home(request: Request):
    ...

    @app.get("/api/posts")
    def get_posts():
    ...

    # Example: Add an about page route
    @app.get("/about", name="about", include_in_schema=False)
    def about(request: Request):
    return templates.TemplateResponse("about.html", {"request": request, "page_title": "About Us"})

  3. Update static file references:

    <link rel="stylesheet" href="{{ url_for('static', path='css/main.css') }}">
    <img src="{{ url_for('static', path='images/logo.png') }}" alt="Logo">

Warning: If you use url_for for a route name that doesn’t exist or isn’t explicitly named, it will raise an error. Ensure route names match.

Conclusion

By following these steps, you’ve successfully integrated Jinja2 templating into your FastAPI application, enabling you to serve dynamic HTML content. You can now create rich, user-facing web applications alongside your JSON APIs, leveraging template inheritance and static file serving for a complete web experience.


Source: Python FastAPI Tutorial (Part 2): HTML Frontend for Your API – Jinja2 Templates (YouTube)

Leave a Reply

Your email address will not be published. Required fields are marked *

Written by

John Digweed

1,290 articles

Life-long learner.