paint-brush
Apply This Technique To Serve HTML Via Templates With FastAPIby@fastapitutorial
2,182 reads
2,182 reads

Apply This Technique To Serve HTML Via Templates With FastAPI

by FastapiTutorial.comMay 12th, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

We will be using Jinja2 as our templating language. We are using the APIRouter of fastapi.com to serve webapps. We need to implement a navbar and a simple message on our homepage. 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 code base clean from the beginning. This I am doing to follow the DRY(Don't Repeat Yourself) principle.

Company Mentioned

Mention Thumbnail
featured image - Apply This Technique To Serve HTML Via Templates With FastAPI
FastapiTutorial.com HackerNoon profile picture

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 it can also serve webapps. The other thing is if someone hits our home/index endpoint they won't understand anything because we are not rendering any webpage . So, a template is necessary.

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
  ├─core/
  │ └─config.py
  ├─main.py
  ├─requirements.txt
  └─templates/  #new
    ├─components/  #new
    │ └─navbar.html  #new
    ├─general_pages/  #new
    │ └─homepage.html  #new
    └─shared/   #new
      └─base.html   #new

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})
  • 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 learn to ask 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 %} 
  • 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/[email protected]/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/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
    {% block scripts %}
    {% endblock %}


</body>
</html>

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()


# @app.get("/") #remove this, It is no longer needed.
# def hello_api():
#     return {"msg":"Hello API"}

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 

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:

Thats all for this post, It has already become little big! I will continue to add navbar in next post. 

Final git commit : Add support for template response in homepage · nofoobar/JobBoard-Fastapi@e5450e9 (github.com)