Web Frameworks 2025 Evaluation - Part 1

Overview and Basics

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

AspectReact + DjangoReact + NestJS + TypeORMReact + FastAPI
Language CompatibilityPython + TypeScriptTypeScript End-to-EndPython + TypeScript
Ease of UseHigh (batteries-included)Medium (steeper learning)High (minimal boilerplate)
PerformanceModerateHighHigh
Built-In FeaturesExtensive (admin, auth)Few (modular, flexible)Minimal
ScalabilityModerateHighModerate-High
Learning CurveLow-ModerateHighLow
Community SupportMature and LargeGrowing and ActiveMedium but Growing
Admin PanelBuilt-InThird-Party RequiredThird-Party Required
Best ForRapid developmentScalable, modular projectsLightweight, 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

💡
Normally React would default to port 5173, but it was already in use with the Django example, so it picked the next available port.

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