Overview
With ChatGPT and the evolution of frameworks, it is easier than ever to get started creating custom, professional-looking tools quickly. One of the problems however, is that web moves fast and while easy to create a new project in minutes for a given technology stack, it is difficult to understand which is best for your application and where to invest your time.
Goals
Identify pros and cons of current web frameworks for a multi-user intranet CRUD type application
Deploy via Docker Compose
Document steps on how to clone and run in local development
Build basic CRUD application
Use self-contained database server to start
Identify ORM or database interface libraries
Identify how to switch to externally hosted database
Basic user management and authentication
- start with basic; then REST API keys; then LDAP or SSL options?
Prerequisites
- For the sake of this write-up, I’m using Windows, VSCode and WSL w/Docker.
Notes
- For all references to React (unless otherwise stated), I’m using (or intend to use) Vite+React+Typescript+Bootstrap.
Comparison Table
Aspect | React + Django | React + NestJS + TypeORM | React + FastAPI |
Language Compatibility | Python + TypeScript | TypeScript End-to-End | Python + TypeScript |
Ease of Use | High (batteries-included) | Medium (steeper learning) | High (minimal boilerplate) |
Performance | Moderate | High | High |
Built-In Features | Extensive (admin, auth) | Few (modular, flexible) | Minimal |
Scalability | Moderate | High | Moderate-High |
Learning Curve | Low-Moderate | High | Low |
Community Support | Mature and Large | Growing and Active | Medium but Growing |
Admin Panel | Built-In | Third-Party Required | Third-Party Required |
Best For | Rapid development | Scalable, modular projects | Lightweight, fast APIs |
React-Django
Django provides a comprehensive framework with built-in ORM (Django ORM), authentication, admin panel, and other utilities.
Getting Started from Scratch
I came across the following YouTube (https://youtu.be/GbQwi_UB4FE?si=4zWhJ5MRSM-yggK5) and recommend it for the basics.
https://londonappdeveloper.com/add-backend-to-react-with-django-rest-framework-and-docker/
Getting Started from Clone
git clone git@github.com:ericjameszimmerman/react-django-web.git
cd react-django-web
docker compose up --build --watch
# front-end: http://localhost:5173/
# back-end: http://localhost:8000/api/hello-world/
Output
The REST API from Django backend runs separately from the front-end application.
Backend
The Django REST framework includes its own UI which can be handy for debug and reference.
Frontend
Per the example, clicking on the button, calls the “hello-world” api and updates the displayed content.
Plain React-Bootstrap
I like the Django path since I’m stronger in Python than typescript / javascript. On the React side, however, I prefer using Typescript and Bootstrap if possible. I found the following walkthrough:
https://tsmx.net/react-typescript-bootstrap-vite/
Getting Started from Scratch
npm create vite frontend -- --template react-ts
cd frontend
npm install
npm run dev
# cancelled running and install Bootstrap
q + <enter>
npm install react-bootstrap bootstrap
# Add bootstrap css to main.tsx
import 'bootstrap/dist/css/bootstrap.min.css';
Updated App.tsx
I added a card from Bootstrap 5 reference documentation
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<div className="card" style={{ width: '18rem' }}>
<div className="card-body">
<h5 className="card-title">Card title</h5>
<h6 className="card-subtitle mb-2 text-muted">Card subtitle</h6>
<p className="card-text">
Some quick example text to build on the card title and make up the bulk of the card's content.
</p>
<a href="#" className="card-link">
Card link
</a>
<a href="#" className="card-link">
Another link
</a>
</div>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
}
export default App
Getting Started from Clone
Since this is the plain template / scaffolding only, I’m not committing this to GitHub. (however, I’m planning to merge this into the Django example.)
git clone <your url>
cd frontend
npm install
npm run dev
Output
NestJS-TypeORM
NestJS promotes a modular architecture, making large applications easier to manage. Provides excellent support for SQL and NoSQL databases and integrates seamlessly with NestJS.
Getting Started from Scratch
I don’t know much about NestJS, but it showed up in my research, and I stumbled upon a recent YouTube. (https://youtu.be/9MGKKJTwicM?si=akXSEsT2PJma-dcr).
# Install Nest cli
npm install -g @nestjs/cli
# Create the project
nest new nestjs-typeorm-web
cd nestjs-typeorm-web
pnpm run start
Docker and MySQL
# install typeorm and mysql
pnpm i @nestjs/typeorm typeorm mysql2
pnpm i @nestjs/config
# retest
pnpm run start:dev
Add “docker-compose.yaml”
services:
mysql:
image: mysql
env_file:
- .env
ports:
- '3306:3306'
Add .env file
MYSQL_ROOT_PASSWORD=randomrootpassword
MYSQL_DATABASE=nestjstypeorm
Start via Docker Compose and verify MySQL
docker compose up
# Download MySQL Workbench if you don't already have it: https://www.mysql.com/products/workbench/
When attempting to connect, I received an odd error: “MySQL Workbench incompatible/nonstandard server”.
All you need to do is, after opening MySQL Workbench, and instead of setting up a new connection, Press CTRL+R or click on DATABASE tab in the top menu. Select Reverse Engineer and provide necessary information. You are good to go now.
…and sure enough
…at this point I realized that the tutorial I was reference is developing a back-end only, and I want to create a minimal full stack for all of these options, so pausing this path for the moment.
NestJS-TypeORM-React
Since my first attempt was back-end only, I looked for a tutorial specifically targeting a React frontend.
https://www.youtube.com/watch?v=nY0R7pslbCI
NestJS promotes a modular architecture, making large applications easier to manage. Provides excellent support for SQL and NoSQL databases and integrates seamlessly with NestJS.
Getting Started from Scratch
mkdir nestjs-typeorm-react
cd nestjs-typeorm-react
npm install -D turbo
# Add the following to package.json
"workspaces": [
"apps/*"
]
# create folder for housing multiple independent projects in a mono-repo
mkdir apps
cd apps
# create the backend
# choose "npm" for package manager
nest new api
# create the frontend
# Select React and Typescript
npm create vite@latest frontend
# node; a few tweaks for the following:
# - turbo.json config
# - vite config change to proxy to the backend
I modified the returned api text to be sure I wasn’t on an old server.
Updated App.tsx
import { useEffect, useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
const [greeting, setGreeting] = useState(0)
useEffect(() => {
fetch('/api')
.then((res) => res.text())
.then(setGreeting);
}, []);
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>{greeting}</h1>
</>
)
}
export default App
Configuration for serving React app as static, for single page application method rather than running two services.
npm install --workspace api @nestjs/serve-static
# added the following to app.module.ts (within imports [])
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, '../..', 'frontend', 'dist'),
}),
],
# added "start" script to root package.json
The front end is now statically served from the back-end for the production build.
Getting Started from Clone
git clone
cd
# for development build
npm run dev
# for production build
npm run build
# run production
npm start
Deferring the Docker portion for the moment…
FastAPI-React
I’ve heard some buzz about FastAPI recently, so want to throw that into the mix as well.
FastAPI is highly performant and designed to support asynchronous programming, making it suitable for real-time applications. Simple to learn and use, with automatic generation of interactive API docs (Swagger/OpenAPI).
Getting Started from Scratch
The links below include instructions, so I’ll list the Cliff Notes version here. Note that a FastAPI project template is only available on PyCharm Professional, so I’m ignoring that part and editing from VSCode and command-line.
References
Backend Setup
mkdir fastapi-react
cd fastapi-react
mkdir backend
cd backend
# create `requirements.txt` in the `backend` folder, containing the following
fastapi
fastapi[standard]
uvicorn
pydantic
# create virtual environment
python -m venv venv
.\venv\Scripts\activate
pip install -r requirements.txt
backend/main.py
Simple Hello World API from https://fastapi.tiangolo.com/tutorial/first-steps/
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
Initial backend test
fastapi dev main.py
Frontend Setup
This is the same as earlier.
Note that like Django, we end up with essentially two separate services. I would imagine there might be ways to make the React portion statically served like we did in NestJS, but there are some benefits to separate hosting and we’ll like use Docker Compose anyway.
cd <root project directory>
# Choose React and Typescript when prompted
# Note that the tutorial used Javascript instead of Typescript
npm create vite@latest frontend --template react
cd frontend
npm install
npm run dev
Connect Frontend to access Backend
The tutorial goes on to adjust for Cross-Origin Resource Sharing (CORS), which prohibits unauthorized websites from access the API, so adjusted main.py accordingly.
Backend
New main.py
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List
app = FastAPI()
origins = [
"http://localhost:5173"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def root():
return {"message": "Hello World"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Frontend
npm install axios
npm install --save-dev @types/axios
It was recommended to add an api.ts
to centralize the base URL.
import axios, { AxiosInstance } from 'axios';
// Create an instance of axios with the base URL
const api: AxiosInstance = axios.create({
baseURL: "http://localhost:8000",
});
// Export the Axios instance
export default api;
Here is the updated App.tsx
import { useEffect, useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import api from './api';
function App() {
const [greeting, setGreeting] = useState<string>('')
useEffect(() => {
const fetchGreeting = async () => {
try {
const response = await api.get('/');
setGreeting(response.data.message);
} catch (error) {
console.error("Error fetching greeting:", error);
}
};
fetchGreeting();
}, []);
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>{greeting}</h1>
</>
)
}
export default App
Getting Started from Clone
I imagine this can be more automated in the future, but here are the present steps.
git clone git@github.com:ericjameszimmerman/fastapi-react.git
cd fastapi-react
# Backend
cd backend
python -m venv venv
.\venv\Scripts\activate
pip install -r requirements.txt
python .\main.py
# Front End
cd frontend
npm install
npm run dev