Get AI summaries of any video or article — Sign up free
Python FastAPI Tutorial (Part 2): HTML Frontend for Your API - Jinja2 Templates thumbnail

Python FastAPI Tutorial (Part 2): HTML Frontend for Your API - Jinja2 Templates

Corey Schafer·
3 min read

Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Create a `templates/` directory and configure `Jinja2Templates(directory='templates')` so FastAPI can locate template files.

Briefing

FastAPI keeps its JSON API endpoints, but adds human-friendly HTML pages by wiring Jinja2 templates into new “page routes.” The key move is switching the home route from returning a raw HTML string to returning a `TemplateResponse` backed by real template files, then injecting dynamic data through a context dictionary—so the same posts data can power both the browser view and the JSON endpoint.

The tutorial starts from a working baseline: the home route returns a simple `<h1>` string, while `/api/posts` returns a JSON list of posts. To replace the brittle string-building approach, it creates a `templates/` directory (FastAPI convention) and configures the app with a `Jinja2Templates` instance pointing at that folder. A first template, `home.html`, is introduced with basic HTML structure. The home route is updated to accept a `request` parameter (required by Jinja2), and to return `templates.TemplateResponse(request,

Cornell Notes

FastAPI’s JSON endpoints stay intact, while new HTML page routes render the same data using Jinja2 templates. The workflow is: create a `templates/` directory, configure `Jinja2Templates`, update routes to return `TemplateResponse` (including a required `request` argument), and pass dynamic values via a context dictionary. Inside templates, Jinja2 syntax provides `for` loops to iterate over posts, `{{ ... }}` to display variables, and `{% if ... %}...{% else %}...{% endif %}` for conditionals like a dynamic page title. Template inheritance then eliminates duplicated HTML by moving shared layout into `layout.html` and letting child templates override a `content` block. Finally, static assets are served from a mounted `static/` directory and referenced with `url_for` for robust links.

Why does the home route need a `request` parameter when returning a Jinja2 template response?

Jinja2 templates in FastAPI require the incoming `request` object so the template engine can render correctly within FastAPI’s context. In practice, the route signature adds `request: Request`, and the route returns `templates.TemplateResponse(request,

How does Jinja2 render a list of posts inside `home.html`?

The template uses a loop with Jinja2 block syntax: `{% for post in posts %}` ... `{% endfor %}`. Inside the loop, variables are displayed with double curly braces like `{{ post.title }}` and `{{ post.content }}`. Even if `post` is a dictionary, Jinja2 supports dot-notation access (e.g., `post.title` maps to the dictionary key `title`) for cleaner template code.

What problem does template inheritance solve, and how is it implemented here?

Without inheritance, every page would need to repeat the full HTML skeleton (head, navigation, footer). Inheritance fixes this by creating a parent `layout.html` containing the shared structure and a named block such as `{% block content %}{% endblock content %}`. Each child template (like `home.html`) uses `{% extends

How are static files served, and why do `url_for` changes matter?

Static files (CSS, JS, images) are served by mounting a static directory: `app.mount('/static', StaticFiles(directory='static'), name='static')`. Templates then reference assets using `url_for('static', path='css/main.css')` (and similar paths for icons and images). This avoids hardcoded URLs and keeps links working if the mount path changes.

Why did the navigation link for “Home” unexpectedly go to `/post`, and how is it corrected?

`url_for('home')` can resolve to the wrong route when multiple decorators point to the same function and the function name isn’t unique. In this case, the home function had both `/` and `/post` decorators, so `url_for` selected the second route. The fix is to assign explicit route names: `name='home'` for the root decorator and `name='post'` for the `/post` decorator, so `url_for('home')` reliably targets `/`.

Review Questions

  1. What three pieces are required to render a Jinja2 template response in FastAPI (route signature, template selection, and context data)?
  2. How do `{% block %}` and `{% extends %}` work together to reduce duplication across multiple pages?
  3. What’s the difference between using `url_for` for routes versus using it for static assets, and why is it safer than hardcoding paths?

Key Points

  1. 1

    Create a `templates/` directory and configure `Jinja2Templates(directory='templates')` so FastAPI can locate template files.

  2. 2

    Switch from returning raw HTML strings to returning `templates.TemplateResponse`, passing the required `request` plus the template name.

  3. 3

    Inject dynamic values into templates via a context dictionary (e.g., `{'posts': posts}`) and render them with `{{ ... }}`.

  4. 4

    Use Jinja2 control structures—`{% for ... %}` loops and `{% if ... %}` conditionals—to display lists and conditional content like page titles.

  5. 5

    Use template inheritance with a parent `layout.html` and a `{% block content %}` section to avoid repeating headers, navigation, and footers.

  6. 6

    Mount a `static/` directory with `app.mount('/static', StaticFiles(...))` and reference assets using `url_for('static', path='...')`.

  7. 7

    Assign explicit route names when one function has multiple decorators, so `url_for` generates the correct URL (e.g., `name='home'` vs `name='post'`).

Highlights

FastAPI can serve HTML pages without sacrificing the JSON API by adding template-based page routes that reuse the same underlying data.
Jinja2 dot-notation lets templates access dictionary keys cleanly (e.g., `post.title` and `post.content`).
Template inheritance (`layout.html` + `content` block) centralizes shared page structure and makes future pages much easier to maintain.
Mounting `static/` and using `url_for('static', path=...)` prevents broken CSS/image links and keeps asset URLs resilient.
When multiple decorators share a function, explicit route naming is essential to keep `url_for` from pointing navigation to the wrong path.

Topics

Mentioned