Let's Build and Deploy a FARM Stack app
Tags: react, python, webdev
Published: 2021-08-08 01:30:47

Have you ever written a full stack application with React, Angular, or Vue? This tutorial will cover the FARM stack, which stands for FastAPI, React, and MongoDB. FastAPI is a Python framework for building APIs, well, fast. This project is a To-Do List, which is a fairly simple project to do in React. This tutorial can generally be applied to other frameworks like Vue, and Angular, but I will be using React.

## Project Setup
Starting a project is very easy. I will be showing two methods, one using my CLI create-farm-app, and one manually. Both are pretty simple, but if you prefer not to set up all that much yourself, you can use the CLI. I recommend setting up the app manually for your first project.

### Manual Setup
Let's get started with manual setup:
```bash
$ mkdir farm-stack-tut
$ cd farm-stack-tut
$ mkdir backend
$ code .
$ git init
$ yarn create react-app frontend --template typescript
$ cd backend
$ git init
$ touch requirements.txt main.py model.py database.py
```
Now let's open up requirements.txt, and put in the following dependencies:
```
fastapi == 0.65.1

uvicorn == 0.14.0

motor == 2.4.0

gunicorn == 20.1.0

pymongo[srv] == 3.12.0
```

We will need uvicorn for running an ASGI server, motor and pymongo[srv] to connect to our MongoDB atlas database, and gunicorn for when we deploy the app.
The reason we are initializing two git repos (plus the one that is automatically initialized by CRA) is to make use of submodules. I prefer this setup to one big repository mainly because it's easier to deploy. I will be showing you how to deploy with submodules in this tutorial, but I'm sure you can find a way to deploy without using them if you look into it.

#### Installing Dependencies
It's actually very simple to install the pip dependencies if you're using pipenv, which I recommend. Simply navigate to the backend folder and enter:
```bash
$ pipenv install -r requirements.txt
```

### Template Setup
This is much easier to do, because I have set up most things through the CLI, you will still have to set up the git submodules though.
```bash
$ yarn create farm-app --name=farm-stack-tut
```
You might see a popup for the name anyway, I'm working on fixing that, but if you type in the same name, it should work fine.

### Git Setup
Let's set up those submodules now, so there's less work to do later:
Make three new remote repos, one for the frontend, one for the backend, and one for the full app.
In the frontend and backend local repos, run the commands:
```bash
$ git remote add origin <url>
$ git add *
$ git commit -m "first commit"
$ git branch -M main
$ git push -u origin main
```
In the main repo, do these commands once those have been pushed.
```bash
$ git submodule add <frontend-url> frontend
$ git submodule add <backend-url> backend
```
Then commit and push the changes to the main remote repo.

## Making the Backend API
We will start in `main.py`, where we need this code to get started:
```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

origins = ["*"] # This will eventually be changed to only the origins you will use once it's deployed, to secure the app a bit more.

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)

@app.get('/')
def get_root():
    return {"Ping": "Pong"}
```
This is the most basic possible api, and will just serve as a test to make sure we've set everything up properly.
Run the uvicorn command here:
```bash
$ uvicorn main:app --reload
```
If you navigate to `http://localhost:8000`, you should get the message { "Ping": "Pong" } that we set to return. If you did, we can get started building the rest of the backend.

