Creating a FARM Stack Dev Environment with Docker Compose — Part 3 of 3: React

David Sanchez
CodeX
Published in
8 min readSep 24, 2021
The logos for the FastAPI, React, and MongoDB companies of the FARM stack.

Introduction

This is the final part of the series on creating a Python FastAPI, React, and MongoDB (FARM)stack development environment using Docker Compose. I will review new additions to the docker-compose.yml file. I will also add a section for packaging this up for distribution to your teammates. Although this is meant for local development, if you would like to make sure your teammates have different MongoDB credentials, I will show you how to use environment variables in Docker Compose so you do not have to include your credentials in your package or Git repository.

The React part of this repository began as the starter application found at the React “Getting Started” website (https://create-react-app.dev/docs/getting-started/). I will review the component that was added to communicate with the FastAPI server. In addition, you will see the added HTML form and table used to load the FastAPI results data.

Previous Parts of the Series

If you missed parts one and two of the series, you can review the MongoDB part of the FARM stack at https://medium.com/codex/farm-stack-with-docker-compose-part-1-mongodb-54cc65e31636 and the FastAPI part of the FARM stack can be found at https://medium.com/codex/creating-a-farm-stack-dev-environment-with-docker-compose-part-2-of-3-fastapi-9ee7ab644809.

Running the FARM Stack

To run the FARM stack and go directly to the React site, type the Docker Compose command below. Please note you must add a “.env” file with the MongoDB credentials to the project’s root directory. For more information please read the “Package for Distribution” section of this article.

docker-compose up

After the Docker Compose development stack is running, direct your browser to http://0.0.0.0:3000. You will see the web form for searching the MongoDB database for comic book issues.

The main page of the Docker Compose FARM stack found in your browser after running the stack.

FastAPI Additions

When communicating with the FastAPI service in your FARM stack from the React front end, you may receive an error such as the following. It may differ depending on the browser you are using.

Access to XMLHttpRequest at ‘http://0.0.0.0:8000/comics/Black%20Panther/15' from origin ‘http://localhost:3000' has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

This is a “Cross-Origin Resource Sharing” (CORS) error thrown by most modern web browsers.

The CORS check sent by your browser for 0.0.0.0:3000 or localhost:3000 will fail. The CORS check fails because FastAPI URL 0.0.0.0:8000 differs due to the different ports. By default, the FastAPI server will not send the “Access-Control-Allow-Origin” header back to the browser. Updated browsers may deny the loading of the resources from your FastAPI service.

You must use the module “CORSMiddleware.” The module “CORSMiddleware” allows you to add origin URLs to satisfy the header requirement of modern browsers. In your case, 0.0.0.0:3000 and localhost:3000 must be added. The documentation can be found at the following location.

https://fastapi.tiangolo.com/tutorial/cors/?h=%20cors#use-corsmiddleware

The following steps allow your FastAPI service to return the valid CORS header to the browser.

  1. Open the main.py file in the “fastapi/app” directory
  2. Add the import line “from fastapi.middleware.cors import CORSMiddleware”
  3. Create a list called origins and add the strings “http://0.0.0.0:3000" and “http://localhost:3000" to the list
  4. Call app.add_middleware with the module “CORSMiddleware” as the first argument and set the argument “allow_origins” to the “origins” variable created in previous step
origins = [
"http://0.0.0.0:3000",
"http://localhost:3000"
]
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

For more information on CORS, view the CORS documentation found at the Mozilla developer site.
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORSstack is

The Docker Compose Configuration File

The build file for the React part of the stack has only four lines.

  • Line 1 assures node is installed to run React
  • The next line sets the working directory to “/app”
  • The port 3000 must be exposed for the web browser
  • The final line makes sure the necessary packages are installed and the React application is kicked off

Docker Build File “react.dockerfile”

from node:14.17.5
WORKDIR /app
EXPOSE 3000
CMD npm install -l; npm start

The section “react” is added to the docker-compose file. The folder “farm-react” is mapped to the working directory “/app” and the container port 3000 is mapped to the host port 3000.

docker-compose.yml “react” Sectionstack is

react:
build:
context: ./
dockerfile: react.dockerfile
volumes:
- './farm-react:/app'
ports:
- 3000:3000

In addition to the “react” section being added to the “docker-compose.yml” file, there are environment variables used for the MongoDB credentials as opposed to the hardcoded credentials added in the previous parts of this series. That will be discussed later in the “Package for Distribution” secstack istion.

React Form Component

The React part of this project was initially created with the React starter example project. I added a form component to show an example of communicating with a FastAPI server. I enjoy using Axios for web requests so you will see that package in the node package manager.

https://github.com/facebook/create-react-app

React Project Depencies

The dependencies can be found at farm-react/package.json. The code can be found at the GitHub location below.

  • Axios: I added, “axios: ^.21.1” to the “dependencies” section of “farm-react/package.json”

Main React Page Code

I updated the create-react-app page “farm-react/src/App.js” and added the form to query from the FastAPI server and display the results in a table at the bottom of the page.

import logo from './logo.svg';
import './App.css';
import {useState} from "react";
import axios from "axios";
function App() {
const [comicBookTitle, setComicBookTitle] = useState("");
const [comicBookIssueNumber, setComicBookIssueNumber] = useState("");
const [comicsList, setComicsList] = useState([]);
const comicSearchHandler = async () => {
const response = await axios.get("http://0.0.0.0:8000/comics/" + comicBookTitle + "/" + comicBookIssueNumber);
console.log(comicsList);
console.log(response.data);
setComicsList(response.data);
console.log(comicsList);
};
const handleComicTitleChange = (event) => {
setComicBookTitle(event.target.value);Example .env File
};
const handleComicBookIssueNumber = (event) => {
setComicBookIssueNumber(event.target.value);
};
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
<h1>Comic Book API Form</h1>
<form>
<label htmlFor="comicBookTitleInput">Comic Book Title:</label><br/>
<input type="text" name="comicBookTitleInput" id="comicBookTitleInput" value={comicBookTitle} onChange={handleComicTitleChange}/><br/>
<label htmlFor="comicBookIssueNumberInput">Issue Number:</label><br/>
<input type="text" name="comicBookIssueNumberInput" id="comicBookIssueNumberInput" value={comicBookIssueNumber} onChange={handleComicBookIssueNumber}/><br/>
</form><br/>
<button id="comicBookSearch" onClick={comicSearchHandler}>Search</button>
<h2>Search Results</h2>
<table>
<thead>
<tr>
<th>Publisher Name</th>
<th>Title</th>
<th>Issue</th>
<th>Publish Date</th>
</tr>
</thead>
<tbody>
{comicsList.map((comicBook, key) =>
<tr key={key}>
<td>{comicBook.publisher_name}</td>
<td>{comicBook.series_name}</td>
<td>{comicBook.number}</td>
<td>{comicBook.on_sale_date}</td>
</tr>
)}
</tbody>
</table>
</header>
</div>
);
}
export default App;

