Backend Development 7 min read

How to Render HTML with FastAPI and Jinja2: A Step‑by‑Step Guide

This tutorial walks you through installing FastAPI, setting up a project structure, creating routes that serve Jinja2‑rendered HTML pages, adding static files, building forms, and mastering essential Jinja2 syntax such as variables, conditionals, loops, template inheritance, filters, macros, and security considerations.

Code Mala Tang
Code Mala Tang
Code Mala Tang
How to Render HTML with FastAPI and Jinja2: A Step‑by‑Step Guide

FastAPI can render HTML pages, making it an excellent choice for full‑stack applications that need server‑side HTML rendering.

Install required packages

<code>pip install fastapi uvicorn jinja2</code>

Project structure should look like:

<code>fastapi/
├── main.py
├── templates/
│   └── index.html
└── static/
    └── style.css</code>

Create the application

<code># main.py
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app = FastAPI()

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

# Set template directory
templates = Jinja2Templates(directory="templates")

@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
    return templates.TemplateResponse("index.html", {"request": request, "message": "Hello from FastAPI!"})
</code>

index.html

<code><!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>FastAPI HTML</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
    <h1>{{ message }}</h1>
    <p>This HTML page is rendered by FastAPI using Jinja2.</p>
</body>
</html>
</code>

You can also add CSS styles in the style.css file.

Next, add a new route /form that renders an HTML template containing a simple login form.

Add the following to main.py

<code>from fastapi import Form

@app.get("/form", response_class=HTMLResponse)
async def form_get(request: Request):
    return templates.TemplateResponse("form.html", {"request": request})

@app.post("/form", response_class=HTMLResponse)
async def form_post(request: Request, name: str = Form(...)):
    return templates.TemplateResponse("form.html", {"request": request, "name": name})
</code>

form.html should look like:

<code><!DOCTYPE html>
<html>
<head>
    <title>Form Example</title>
</head>
<body>
    <form method="post">
        <input type="text" name="name" placeholder="Enter your name" required>
        <button type="submit">Submit</button>
    </form>
    {% if name %}
    <p>Hello, {{ name }}!</p>
    {% endif %}
</body>
</html>
</code>

Through the above, you have learned how to render HTML pages and use Jinja2 tags.

Common Jinja2 tag syntax

1. Variable interpolation

<code>&lt;h1&gt;Hello, {{ username }}!&lt;/h1&gt;</code>

2. Conditional statements

<code>{% if username %}
    &lt;p&gt;Welcome, {{ username }}!&lt;/p&gt;
{% else %}
    &lt;p>Please log in.&lt;/p&gt;
{% endif %}</code>

3. Loops

<code>&lt;ul&gt;
{% for item in items %}
    &lt;li&gt;{{ item }}&lt;/li&gt;
{% endfor %}
&lt;/ul&gt;</code>

4. Template inheritance

base.html:

<code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;{% block title %}My Site{% endblock %}&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;header&gt;Header Here&lt;/header&gt;
    &lt;main&gt;{% block content %}{% endblock %}&lt;/main&gt;
    &lt;footer&gt;Footer Here&lt;/footer&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code>

index.html:

<code>{% extends "base.html" %}

{% block title %}Home{% endblock %}

{% block content %}
  &lt;h1&gt;Welcome to Home Page&lt;/h1&gt;
{% endblock %}
</code>

5. Comments

<code>{# This is a comment and will not appear in the rendered HTML #}</code>

6. Filters

<code>&lt;p&gt;{{ username | upper }}&lt;/p&gt;   <!-- outputs uppercase -->
&lt;p&gt;{{ user.age | default(18) }}&lt;/p&gt; <!-- shows 18 if age is missing -->
</code>

7. Macros (reusable snippets)

Define a macro:

<code>{% macro input(name, value='', type='text') %}
  &lt;input type="{{ type }}" name="{{ name }}" value="{{ value }}"&gt;
{% endmacro %}
</code>

Use the macro:

<code>{% from "macros.html" import input %}
&lt;form&gt;
  {{ input('username') }}
&lt;/form&gt;
</code>

Jinja2 and security

Jinja2 automatically escapes HTML to prevent XSS attacks:

<code>name = "<script>alert(1)</script>"
{{ name }}
</code>

The output will be:

<code>&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;</code>

If you need to render raw HTML (only for trusted content), use the |safe filter:

<code>{{ html_snippet | safe }}</code>

Conclusion

By following this guide, you now understand the basic method of combining FastAPI with Jinja2 to render HTML pages, as well as the common Jinja2 syntax and features that can help you build dynamic web applications.

PythonWeb DevelopmentfastapiJinja2HTML rendering
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.