#### MongoDB Atlas
Let's take a quick break and move on to making the database. If you don't plan to deploy this app, you can just use a local MongoDB database, but since I will be deploying my app, I will be using their cloud hosting service. Navigate to [MongoDB Atlas](https://www.mongodb.com/cloud/atlas/lp/try2), and set up a new account, or create a new project if you've used this before. Once a project has been created, you can add a cluster for free, with the "Add a Database" button. Name your cluster and allow it to be created. When it is done, hit the "Browse Collections" button and insert a new database and collection named "TodoDatabase" and "todos" respectively. That's all we need to do for now.

#### Make our Model and Connect to our Database
We're going to need to do two things to push data to our database, the first is to make a model for the data to follow, which we can do in `model.py`. We're going to include 3 strings, a nanoid, a title, and a description, plus a boolean value to check if it is finished or not. The model looks like this:
```python
from pydantic import BaseModel

class Todo(BaseModel):
    nanoid: str
    title: str
    desc: str
    checked: bool
```
The next thing we need to do is actually connect to our database, which is easy enough with motor and pymongo, however, to secure our application, we're going to use an environment variable for the database URI, meaning we're going to need to use python-dotenv now:
```bash
$ pipenv install python-dotenv
```
Create at the root of your backend a .env file, inside place this with the database URI (which you can find by clicking connect on MongoDB Atlas) filled in:
```
DATABASE_URI = "<URI>" 
```
Technically this is only intended to keep our application working on our local machine, as heroku will allow us to insert an environment variable when we deploy, but it's good practice to keep your sensitive data hidden. If you haven't already, make a `.gitignore` file, and put `.env` inside.
Let's connect to the database now.
To do so, we'll first use dotenv to get the URI from our file.
```python
from model import *
import motor.motor_asyncio
from dotenv import dotenv_values
import os

config = dotenv_values(".env")
DATABASE_URI = config.get("DATABASE_URI")
if os.getenv("DATABASE_URI"): DATABASE_URI = os.getenv("DATABASE_URI") #ensures that if we have a system environment variable, it uses that instead

client = motor.motor_asyncio.AsyncIOMotorClient(DATABASE_URI)
```

Now we can make variables for our database and collection, and then make a bunch of functions to modify the collection's data.
```python
database = client.TodoDatabase
collection = database.todos

async def fetch_all_todos():
    todos = []
    cursor = collection.find()
    async for doc in cursor:
        todos.append(Todo(**doc))
    return todos

async def fetch_one_todo(nanoid):
    doc = await collection.find_one({"nanoid": nanoid}, {"_id": 0})
    return doc

async def create_todo(todo):
    doc = todo.dict()
    await collection.insert_one(doc)
    result = await fetch_one_todo(todo.nanoid)
    return result

async def change_todo(nanoid, title, desc, checked):
    await collection.update_one({"nanoid": nanoid}, {"$set": {"title": title, "desc": desc, "checked": checked}})
    result = await fetch_one_todo(nanoid)
    return result

async def remove_todo(nanoid):
    await collection.delete_one({"nanoid": nanoid})
    return True
```

These are all the functions we should need, but feel free to add your own. Let's get some http operations going in `main.py`:
```python
@app.get("/api/get-todo/{nanoid}", response_model=Todo)
async def get_one_todo(nanoid):
    todo = await fetch_one_todo(nanoid)
    if not todo: raise HTTPException(404)
    return todo

@app.get("/api/get-todo")
async def get_todos():
    todos = await fetch_all_todos()
    if not todos: raise HTTPException(404)
    return todos

@app.post("/api/add-todo", response_model=Todo)
async def add_todo(todo: Todo):
    result = await create_todo(todo)
    if not result: raise HTTPException(400)
    return result

@app.put("/api/update-todo/{nanoid}", response_model=Todo)
async def update_todo(todo: Todo):
    result = await change_todo(nanoid, title, desc, checked)
    if not result: raise HTTPException(400)
    return result

@app.delete("/api/delete-todo/{nanoid}")
async def delete_todo(nanoid):
    result = await remove_todo(nanoid)
    if not result: raise HTTPException(400)
    return result
```
Now let's test out these operations by going to `http:localhost:8000/docs` and trying them out.
You should see a screen with all of your operations, and if you click on any of them, it will pop up with this:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kn6sqnwphozr20fgdooz.png)
 
Hit "Try it out" on any of them, but probably start with the add todo one, and then you can perform an operation. Ignore the response for now and check your MongoDB database in the view collections section. You should see a new item, but if you don't you can go back to the response and debug it (you may have to refresh the database if you already had the page open). You should try out the other operations as well, but if all goes well, you should be able to start working on your frontend.

## Frontend
If you know how React works, and you know how to send http requests through axios, I recommend skipping this section, but for the rest of you, here's my version of the frontend.

#### Libraries
I'm using node@16.4.2
- node-sass@6.0.0 (you can use a different version of node-sass and sass-loader depending on your node version, the only reason I'm not using dart sass is the slow compile time)
- sass-loader@12.1.0
- nanoid
- axios
- that's basically it for libraries I'm actually going to use, my template adds react-router as well

### App
Let's start by setting up a nice folder structure (my template, sammy-libraries, does this for me, but this is how I like to set it up):
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/81tp6pg7csj4e4tyhl7o.png)
Now we can get started on our app. 

Let's leave index.tsx alone, and go straight for App.tsx, which should look like this:
```typescript
import React from "react";
import TodoList from "./components/TodoList";

function App() {
    return (
        <div className="app-container">
            <header className="app-header">
                <h1>To-Do List</h1>
            </header>
            <div className="content">
                <TodoList />
            </div>
        </div>
    );
}

export default App;
```
Before we do any styling, let's set up the three other components we're going to need, which are `TodoList.tsx`, `Todo.tsx`, and `AddTodo.tsx`. They should all look basically the same for now, just a div with a className depending on what they are, like this for the todo:
```typescript
import React from "react";

function Todo() {
    return(
        <div className="todo-container">

        </div>
    );
}

export default Todo;
```

