DEV Community

FastapiTutorial-com
FastapiTutorial-com

Posted on • Updated on

Serving HTML with FastAPI

This is a part of my series on FastAPI. The complete series is available at : https://www.fastapitutorial.com/blog/fastapi-course/

I have a habit of developing monolithic web apps for a long time. So, if I don't see anything on the UI, it doesn't look good to me. So, what I am going to do next is to implement a navbar and a simple message on our homepage. I know FastAPI follows API first approach but just to satisfy my cravings I am going to serve a template in UI. The other thing is if someone hits our home/index endpoint they won't understand anything. So, the template is necessary. The second point I wrote just to justify me to myself !!
We will be using Jinja as our templating language. Before that, we need to make some folders and files. Notice the below folder structure of mine, the names 'apis/', 'templates/' are ending with a '/', so these are folders and others are simple .py or .html files. I have added a comment '#new' for the new files and folders that need to be created.

learning_fastapi/
├─.gitignore
└─backend/
  ├─apis/  #new
  │ └─general_pages/ #new
  │   └─route_homepage.py  #new file
  ├─core/
  │ └─config.py
  ├─main.py
  ├─requirements.txt
  └─templates/  #new
    ├─components/  #new folder
    │ └─navbar.html  #new
    ├─general_pages/  #new folder
    │ └─homepage.html  #new
    └─shared/   #new
      └─base.html   #new
Enter fullscreen mode Exit fullscreen mode

Now, enter the below lines in 'route_homepage.py'.

#route_homepage.py

from fastapi import APIRouter
from fastapi import Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates


templates = Jinja2Templates(directory="templates")
general_pages_router = APIRouter()


@general_pages_router.get("/")
async def home(request: Request):
    return templates.TemplateResponse("general_pages/homepage.html",{"request":request})
Enter fullscreen mode Exit fullscreen mode
  • We imported the necessary modules.
  • We created an object of Jinja2Templates and instantiated it with directory/folder name templates. So, now Jinja2 understands that it has to search for HTML files inside the templates folder.
  • We created an instance of APIRouter named general_pages_router. But why? We could have kept all this code in the main.py file but as our codebase grows we will find it to be messy. So, we are trying to keep our codebase clean from the beginning and so, we are utilizing the APIRouter of fastapi.
  • Next is a home function, we have made it async but don't concentrate too much on it. We will learn it later. It is not necessary for this function. In this function, we are basically capturing the actual request and returning an HTMLResponse with the request in a dictionary. This dictionary is called a context dictionary.
  • Always ask why and also we need to find answers to why. Why are we capturing request and passing it in the context dictionary. The answer lies in request only, If we add a print statement print(dir(request)) ,we see that request has many important attributes like 'user','cookies', 'form', 'get', 'headers', 'path_params', 'query_params', ' 'url','url_for','values' which might be used in templates. E.g. It is very common to use request.user in template file. Next, we need to concentrate on homepage.html. Copy the below code in this file.
{% extends "shared/base.html" %}


{% block title %} 
  <title>Job Board</title>
{% endblock %} 

{% block content %} 
  <div class="container">
    <h1 class="display-4">Find Jobs...</h1>
  </div>
{% endblock %} 
Enter fullscreen mode Exit fullscreen mode
  • I am using template inheritance here. Basically, there is some base.html file that has some empty blocks/space. We are asking jinja to find the base.html file and insert the code in the block of homepage to block inside base.html.
  • But why am I complicating all of this? This I am doing to follow the DRY(Don't Repeat Yourself) principle. There are some common lines which we don't need to write again and again. Consider bootstrap cdn links, we are going to use bootstrap for all the html, So, why to keep these links on all the pages. So, we have base.html which will be shared by other HTML files. This is our base.html file.
<!DOCTYPE html>
<html lang="en-us">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="nofoobar.com" content="Nofoobar">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">

    {% block title %}
    {% endblock %}
</head>

<body>
    {% block content %}
    {% endblock %}

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
    {% block scripts %}
    {% endblock %}


</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Done? No! there are still some things left. One is our main.py file has app, which does not know about all of these. So, we are going to inform our main.py file to include this general_pages_router.

#main.py 

from fastapi import FastAPI
from core.config import settings
from apis.general_pages.route_homepage import general_pages_router


def include_router(app):
    app.include_router(general_pages_router)


def start_application():
    app = FastAPI(title=settings.PROJECT_NAME,version=settings.PROJECT_VERSION)
    include_router(app)
    return app 


app = start_application()

Enter fullscreen mode Exit fullscreen mode

Ok, one last thing, I promise last. We don't have Jinja2, So, add the below line in requirements.txt:

fastapi
uvicorn


#for template  #new
jinja2 
Enter fullscreen mode Exit fullscreen mode

Now, install Jinja2 like with pip install -r requirements.txt. All done, not start the server with uvicorn main:app --reload and visit http://127.0.0.1:8000/. You should see a template response as:
Alt Text

Top comments (0)