Consuming a FastAPI Backend from a React Frontend

undraw svg category

Welcome to Part 17 of Up and Running with FastAPI. If you missed part 16, you can find it here .

This series is focused on building a full-stack application with the FastAPI framework. The app allows users to post requests to have their residence cleaned, and other users can select a cleaning project for a given hourly rate.

Up And Running With FastAPI

In the previous post we finished implementing a front-end authentication system with react and redux. Users can now sign up, login, and see their profile page. Authentication errors are handled gracefully and some routes are protected from access by unauthenticated users.

With all that in place, we’re now going to shift gears and start consuming resources served by our FastAPI backend. This includes giving users the ability to post cleaning opportunities in the application and letting other users make bids on those posts. This will be a gradual process and we’ll start with creating posts. Though we’ll stay mostly in the frontend, there will be times when we’ll need to refactor some server-side code. Customer needs change and adapt, so this is to be expected in the development process.

Let’s get to it.

The Cleaning Jobs Page

Our navbar currently has three links that don’t go anywhere - Find Cleaners, Find Jobs, and Help. We’re going to build out a simple page for the Find Jobs link that will allow a user to post a job and helps other users who may be looking for that opportunity.

Create three new components called CleaningJobsPage.js, CleaningJobsHome.js, and CleaningJobCreateForm.js.

We’ll plan ahead and build out a simple parent component that takes advantage of nested routing provided by the new react-router version.

Add the following to the CleaningJobsPage.js component:

CleaningJobsPage.js

