The course: "FastAPI for Busy Engineers" is available if you prefer videos.

The Ultimate FastAPI Tutorial Part 12 - Setting Up a React Frontend

In part 12 of the FastAPI tutorial, we'll look at setting up a React frontend
Created: 25 January 2022
Last updated: 04 December 2021

Introduction

Welcome to the Ultimate FastAPI tutorial series. This post is part 11. The series is a project-based tutorial where we will build a cooking recipe API. Each post gradually adds more complex functionality, showcasing the capabilities of FastAPI, ending with a realistic, production-ready API. The series is designed to be followed in order, but if you already know FastAPI you can jump to the relevant part.

Code

Project github repo directory for this part of the tutorial

Tutorial Series Contents

Optional Preamble: FastAPI vs. Flask

Beginner Level Difficulty

Part 1: Hello World
Part 2: URL Path Parameters & Type Hints
Part 3: Query Parameters
Part 4: Pydantic Schemas & Data Validation
Part 5: Basic Error Handling
Part 6: Jinja Templates
Part 6b: Basic FastAPI App Deployment on Linode

Intermediate Level Difficulty

Part 7: Setting up a Database with SQLAlchemy and its ORM
Part 8: Production app structure and API versioning
Part 9: Creating High Performance Asynchronous Logic via async def and await
Part 10: Authentication via JWT
Part 11: Dependency Injection and FastAPI Depends
Part 12: Setting Up A React Frontend
Part 13: Using Docker, Uvicorn and Gunicorn to Deploy Our App to Heroku
Part 14: Using Docker and Uvicorn to Deploy Our App to IaaS (Coming soon)
Part 15: Exploring the Open Source Starlette Toolbox - GraphQL (Coming soon)
Part 16: Alternative Backend/Python Framework Comparisons (i.e. Django) (Coming soon)

Post Contents

Theory Section - How Frontends Interact with FastAPI
Practical Section 1 - Setting Up React Create App
Practical Section 2 - Calling API from the Frontend

FastAPI logo


Theory Section - How Frontends Interact with FastAPI

So far in this tutorial series we’ve only interacted with our API via the Open API (swagger) UI, and by serving a fairly limited Jinja2 template. If your system is all backend microservices, then this is fine. However, if you’re looking to serve a more complex frontend then you’ll probably want to leverage a modern JavaScript framework like:

  • React
  • Angular
  • Vue

Whilst each of these frameworks has their pros and cons, their interaction with FastAPI is quite similar.

Here is a rough architecture diagram:

TODO

Since React is the most popular of the modern frontend frameworks, this is the one I have chosen to use for the tutorial series. Whilst this isn’t a series on React, I will cover it in enough detail to give a meaningful example of how it would work with FastAPI - so I’m including common requirements like auth, and including multiple pages and components.

You could implement the backend in any language (node, PHP, Java…any language that can create a web server), but since this is a FastAPI tutorial, that choice is made for us :)

Note, we’ll look at deployment of both the front and backends in the next part of this series.


Practical Section 1 - Setting Up React Create App

If you’re not familiar with React then I suggest checking out the very approachable docs. The key thing to note here is that the create-react-app package we’re making use of is an officially supported tool that simplifies React apps:

Create React App is an officially supported way to create single-page React applications. It offers a modern build setup with no configuration.

Under the hood, React relies on:

  • webpack: a static module builder
  • babel: a JavaScript compiler mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript)
  • ESLint: A powerful code linter

Each of these usually requires configuring, and it can be a painful hurdle for those unfamiliar with the ecosystem. Create React App basically sets sensible defaults for you so you can skip all that setup. At any point you can call the eject command (which is irreversable) and then all the underlying config files are revealed so you can customize them. Given this simplification, it’s a great tool for when you are starting out, and it offers flexibility as your app grows in complexity.

Setting Up the Project Structure

