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
Welcome to the Ultimate FastAPI tutorial series. This post is part 12. 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.
Tutorial Series Contents
Beginner Level Difficulty
Intermediate Level Difficulty
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)
Theory Section - How Frontends Interact with FastAPI
Whilst each of these frameworks has their pros and cons, their typical interaction with FastAPI is quite similar.
Here is a rough architecture diagram:
A variation on this architecture would be a GraphQL approach, but that is not what we will focus on in this post.
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, the Python choice is made for us :)
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. My idea here is that if you combine this tutorial with other dedicated React tutorials (I recommend the getting started docs), you’ll be able to put together all the pieces you need for interaction across the full stack.
Note, we’ll look at deployment of both the front and backends later in the tutorial 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
- 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 irreversible) 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
frontend directory where all React code will be. To get started,
cd into this directory then install the dependencies:
- Note that
create-react-apprequires NodeJS 14+
npm installin the directory where your
package.jsonfile is located (this lists our dependencies)
- To start the React app run
Your app will start, and if you then navigate over to
http://localhost:3000/ you should see this:
At the moment the recipes are being fetched from an externally running version of our FastAPI app (which is deployed
using the method we’ll explore in the next part of this tutorial series). This API is specified in our
REACT_APP_API_BASE_PATH setting. However, to test everything locally we’ll want to update this value to be our
running backend application:
http://localhost:8001. Obviously we’ll first have to start up our backend app, as we have
done in the previous entries in the tutorial series.
Before we hook up the local backend, let’s inspect our frontend structure:
. ├── App.css ├── App.js ├── client.js ├── components │ ├── Button │ │ └── Button.jsx │ ├── DashboardHeader │ │ ├── index.jsx │ ├── Footer │ │ ├── index.jsx │ ├── FormInput │ │ └── FormInput.jsx │ ├── Idea │ │ └── index.jsx │ ├── IdeaTable │ │ └── index.jsx │ ├── Loader.jsx │ ├── Modal │ │ └── PopupModal.jsx │ ├── Recipe │ │ └── index.jsx │ └── RecipeTable │ └── index.jsx ├── config.js ├── index.css ├── index.js ├── pages │ ├── error-page │ │ └── index.jsx │ ├── home │ │ └── index.jsx │ ├── ideas │ │ ├── index.jsx │ ├── login │ │ └── index.jsx │ ├── my-recipes │ │ ├── index.jsx │ │ └── NotLoggedIn.jsx │ └── sign-up │ ├── index.jsx
This is a pretty standard structure:
pagesare our main “containers” of HTML, representing different pages on the site as the name implies
componentsare the building blocks which make up these pages, such as forms, buttons, tables and modals.
App.jsis where we set up the routing logic which assigns page React functions to a particular url of the site.
We’ll get to the more advanced parts (like
client.js a little further on in this post).
Sidebar: What is JSX?
You’ll notice that all of our page and component files have a
.jsx extension. This is called JSX, and it is a syntax
Sidebar: What’s With All the ClassNames - Tailwind CSS
.jsx files we’re styling the HTML with Tailwind CSS (this is what
className="flex items-center justify-center type of lines are about) which
has gained popularity over the past few years due to its ease of use, especially for responsive design.
Sidebar: React Hooks
Hooks were added in React 16.8. If you wrote React before this update and
you’re now looking at this tutorial, you might be wondering where all the React classes are. The answer is: They’re not
needed if we use hooks.
From the docs:
Hooks let you use more of React’s features without classes. Conceptually, React components have always been closer to functions. Hooks embrace functions, but without sacrificing the practical spirit of React
Practical Section 2 - Calling FastAPI from the Frontend
Now that we understand how our React frontend application works, let’s have it fetch data from our FastAPI backend.
frontend/src/pages/home/index.jsx We have the following component:
Let’s break this down
- We define our React
MainViewreusable component as a function (note this is the functional component style, see this guide for an overview of the differences vs. class based components)
- Because we’re using functional components, we use React hooks such as useState
- We make use of the powerful
useEffecthook (docs)), which allows us to perform side effects in function components. Note that the empty array passed in as the second argument indicates this effect will only be called once (this empty array is the default so could be omitted, but I’m including it for clarity)
- We use our instantiated
FastAPIClient(more on this soon) to call the backend API. The response data is gathered via
async/await(ES7). Although not as modern as the
async/awaitsyntax, I think it’s more useful for backend devs who might not be as up-to-date with modern JS to easily follow along (see here for a comparison of the two approaches)
- We set the recipe data fetched from the API using the state hook.
OK, let’s go deeper now and look at what the client
getSampleRecipes method is doing:
- We instantiate the
apiClient, which is an Axios session
apiBasePathURL comes from the
config.jsfile - for local development this is
http://localhost:8001, but you’d update it via environment variable for deployment (we’ll cover this in the next post in the series). Note that we append the
/api/v1to match our recipe API structure. All subsequent requests with this client will automatically have this base URL prepended.
- We make use of Axios request interceptors which allow us to update the request headers for auth purposes (more on auth shortly).
- Using the Axios session we call our
recipes/search?keywordendpoint and return the response data.
The recipes/search endpoint should be familiar from previous parts of the tutorial
In order to see this client working, let’s start the backend:
- Follow the setup in the README.md
- Create the DB tables with:
poetry run ./prestart.sh
- Start the FastAPI server with
poetry run ./run.sh
- Update your frontend/config.js to set REACT_APP_API_BASE_PATH to
http://localhost:8001(note do not use https with localhost)
- In a separate terminal/command prompt, start your React app (or if it is already running, refresh the page at
You should now see recipe data:
And if you open up the browser dev tools and look at the network tab, you’ll see the app making requests to the backend running at http://localhost:8001:
Great! Now let’s look at user auth.
Practical Section 3 - React Auth with FastAPI and JWTs
As promised, we’re going beyond just a toy example. We’ll hook up an auth mechanism between our React frontend and our JWT-based backend auth system which we covered in part 10
We’ll start with the
We have a pretty standard React registration form, which makes use of our
Button components located:
This is pretty standard React code, but for those backend devs who might be a bit rusty (I sympathise!), here’s a quick breakdown:
- We update the form input fields with the React
useNavigatehook is from the react-dom-router library, which is used for routing.
- We’re using our FastAPI client
registermethod to call the backend (we’ll look at this next)
- Upon successful registration we navigate to the
- Standard React form submission code, indicating that we’ll call the
onRegisterfunction when the form is submitted.
OK great, next up we’ll dig down into the client:
Here we prepare all the registration form data gathered in our React component. This
loginData is then set as the body
of a POST request to our API
/auth/signup endpoint. We return the response (which will be the JWT token). Feel
free to play around with this endpoint yourself by starting up the backend, navigating to the /docs interactive UI and
trying it out:
So now we have registered a user. We still need to login.
The login React page follows almost exactly the same format and logic as the registration page (obviously calling the
method on the client instead of the
register method) so I won’t go over that. Where
things differ is in the client:
Lots happening in this code block, let’s break it down:
- The Authorization request header is the key header that must be set correctly for a valid login. We start by deleting it to ensure a stale default value is not used by the session.
- We POST the login data to the backend
- We store the response (which will be a JWT) in the browser local storage
- We’re now in possession of a JWT, and we use that to fetch the user data via the
- This involves making a GET request to the backend
- And then we store the response user data in local storage (as well as the token)
Every request the React app makes to the backend API has an
Authorization header inserted via the
we specified earlier in the
getApiClient method. Let’s look at the interceptor function, which is the final piece in our
client-side auth story:
Key lines to note:
- We use the jwt-decode library to decode the token - note that decoding is not the same as validating which can only be done on the server where the JWT secret resides (part 10 goes over JWT theory if you need a refresher)
- We then check the expiry data of the JWT using the Moment.js library (you can use a more modern alternative if you prefer)
- Finally, we set the
Authorizationheader for the request
And voila, now requests to our FastAPI endpoints which require user auth are possible. In our React app, this allows us to have
the concept of login-required pages. The
pages/my-recipes page is an example of this. We can set component state based on the
presence of a token:
And then display different HTML and/or redirect based on this state. In our example React app the “my-recipes” page is only displayed to logged in users, and attempts to create new recipes for non-logged in users will fail.
We’re also able to chain calls to get user information and then create recipes for a specific user
based on the ID (recall, this is how the POST
/recipes/ endpoint is structured, expecting a
submitter_id as one of the
POST body fields):
We’ve mostly considered the frontend additions so far, but we also need to take a moment to look at a few key updates to the API. —
Practical Section 4 - FastAPI Updates
FastAPI CORS With Frontends (like React)
Quoting from the docs:
In short, this gives you control over which frontends can call your API, which is often useful. In the case of our Recipe API and React frontend, we do need to allow some origins to call our API, such as localhost (for local development) and our deployed frontend application.
We do this in two places. The first is by using the FastAPI CORS Middleware.
We add this to
main.py like so:
BACKEND_CORS_ORIGINS is set in our settings, then the CORS Middleware is applied, and the
are set via a list comprehension on the
BACKEND_CORS_ORIGINS value. This brings us to the second update, which is
introducing this setting:
BACKEND_CORS_ORIGINS includes both localhost and our deployed frontend application (on Heroku). We’ll
be looking at the Heroku deployment in the next part of the tutorial. Wherever you deploy your frontend, you’ll need
to update this value to reflect it. Once the value is set correctly, you’ll be able to call your API without any
Phew! That was a lot of information. But now we have a truly modern frontend to interact with our FastAPI backend. Now we need to deploy everything…that’s coming up next.
Continue Learning FastAPI
Next we’ll deploy everything!
Finding this useful? Please subscribe to my email list for updates.