Upon navigating to /cleaning-jobs, users will see the CleaningJobsHome component by default. That’s what the path="/" accomplishes, as it is relative to the current path and we plan on mounting this page under the /cleaning-jobs/* route. Any route that doesn’t match will show the NotFoundPage.

In the CleaningJobsHome.js file add the following:

CleaningJobsHome.js

If we navigate to http://localhost:8000/docs and click on the Cleanings:Create-Cleaning route, the openapi docs show us exactly how the request body should be shaped in order to create a new cleaning job. So our CleaningJobCreateForm component should accurately represent that.

The docs point us to this model:

That means we’ll need a simple input field for name, probably a textarea for description, a numerical input for price, and a select for cleaning_type. As before, elastic-ui makes all that pretty easy, so let’s get right to it.

CleaningJobCreateForm.js

Big component huh? Don’t freak out, most of it should be familiar.

Even so, we are being introduced to a few new items here. First and foremost, we’re using the EuiSuperSelect as a dropdown. The docs for this component have all the information needed to get started, but we’ve got most of the basics on display here. Our validation and error system follows the same pattern as both the LoginForm and RegistrationForm components. One cool new thing to mention is that as soon as we submit the form, we check to see if the response has a success attribute attached and redirect the user to the /cleaning-jobs/{cleaningId} route if it is.

Note that the res.success property is meant to come from the onSuccess handler we attached to our apiClient in the last post. Readers who missed that article are encouraged to check it out before proceeding with this one.

Even though we don’t currently have any validation in place for the name, description, or price fields, they’ve been added here for consistency.

Though, honestly, it probably makes sense to add one for price. Let’s go ahead do that now!

utils/validation.js

Nothing too crazy, just a standard regex test. And we’re off to the races!

Before we can see the fruits of our labor, we’ll need to export each of these components from our components/index.js file and create the new route in the components/App.js file.

components/index.js

and in our App.js component:

App.js

We’re again protecting our route from unauthenticated users and setting the path to /cleaning-jobs/* so that any route with the prefix of /cleaning-jobs will match this component. This is what allows us to do the nested routing that we saw in the CleaningJobsPage component.

To polish off this section, let’s add our new route to the Navbar under the correct link.

App.js

Time to see how we did:

Check it out on Code Sandbox

phresh-frontend-part-5-creating-cleaning-jobs

This is looking nice!

The next step is to setup our redux slice to manage cleaning jobs.

Configuring Redux for Cleanings

We’re going to create a new slice of state in redux calling cleanings. The general pattern to follow whenever we want to create a new slice of state in redux is as follows:

  1. Add the default state for our new slice in initialState.js.
  2. Create a new file in the redux directory for that slice.
  3. Define and export any constants that are needed at the top of the file.
  4. Configure a new reducer and make it the default export for that file.
  5. Export action creators that will be used to modify the state slice.
  6. Import the reducer into the root reducer file and add it in the combineReducers call.

Starting with #1, we’ll update the initialState.js file with a new section.

redux/initialState.js

Not much noteworthy here. We’re storing the error and isLoading attributes as before. This time, we’re also adding on a data attribute along with a currentCleaningJob attribute. Anytime our page needs to cache a number of cleaning jobs locally, they’ll by indexed by id under the data attribute. If we’re viewing a single cleaning job at a time, that will be stored under the currentCleaningJob attribute.

As this object grows, we may want to leave some notes as to what each property represents, but we’ll be fine for now .

Go ahead and create a new file calling cleanings.js.

And add the following to it:

redux/cleanings.js

Alright, let’s review what’s happening here. We’re setting up a new reducer that manages the different states seen when a user creates a cleaning job. If the request is successful, we store that job in the data object with the cleaning job’s id as the key. On unsuccessful requests, we simply store the error. We’ve also defined and exported a createCleaningJob action creator that is relatively simple thanks to our apiClient abstraction.

And in our rootReducer.js file:

redux/rootReducer.js

And if we check out our state tree in the redux-devtools-extension, we see that our cleaning slice is ready to go. Perfect.

So now we can go ahead and map the appropriate redux data to our CleaningJobCreateForm component props and try this out.

CleaningJobCreateForm.js

Let’s go ahead and try this out. Create a new cleaning job and hit submit. If all goes well, we should be redirected to the a new page with nothing there at the moment. However, if we check out the terminal where our FastAPI server is running, we see a succesfull POST request has been logged. On top of that, when we check out redux state tree, we see the freshly minted cleaning job stored at state.cleanings.data.

Check it out on Code Sandbox

phresh-frontend-part-5-the-cleanings-redux-slice

Fantastic!

Now, we can go ahead and actually create a page to view new cleaning jobs once they’re created.

Fetching Cleaning Jobs

The page we redirect users to once they’ve created a cleaning job - /cleaning-jobs/:cleaning_id - is a dynamic route that should render a different cleaning job depending on whatever value cleaning_id takes. We’ll do that by querying our backend as soon as the user loads that page.

Create a new component called CleaningJobView:

And add the following to it:

CleaningJobView.js

We’re importing the useParams hook from react-router-dom so that we can extract the cleaning_id from the url and use it how we want. To tell react-router-dom what to name that path parameter, we’ll specify it in the path argument in the CleaningJobsPage component.

First, add the CleaningJobView component to our default exports:

components/index.js

Next, create a new path in the nested routes defined in CleaningJobsPage.

CleaningJobsPage.js

What we’re doing here is matching the /cleaning-jobs/ path to the CleaningJobsHome component and then indicating that if a value is found after the trailing slash - such as /cleaning-jobs/2 - it should be interpreted as the cleaning_id parameter.

Navigate to any path that looks like /cleaning-jobs/:cleaning_id and see that the value is displayed in the center of the page. By syncing our component with the url, we have created a system where each cleaning job gets its own page. Now, we have to determine how to fetch that job once we have its id.

Back in redux/cleanings.js, add 4 new action types, 4 reducer updates, and two action creators.

redux/cleanings.js

The fetchCleaningJobById is simple. It calls the appropriate API endpoint with whatever cleaning_id is passed to it. The clearCurrentCleaningJob doesn’t actually make any requests to our API. Instead, we’ll use it to clear whatever data is stored under currentCleaningJob by setting it to null.

In our reducer, we’re indicating that a successful query should result in the requested job being stashed under currentCleaningJob. Unsuccessful requests result in an empty object being stashed there instead. Why the discrepancy? When currentCleaningJob is null, we know that the request hasn’t resolved yet and we can handle that appropriately in the UI. An empty object will indicate that no cleaning job was found and we should display a 404 page.

Speaking of the 404 page, ours is pretty lacking. We should spruce that up in a moment.

For now, let’s wire up our CleaningJobView to use our new additions.

CleaningJobView.js

We’re once again leveraging React.useEffect to execute an API request any time cleaning_id changes in the CleaningJobView component. We’re also returning the a function calling our clearCurrentCleaningJob action creator so that when the user navigates away, our state is reset.

Below that we check to see if we’re currently fetching a cleaning job and show a spinner if so. If we’re not loading, but the currentCleaningJob is still null we also show a spinner. This helps prevent quick flashes of a NotFoundPage in the time between when a request finishes loading and the data is rendered to the page. In the case that the currentCleaningJob exists but doesn’t contain a name, we show the NotFoundPage.

Try navigating to /cleaning-jobs/1 and check out the new resource we’ve just created.

It works! And we see some content. But…

It’s pretty ugly.

Let’s make this look a tad nicer.

Create a new component called CleaningJobCard.

And add the following:

CleaningJobCard.js

More as a showcase than anything else, we’re employing the EuiBadge and EuiCard components along with the EuiLoadingChart component from elastic-ui to compose our CleaningJobCard component. We’re loading a random image from unsplash.com under the “Soap” category and also showing the name, description, and price associated with the job. Finally, we’ve added a currently-inactive button that users can click to make an offer for this job.

Go ahead and export this component, as always.

components/index.js

And then integrate it into CleaningJobsView.

CleaningJobsView.js

Cool! That’s looking pretty nice. However, we’re missing the user who posted this offer. Currently the GET_CLEANING_BY_ID_QUERY used by the CleaningsRepository in our FastAPI server doesn’t actually extract any information about the owner when grabbing the cleaning resource.

We should fix that, and that means we’ll need to refactor our backend code a bit to handle this need.

Before we do, let’s spruce up the NotFoundPage component so that the user actually knows what’s going on when they navigate to a page that doesn’t exist.

NotFoundPage.js

That looks much better. We’re using the the EuiEmptyPrompt from elastic-ui to display the 404 page depending on what resource could not be located. The actions prop also allows us to specify a button that directs users back to whatever link they arrived from. Try navigating to something like /cleaning-jobs/200000 and see it in action.

Not bad!

Now on to the backend.

Refactoring the Cleanings Resource in our FastAPI Backend

The goal here is to ensure that when we request a cleaning job from our API, we also get information about the owner who created it. First things first, we’re going to head into our CleaningsRepository and make a few modifications.

repositories/cleanings.py

Ok, let’s start from the top. We’re importing the UsersRepository and the databases package. Then we define an __init__ method for our CleaningsRepository class and add a users_repo attribute to it. We’ve then gone ahead and updated the get_cleaning_by_id method to take in an additional boolean parameter - populate. If populate is true, this method returns a call to self.populate_cleaning, which we define at the bottom of the repository. This method creates a CleaningPublic model with all the attributes of the cleaning record retrieved by the GET_CLEANING_BY_ID_QUERY, but the owner field is replaced with the result of calling self.users_repo.get_user_by_id with the id of the cleaning resource’s owner.

Now that method on the UsersRepository doesn’t exist yet, so let’s go fill that in.

repositories/users.py

Since the UsersRepository already has a populate_user method, we just needed to write the SQL query and mirror a standard get_[resource]_by_id method.

Now funny enough, this should be all that we need!

But there’s a problem.

Run the test suite again and see what happens.

Oh no! Look at all those errors! Why? We’re getting 403 errors all over the place.

The answer rests in our dependencies. Let’s start with the api/dependencies/cleanings.py file. We’re using the get_cleaning_by_id_from_path dependency all over our application and we’re expecting it to return a CleaningInDB model. However, that’s no longer the case by default. We’re now populating the response and returning a CleaningPublic model with a UserPublic model nested under the owner property.

So when our check_cleaning_modification_permissions looks at cleaning.owner == current_user.id to check for ownership, it will always return false. Instead of cleaning.owner being an integer representing the id of the owner, it’s now a UserPublic model. We should instead be looking at cleaning.owner.id. Let’s go ahead and create a utility method to determine if a user owns a cleaning resource and account for both possiblities.

dependencies/cleanings.py

Well that’s a tad bit clunkier, but it seems to work nicely. Since we’re making this check in the evaluations and offers dependencies, we’ll have to update the methods there as well.

So for the evaluations.py file, add the following:

dependencies/evaluations.py

And in the offers.py file:

dependencies/offers.py

One last tiny change to get 100% of tests passing.

Head into the tests/test_cleanings.py and make the following change:

tests/test_cleanings.py

Since the CleaningPublic instance returned from our "cleanings:get-cleaning-by-id" endpoint returns a populated user, we need to remove that value to make this test pass (or we could convert each model appropriately, but we’ll leave that as an exercise for the reader).

Run the tests one more time and watch them all pass.

Whew! Good thing we wrote all those tests! This is truly where they shine - as a guarantee that changing code won’t break things. We can refactor with confidence and make the necessary changes when needed.

Let’s polish off this post back in the front end.

We’ll do something slightly more sophisticated later on, but for now, let’s just show the avatar and username of the owner who’s posted the cleaning job.

Update the CleaningJobView component like so:

CleaningJobView.js

Not half bad.

Check it out on Code Sandbox

phresh-frontend-part-5-fetching-cleaning-jobs-by-id

We’re really cooking here.

Wrapping Up and Resources

That’s more than enough for today. We’ve setup our React front end to consume resources from our FastAPI backend and made it all look nice with elastic-ui. Our backend required a small refactor and our test suite helped us correct permissions errors that popped up as a result. In doing so, we can now create cleaning jobs from our UI and view individual cleaning jobs on their own personal page.

The next step will be to allow users to edit their own posts, and to give other users the ability to view a list of available posts. In future posts, we’ll also set up our evaluations system and show aggregate stats for cleaners and employers alike.

Github Repo

All code up to this point can be found here:

Tags:

Previous Post undraw svg category

Client-Side Protected Routes and User Registration

Next Post undraw svg category

Edit User-Owned Cleaning Resources with React and FastAPI