Now that we have those components let's define some styles for our app, I will be using SCSS instead of SASS, but this should be easily adaptable into SASS (or CSS if you want to do some extra work). 
Here is the stylesheet I went with for `index.scss`:
```scss
$primary: #146286;
$secondary: #641486;
$accent: #3066b8;

.app-header {
    background-color: $primary;
    color: white;
    padding: 5px;
    border-radius: 10px;
    margin-bottom: 5px;
}

.content {
    .todo-list-container {
        display: grid;
        grid-template-columns: repeat(5, 1fr);
        grid-template-rows: repeat(5, 1fr);
        grid-gap: 10px;

        .todo-container {
            display: flex;
            flex-direction: column;
            justify-content: space-evenly;

            border-radius: 6px;
            padding: 10px 6px;
            background-color: $secondary;
            color: white;

            h1 {
                font-size: 20px;
            }

            span {
                font-size: 14px;
            }

            footer {
                display: flex;
                flex-direction: row-reverse;
            }
        }
    }
}
```

This should be the only styling we need to do, but you can do some extra if you'd like.

Now let's get to work on the components.

The finished App looks like this:
```typescript
import { nanoid } from "nanoid";
import React, { useState } from "react";
import { TodoType } from "./components/Todo";
import TodoList from "./components/TodoList";

function App() {
    const [todoList, setTodoList] =  useState<TodoType[]>([]);

    const [title, setTitle] = useState<string>("");
    const [desc, setDesc] = useState<string>("");

    const changeTitle = (event: React.ChangeEvent<HTMLInputElement>) => {
        setTitle(event.currentTarget.value);
    };

    const changeDesc = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
        setDesc(event.currentTarget.value);
    }

    const changeChecked = (event: React.MouseEvent<HTMLInputElement>, id: string) => {
        let temp = [...todoList];
        temp.forEach((item) => {
            if (item.nanoid === id) {
                item.checked = !item.checked;
            }
        });
        setTodoList(temp);
    };

    const addTodo = (event: React.MouseEvent<HTMLButtonElement>) => {
        let newTodo: TodoType = {
            nanoid: nanoid(),
            title: title,
            desc: desc,
            checked: false
        };
        setTodoList([...todoList, newTodo]);
    }

    return (
        <div className="app-container">
            <header className="app-header">
                <h1>To-Do List</h1>
            </header>
            <div className="content">
                <TodoList submit={addTodo} changeDesc={changeDesc} changeTitle={changeTitle} list={todoList} changeChecked={changeChecked} />
            </div>
        </div>
    );
}

export default App;
```

This performs some very basic functions to pass the props down the tree via react hooks.

The TodoList will look like this:
```typescript
import React from "react";
import AddTodo from "./AddTodo";
import Todo, { TodoType } from "./Todo";

interface TodoListProps {
    list: TodoType[]
    changeChecked: (event: React.MouseEvent<HTMLInputElement>, nanoid: string) => void;
    changeTitle: (event: React.ChangeEvent<HTMLInputElement>) => void;
    changeDesc: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
    submit: (event: React.MouseEvent<HTMLButtonElement>) => void;
}

function TodoList(props: TodoListProps) {
    return(
        <div className="todo-list-container">
            {props.list.map((item) => {
                return(
                    <Todo nanoid={item.nanoid} title={item.title} desc={item.desc} checked={item.checked} changeChecked={props.changeChecked} /> 
                );
            })}
            <AddTodo changeTitle={props.changeTitle} changeDesc={props.changeDesc} submit={props.submit} />
        </div>
    );
}

export default TodoList;
```

The Todo should look like this:
```typescript
import React from "react";

export type TodoType = {
    nanoid: string;
    title: string;
    desc: string;
    checked: boolean;
}

interface TodoProps extends TodoType {
    changeChecked: (event: React.MouseEvent<HTMLInputElement>, nanoid: string) => void;
}

function Todo(props: TodoProps) {
    return(
        <div className="todo-container">
            <h1>{props.title}</h1>
            <span>{props.desc}</span>
            <footer>
                <input type="checkbox" checked={props.checked} onClick={(e) => props.changeChecked(e, props.nanoid)} />
            </footer>
        </div>
    );
}

export default Todo;
```