You’ll notice that in part 12 of our project repo we have a new frontend directory where all React code will be. To get started, cd into this directory then install the dependencies:

  • Note that create-react-app requires NodeJS 14+
  • Run npm install in the directory where your package.json file is located (this lists our depenedencies)
  • To start the React app run npm start

Your app will start, and if you then navigate over to http://localhost:3000/ you should see this:

TODO: Screenshot

You’ll notice that there are no recipes, and that is deliberate. The data for our frontend is stored in a SQL database and accessed via the FastAPI backend application. Before we hook up the backend, let’s inspect our frontend structure:

TODO: ASCII diagram

We have:

  • node_modules where our frontend dependencies are installed
  • a public directory for things like a favicon and robots.txt
  • src/components
  • src/pages
  • src/index.js
  • src/App.js
  • src/client.js
  • src/config.js

Practical Section 2 - Calling API from the Frontend

In this part we added the first test to our example app. The test runner is pytest.

If you’re not familiar with pytest, checkout this free pytest introduction lecture from my testing and monitoring ML models course.

For the purposes of this tutorial, the key thing to understand is that test fixtures in pytest are defined by convention in a file called conftest.py. So in our loan test file app/tests/api/test_recipe.py we have a relatively short bit of code hiding a lot more:

# test_recipe.py
from app.core.config import settings


def test_fetch_ideas_reddit_sync(client):  # 1
    # When
    response = client.get(f"{settings.API_V1_STR}/recipes/ideas/")
    data = response.json()

    # Then
    assert response.status_code == 200
    for key in data.keys():
        assert key in ["recipes", "easyrecipes", "TopSecretRecipes"]

The key thing to note at comment (1) where the test takes the client as its first argument. client is a test fixture defined in conftest.py:

# conftest.py
from typing import Generator
from unittest.mock import MagicMock

import pytest
from fastapi.testclient import TestClient

from app.main import app
from app.api import deps


async def override_reddit_dependency() -> MagicMock:
    mock = MagicMock()  # 4
    reddit_stub = {
        "recipes": [
            "baz",
        ],
        "easyrecipes": [
            "bar",
        ],
        "TopSecretRecipes": [
            "foo"
        ],
    }
    mock.get_reddit_top.return_value = reddit_stub  # 5
    return mock


@pytest.fixture()  # 1
def client() -> Generator:
    with TestClient(app) as client:  # 2
        app.dependency_overrides[deps.get_reddit_client] = override_reddit_dependency  # 3
        yield client  # 6
        app.dependency_overrides = {}  # 7

There is a lot happening here, let’s break it down by the comment numbers:

  1. We use the pytest fixture decorator to define a fixture
  2. We access the FastAPI built-in test client via a context manager so we can easily perform clean-up (see 7)
  3. We use the FastAPI app dependency_overrides to replace dependencies. We replace what the selected dependency (in this case get_reddit_client) callable is, pointing to a new callable which will be used in testing (in this case override_reddit_dependency)
  4. We make use of the Python standard library unittest mock MagicMock (docs for those unfamiliar)
  5. We specify the return value of a particular method in our mocked reddit client (get_reddit_top), here it will return dummy data
  6. We yield the modified client
  7. We perform clean up on the client, reverting the dependencies

This is an illustration of the power of dependency injection in a testing context. Whilst libraries like request-mock would also allow you to replace the return value of a particular HTTP call, we can also apply this approach to any callable, whether it’s:

  • Interacting with a database
  • Sending emails via SMTP
  • Working with other protocols (e.g. ProtoBuf)
  • Simply calling complex code we don’t need to worry about for a particular test.

All with fairly minimal setup, no monkey-patching, and fine-grained control. This is part of a broader pattern in software development: composition over inheritance (useful Python overview here).

Hopefully this shows the power and versatility of FastAPI’s dependency injection system. Learning the value of this approach will save you a lot of pain.


Continue Learning FastAPI

Next we’re going to consolidate what we’ve learned in a post on dependency injection

coming in late August

Category