Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.


Serving HTML pages

Now that we know how to return JSON, we’ll see how to return HTML

one possible architecture for a web application (using Server Side Rendering)

one possible architecture for a web application (using Server Side Rendering)

We’ll focus on Jinja2 templates here, which is the most common solution in the Python world. The general principles apply to other template engines as well.


Static files vs templates

Focusing on the middle box in the above diagram, 2 types of files can be served:

Static responses

content depending on nothing
just files served as-is
e.g. images, fonts, css, js, etc.

Dynamic responses

content depending on external data e.g. product search, user profile

obviously the most common case


Repo structure

Before we dig in, this is not imposed by the framework, but we usually find a repo structure like this:

./
├── main.py                   # FastAPI app
├── templates/                # Jinja2 templates
│   ├── base.html
│   └── users/
│       └── profile.html
├── static/                   # CSS-JS-fonts-images
└── config.py                 # settings

Program 1:Usual repo layout for a FastAPI app


Template engines



A template engine let us combine:

  • an HTML skeleton

  • variables injected from Python
    inside the skeleton

Several technologies/solutions:

Jinja2, Pug, Mustache, Ejs

We’ll briefly describe how to leverage Jinja2 from a FastAPI app,
and see also a bit more advanced features, like loops, conditions, etc...


Example: the Jinja2 template

For starters, With this input file templates/hello.html:

1
2
3
4
5
6
<!DOCTYPE html>
<html>
  <body>
    <h1>Good morning {{ name }}!</h1>
  </body>
</html>

the part {{ name }} is a variable placeholder
it means jinja will replace it with the value of the variable name provided by Python

now, let’s see how to do that in practical terms


Example: the Python code

Here’s the corresponding code on the Python side:

1
2
3
4
5
6
7
8
9
10
11
12
13
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse

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

@app.get("/hello/{name}", response_class=HTMLResponse)
def hello(request: Request, name: str):
    return templates.TemplateResponse(
      "hello.html",
      {"request": request, "name": name}
    )

➡️ When a user visits e.g. /hello/Alice, they see Good morning Alice! rendered as HTML


Jinja2 features


Variable substitution

To display the content of a variable in HTML, you need to surround it with double braces in HTML code.

<div>Hello {{ name }}</div>

Jinja will evaluate the expression inside {{ ... }} and replace it with the corresponding value; so if name = "Alice" in Python, the rendered HTML will be

<div>Hello Alice</div>

Conditional blocks

To choose whether to display a part of the HTML page
you can use branches of type {% if %} {% else %} {% endif %}
The syntax is as follows

1
2
3
4
5
6
7
{% if a_condition %}
<div>some html mess</div>
{% elif another_condition %}
<div>another html jumble</div>
{% else %}
<div>the default html</div>
{% endif %}

Note Python’s None becomes none in Jinja2


For loops

The main interest being dynamic table display.
{% for %} loops in Jinja2 allow you to iterate over any iterable Python object
The syntax is as follows

1
2
3
{% for x in my_list %}
<div>Iteration {{ x }}</div>
{% endfor %}

Dictionary access

if x is itself a dictionary, we can access its keys/values via e.g. x.name or x['name'], the first being generally more convenient

1
2
3
4
5
6
7
{% for user in users %}
<div>Iteration
  {{ x.name }}
  or also
  {x['age']}
</div>
{% endfor %}

see python/jinja-demo.py for an executable example


Plenty of other cool stuff

We’ve skimmed over Jinja’s basic features but there are lots of advanced super practical things

Please refer to the official documentation for more details

Non-exhaustive list:


Difference API / Templates

👉 FastAPI can do both depending on needs:

One of the reasons why it’s helpful to expose the API separately is that the data becomes available to other clients - mobile apps, third-party services, etc... - that would otherwise need to reverse-engineer the HTML pages to extract the data !


Optional part



The rest of this notebook is optional

It deals with more general information about current trends in web development

It’s clearly not crucial for beginners, feel free to skip it !


Dynamic page: CSR vs SSR

Now, more generally for dynamic pages, two approaches exist



Client Side Rendering
vs
Server Side Rendering

What we’ve just seen - using templates - belongs in the SSR category


SSR (Server-Side Rendering)

The server produces complete HTML for each request (e.g., templating).

Pros:

  • SEO (search engines see the content directly).

  • Very fast first page load.

Cons:

  • each interaction often requires reloading a page.


CSR (Client-Side Rendering)

Pros:

  • Smooth user experience
    (SPA-type app).

  • Good backend (API) / frontend (JS) separation.

Cons:

  • Longer initial load time.

  • More complex SEO.


ISR - Hybrid approaches

Bleeding edge technologies (e.g., Next.js, Nuxt) tend to mix both approaches:

this paradigm is sometimes called ISR (Incremental Static Regeneration)


CSR vs SSR summary

Both modes have advantages and disadvantages, but roughly:

CSR is cool for

Having pages with lots of interaction,
especially when you’re more into web app than website

SSR is good for

speeding up the initial loading of your site, if you have little user interaction,
if you want to optimize your natural search engine ranking.

And from a very pragmatic point of view, this may also depend on your comfort level programming in Python or Javascript 😉


When to use what


Conclusion

FastAPI is not limited to APIs: it can also serve dynamic HTML templates.
The choice between static, dynamic with templates, SSR or CSR depends on the type of application and needs (SEO, interactivity, performance).

👉 In an educational context, starting with Jinja2 templates is ideal for understanding server-side page generation before exploring modern front-end architectures.