Skip to content

Latest commit

 

History

History
385 lines (282 loc) · 11.5 KB

README.md

File metadata and controls

385 lines (282 loc) · 11.5 KB

FastAPI + React Slim

Dependabot Status

fastapi-logo react-logo     react-admin     react-logo     react-logo sql-alchemy openai-logo

A cookiecutter template for bootstrapping a FastAPI and React project using a modern stack.


Features

Table of Contents

Background

It is often laborsome to start a new project. 90% of the time you have to decide how to handle authentication, reverse proxies, docker containers, testing, server-side validation, linting, etc. before you can even get started.

FastAPI-React-Slim serves to streamline and give you that functionality out of the box.

It is meant as a lightweight/React alternative to FastAPI's official fullstack project. If you want a more comprehensive project in Vue, I would suggest you start there. A lot of the backend code is taken from that project or the FastAPI official docs.

Quick Start

Cookiecutter

First, install cookiecutter if you don't already have it:

python3 -m pip install cookiecutter

Second, install docker-compose if you don't already have it:

docker-compose installation official docs.

Then, in the directory you want your project to live:

cookiecutter --no-input gh:will-wright-eng/fastapi-react-slim

Without --no-input you will need to put in a few variables and it will create a project directory (called whatever you set for project_slug).

Input Variables
  • project_slug [default fastapi-react-slim-project] - this is your project directory
  • project_name [default fastapireactslimproject]
  • project_name_title [default Fastapi React Slim Project]
  • port [default 8000]
  • postgres_user [default postgres]
  • postgres_password [default password]
  • postgres_database [default app]
  • superuser_email [default [email protected]]
  • superuser_password [default password]
  • secret_key [default super_secret]

Deploy Locally

The following commands with initalize the app, build and start all the containers, then tail the docker-compose logs

cd {{cookiecutter.project_slug}}
make open # opens browser where your app will be available
make init # details below

Once you see the following logs, reload your browser

{{cookiecutter.project_slug}}-frontend-1  | Starting the development server...
{{cookiecutter.project_slug}}-frontend-1  | Compiled successfully!

Develop

Step-by-step

Change into your project directory and run:

chmod +x scripts/build.sh
./scripts/build.sh

This will build and run the docker containers, run the alembic migrations, and load the initial data (a test user).

It may take a while to build the first time it's run since it needs to fetch all the docker images.

Once you've built the images once, you can simply use regular docker-compose commands to manage your development environment, for example to start your containers:

docker-compose up -d

Once this finishes you can navigate to the port set during setup (default is localhost:8000), you should see the slightly modified create-react-app page:

default create-react-app

Note: If you see an Nginx error at first with a 502: Bad Gateway page, you may have to wait for webpack to build the development server (the nginx container builds much more quickly).

Login screen: regular login

The backend docs will be at http://localhost:8000/api/docs. API Docs

Admin Dashboard

This project uses react-admin for a highly configurable admin dashboard.

After starting the project, navigate to http://localhost:8000/admin. You should see a login screen. Use the username/password you set for the superuser on project setup.

NOTE: regular users will not be able to access the admin dashboard

React Adming Login

You should now see a list of users which you can edit, add, and delete. The table is configured with the REST endpoints to the FastAPI /users routes in the backend.

React Admin Dashboard

The admin dashboard is kept in the frontend/src/admin directory to keep it separate from the regular frontend.

Security

To generate a secure key used for encrypting/decrypting the JSON Web Tokens, you can run this command:

openssl rand -hex 32

The default is fine for development but you will want something more secure for production.

You can either set this on project setup as secret_key or manually edit the Python SECRET_KEY variable in backend/app/core/security.py.

Testing

This project comes with Pytest and a few Pytest fixtures for easier mocking. The fixtures are all located in backend/conftest.py within your project directory.

All tests are configured to run on a test database using SQLAlchemy transactions to reset the testing state on each function. This is to avoid a database call affecting the state of a different test.

Fixtures

These fixtures are included in backend/conftest.py and are automatically imported into any test files that being with test_.

test_db

The test_db fixture is an empty test database and an instance of a SQLAlchemy Session class.

def test_user(test_db):
    assert test_db.query(models.User).all()

test_user

def test_user_exists(test_user):
    assert test_user.email == "[email protected]"

test_superuser

def test_superuser(client, test_superuser):
    assert test_superuser.is_superuser

client

To use an unauthenticated test client, use client:

def test_get_users(client):
    client.get("/api/v1/users")
    assert response.status_code == 200

user_token_headers

If you need an authenticated client using OAuth2 and JWTs:

def test_user_me(client, user_token_headers):
    response = client.get(
      "/api/v1/users/me",
      headers=user_token_headers,
    )
    assert response.status_code == 200

Since OAuth2 expects the access token in the headers, you will need to pass in user_token_headers as the headers argument in any client request that requires authentication.

superuser_token_headers

def test_user_me(client, superuser_token_headers):
    response = client.get(
      "/api/v1/users",
      headers=superuser_token_headers,
    )
    assert response.status_code == 200

Frontend Utilities

There are a few helper methods to handle authentication in frontend/src/utils. These store and access the JWT returned by FastAPI in local storage. Even though this doesn't add any security, we prevent loading routes that might be protected on the frontend, which results in a better UX experience.

Utility Functions

login

// in src/utils/auth.ts

/**
 *  Handles authentication with backend and stores in JWT in local storage
 **/
const login = (email: string, password: string) => boolean;

logout

// in src/utils/auth.ts

// clears token from local storage
const logout = (email: string, password: string) => void;

isAuthenticated

// Checks authenticated state from JWT tokens
const isAuthenticated = () => boolean;

Routes

Some basic routes are included (and handled in frontend/Routes.tsx).

  • /login - Login screen
  • /logout - Logout
  • / - Home
  • /protected - Example of protected route

Higher Order Components

PrivateRoute

This handles routes that require authentication. It will automatically check whether the correct token with the "user" permissions is present or redirect to the home page.

// in src/Routes.tsx
import { Switch } from 'react-router-dom';

// Replace this with your component
import { ProtectedComponent } from 'components';

const Routes = () => (
  <Switch>
    <PrivateRoute path="/protected_route" component={ProtectedComponent} />
  </Switch>
);

Deployment

This stack can be adjusted and used with several deployment options that are compatible with Docker Compose, but it may be easiest to use Docker in Swarm Mode with an Nginx main load balancer proxy handling automatic HTTPS certificates, using the ideas from DockerSwarm.rocks.

Please refer to DockerSwarm.rocks to see how to deploy such a cluster easily. You will have to change the Traefik examples to Nginx or update your docker-compose file.

Contributing

Contributing is more than welcome. Please read the Contributing doc to find out more.