FastAPI Flashing Message like Flask

FastAPI is an ASGI framework for Python. Nowadays FastAPI is used a lot for creating fast, self-documented APIs. It also gives flexibility for Python programmers to use modern Python syntax like the type hinting. FastAPI integration with the template engine. But the problem is we cannot use flash messages like Flask. It reduces interactivity of template and hence we will be forced to use frontend frameworks like React, Vue, Angular etc. But for me, these front end frameworks is a pain. So I asked how to flash messages from FastAPI like flask in each online forums. But no one didn't help me. Finally, I found some solution. I belives I have to share that solution.

Actually, I got this idea from starlette-core. I made some changes in the source code and made it possible. Necessary packages to install:

requirements.txt

aiofiles==0.7.0
fastapi==0.70.0
itsdangerous==2.0.1
Jinja2==3.0.2
python-multipart==0.0.5
uvicorn==0.15.0

Let us go through our code:

from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates
from starlette.middleware import Middleware
from starlette.middleware.sessions import SessionMiddleware
import typing

In the above snippet, we are importing the necessary packages. Middleware and SessionMiddleware are imported from Starlette since we are keeping flashed messages in the session.

def flash(request: Request, message: typing.Any, category: str = "primary") -> None:
    if "_messages" not in request.session:
        request.session["_messages"] = []
    request.session["_messages"].append({"message": message, "category": category})


def get_flashed_messages(request: Request):
    print(request.session)
    return request.session.pop("_messages") if "_messages" in request.session else []

The flash method is used to save the message in session. The category is by default primary colour. The category is used to show specific colours according to warning, info, danger etc. in order to get more interactivity through these CSS classes. If you are familiar with CSS kits like bootstrap you will get what I am talking about.

The get_flashed_messages method is used to get flashed messages in the template. Here we are just pops out the _messages from the session.

middleware = [
    Middleware(SessionMiddleware, secret_key='super-secret')
]
app = FastAPI(middleware=middleware)

In order to use session middleware, we have to pass the middleware to our FastAPI app. It is done on the above snippet.

app.mount("/static/", StaticFiles(directory='static', html=True), name="static")

templates = Jinja2Templates(directory="templates")
templates.env.globals['get_flashed_messages'] = get_flashed_messages

We have to now bind the static directory to our FastAPI application. Also, we have to pass the path to the template directory to Jinja2Templates.

Now we have to declare the template global function in order to access the flashed messages from the template. That is done on the last line of the snippet shown above.

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

@app.post("/login/", response_class=HTMLResponse)
async def login(request: Request, username: str = Form(...), password: str = Form(...)):
    if username == "test" and password == "test":
        flash(request, "Login Successful", "success")
        return templates.TemplateResponse("login.html", {"request": request})
    flash(request, "Failed to login", "danger")
    return templates.TemplateResponse("login.html", {"request": request})

The first route is used to show the template for the user. The second route is used to post the data from our form. If the username and password are not equal to the test it will flash a warning message, unless it will flash a success message.

Now in the template, I did something like below:

templates/base.html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="{{ url_for('static', path='/style.css') }}" rel="stylesheet">
    {% block head %}

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

    {% endblock body %}
</body>
</html>

We are extending our template from the above base.html. The style.css is a simple script shown above:

static/style.css

.success{
    color: green;
}
.danger{
    color: red;
}

Then finally our login.html is:

templates/login.html

{% extends 'base.html' %}
{% block body %}
    {# Flashing message start here #}
            {% for message in get_flashed_messages(request) %}
                <div class="{{ message.category }}">{{ message.message }}</div>
            {% endfor %}
    {# Flashing message ends here #}
    <form action="{{ url_for('login') }}" method="post">
        <table>
            <tr>
                <td>Username</td>
                <td><input type="text" name="username" id=""></td>
            </tr>
            <tr>
                <td>Password</td>
                <td><input type="password" name="password" id=""></td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" value="Login">
                </td>
            </tr>
        </table>
    </form>
{% endblock body %}