Import Axios

You must import the “Axios” package with the following line.

import axios from "axios";

Set Up the React Application State Properties

Add the application state properties to your React app’s “App” function with the following lines. These properties represent the data that will be returned from the FastAPI server. The property data will be populated to the HTML table added to the React application.

const [comicBookTitle, setComicBookTitle] = useState("");
const [comicBookIssueNumber, setComicBookIssueNumber] = useState("");
const [comicsList, setComicsList] = useState([]);

Add the FastAPI GET Request and Search Form Handler

In the “App” function add a form search handler, “comicSearchHandler.” To call the FastAPI server, create a GET request using “await axios.get.” Notice “0.0.0.0:8000" is hardcoded. But that could have easily have been an environment variable. For simplicity and demonstration purposes, the FastAPI server URL is hardcoded. The call “await axios.get” provides the functionality to get the comic book data that is requested.

The properties “comicBookTitle” and “comicBookIssueNumber” are used to create the URL used in the GET request.

const comicSearchHandler = async () => {
const response = await axios.get("http://0.0.0.0:8000/comics/" + comicBookTitle + "/" + comicBookIssueNumber);
console.log(comicsList);
console.log(response.data);
setComicsList(response.data);
console.log(comicsList);
};

Update the Application States on Input Box Changes

The variables “handleComicTitleChange” and “handleComicBookIssueNumber” are added to handle any changes made to the form text input boxes.