And finally, the AddTodo should look like this:
```typescript
import React from "react";

interface AddTodoProps {
    submit: (event: React.MouseEvent<HTMLButtonElement>) => void;
    changeTitle: (event: React.ChangeEvent<HTMLInputElement>) => void;
    changeDesc: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
}

function AddTodo(props: AddTodoProps) {
    return(
        <div className="todo-container add-todo-container">
            <input type="text" className="title" placeholder="Title..." onChange={props.changeTitle} />
            <textarea className="desc" placeholder="Description..." onChange={props.changeDesc}>
            </textarea>
            <button className="submit" onClick={props.submit}>Add Todo</button>
        </div>
    );
}

export default AddTodo;
```

Now it's time to use `useEffect()` and axios to store all this data in the database.
This is our final `App.tsx`:
```typescript
import axios from "axios";
import { nanoid } from "nanoid";
import React, { useEffect, useState } from "react";
import { TodoType } from "./components/Todo";
import TodoList from "./components/TodoList";

function App() {
    const [todoList, setTodoList] = useState<TodoType[]>([]);

    const [title, setTitle] = useState<string>("");
    const [desc, setDesc] = useState<string>("");

    useEffect(() => {
        axios
            .get(process.env.REACT_APP_BACKEND_URL + "/api/get-todo")
            .then((res) => {
                setTodoList(res.data);
            });
    }, []);

    const changeTitle = (event: React.ChangeEvent<HTMLInputElement>) => {
        setTitle(event.currentTarget.value);
    };

    const changeDesc = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
        setDesc(event.currentTarget.value);
    };

    const changeChecked = (
        event: React.MouseEvent<HTMLInputElement>,
        id: string
    ) => {
        let temp = [...todoList];
        let tempIndex = 0;
        temp.forEach((item, i) => {
            if (item.nanoid === id) {
                item.checked = !item.checked;
                tempIndex = i;
            }
        });
        setTodoList(temp);
        let item = todoList[tempIndex];
        axios.put(
            process.env.REACT_APP_BACKEND_URL +
                `/api/update-todo/${item.nanoid}`,
                { nanoid: item.nanoid, title: item.title, desc: item.desc, checked: item.checked}
        );
    };

    const addTodo = (event: React.MouseEvent<HTMLButtonElement>) => {
        let newTodo: TodoType = {
            nanoid: nanoid(),
            title: title,
            desc: desc,
            checked: false,
        };
        setTodoList([...todoList, newTodo]);
        axios.post(
            process.env.REACT_APP_BACKEND_URL + "/api/add-todo",
            JSON.stringify(newTodo)
        );
    };

    return (
        <div className="app-container">
            <header className="app-header">
                <h1>To-Do List</h1>
            </header>
            <div className="content">
                <TodoList
                    submit={addTodo}
                    changeDesc={changeDesc}
                    changeTitle={changeTitle}
                    list={todoList}
                    changeChecked={changeChecked}
                />
            </div>
        </div>
    );
}

export default App;
```

Now that that's done, we can get ready to deploy the app.

## Deployment

I will be using Heroku to deploy the backend, and GitHub pages to deploy the frontend. The only real downside I've encountered with Heroku is that if it is idle, the backend has to be restarted whenever it is no longer idle, so you may experience long loading times after breaks in between uses of the app. GitHub Pages is something I've never had a problem with.

### Backend Deployment
Create a new account on Heroku, if you don't have one already, and then create a new app. I find it easiest to deploy through GitHub, but you get more control if you use the Heroku CLI. Regardless, these are the basic steps you have to follow.
Create a new file simply called `Procfile` at the root of the backend, and put this in it:
```
web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app
```
Also make sure to add `python-dotenv == 0.19.0` to your `requirements.txt` file and reinstall dependencies to ensure everything boots properly.
Then go back to `main.py`, and replace the `"*"` in the origins array with `"https://<username>.github.io"`.
Push to github, deploy, and let it go. If it works you should be able to view the same root page we viewed earlier.
Go to app settings, reveal config vars, and put the `DATABASE_URI` in as a config var.

### Frontend Deployment
This is slightly more complicated because we have to install a dependency and edit `package.json`, but it's pretty straight forward still. 
Edit `.env`'s backend url to be the heroku app url, commit and push, then do:
```bash
$ yarn add --dev gh-pages
```
Then you can open up `package.json`, and add these lines to `"scripts"`:
```json
"predeploy": "yarn build",
"deploy": "REACT_APP_BACKEND_URL=<backend-url> gh-pages -d build"
```

Also add:
```json
"homepage": "https://<username>.github.io/<project-name>-frontend/"
```
In github, add a secret that serves as the same environment variable as the backend url, make sure it's named the same.
```bash
$ yarn start
^C
$ yarn deploy
```
If all goes well, you should have a 100% working app.
The source code of this is on github here:
https://github.com/jackmaster110/farm-stack-tut