The properties “comicBookTitle” and “comicBookIssueNumber” are updated with the handlers assigned to the variables “handleComicTitleChange” and “handleComicBookIssueNumber.”

const handleComicTitleChange = (event) => {
setComicBookTitle(event.target.value);
};
const handleComicBookIssueNumber = (event) => {
setComicBookIssueNumber(event.target.value);
};

Create the Search Form and Set Up the Change Handlers

Add an HTML form to input the comic book title, comic book issue number, and include a search button.

The handlers “handleComicTitleChange” and “handleComicBookIssueNumber” are set to the “onChange” attributes for the input fields “comicBookTitleInput” and “comicBookIssueNumberInput.”

Set the “onClick” attribute of the button “comicBookSearch” to “comicSearchHandler.”

<form>
<label htmlFor="comicBookTitleInput">Comic Book Title:</label><br/>
<input type="text" name="comicBookTitleInput" id="comicBookTitleInput" value={comicBookTitle} onChange=,{handleComicTitleChange}/><br/>
<label htmlFor="comicBookIssueNumberInput">Issue Number:</label><br/>
<input type="text" name="comicBookIssueNumberInput" id="comicBookIssueNumberInput" value={comicBookIssueNumber} onChange={handleComicBookIssueNumber}/><br/>
</form>
<button id="comicBookSearch" onClick={comicSearchHandler}>Search</button>

Add a Results Table

The table added will be used to load the FastAPI call results data. The table data is loaded in “comicSearchHandler” discussed above. Each row cell is mapped to the JSON data returned by the FastAPI service in the “comicSearchHandler” GET request. “comicsList.map” is the mechanism used for iteration through the “comicsList” property.

<table>
<thead>
<tr>
<th>Publisher Name</th>
<th>Title</th>
<th>Issue</th>
<th>Publish Date</th>
</tr>
</thead>
<tbody>
{comicsList.map((comicBook, key) =>
<tr key={key}>
<td>{comicBook.publisher_name}</td>
<td>{comicBook.series_name}</td>
<td>{comicBook.number}</td>
<td>{comicBook.on_sale_date}</td>
</tr>
)}
</tbody>
</table>

Package for Distribution

In the previous parts of this series, the MongoDB credentials can be found hard-coded in the docker-compose file. If you would like to distribute this for teammates and do not want them to use the same credentials as you, you can use an environment variables configuration file called “.env.” The Docker Compose application will load the “.env” file and use the environment variables found in the “.env” file if it exists. You can add “.env” to the “.gitignore” file of your git repository so that the “.env” file is not included in your repository.

To reference the environment variables in your “docker-compose.yml” file, type the “$” character with the environment variable inserted inside curly brackets “{}.” For example, type “${MONGO_USER}” if you want to reference the environment variable “MONGO_USER.”

Example .env File

MONGO_USER=root
MONGO_PASSWORD=OTNmYTdjYmZkMjE5ZmYzODg0MDZiYWJh

Docker-compose.yml Environment Variables in the “mongo” Section

mongo:
image: mongo
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
volumes:
- ./data/mongo:/data/db
- ./data/mongo-init:/docker-entrypoint-initdb.d
ports:
- "27017:27017"

Conclusion

Now that the Docker Compose stack is complete, you can run the stack with the “docker-compose up” command.

Direct your web browser to “https://0.0.0.0:3000” and you will see the “Comic Book API Form.”

Enter a comic book title with an issue number, “Black Panther” and “15”, for example. Click “Search.” And you will see the results load below the form.

The “Comic Book API Form” where you can enter the “Comic Book Title” and then click “Search.”
The search results after you enter the comic book title and issue in search form.

I hope you found this series useful and are now able to quickly spin up your FARM stack for development. You should also be able to distribute your FARM stack to your teammates without compromising the security of your local FARM stack.

--

--

David Sanchez
CodeX
Writer for

Computer Scientist, MS, interested in machine learning, data driven automation, nature, and whiskey. CEO and founder of the DNS AI, LLC software company.