Production‑Ready React + FastAPI Monorepo
Goal & Why Monorepo (even across languages)
Build a full‑stack platform where React + TypeScript frontend and Python FastAPI backend coexist with generated API clients, shared tooling, and Dockerized development. This isn’t just theory, it’s a production‑grade setup that solves API drift, duplicated types, and slow onboarding.
One PR: schema update → regenerate client → UI + E2E tests. No version mismatch.
OpenAPI → TypeScript client. Removes manual typing & runtime bugs.
Single lint, format, CI, Docker, and
make dev for the whole platform.Only rebuild changed apps → huge productivity gain.
Final Architecture Overview
Frontend (Vite + React) + FastAPI REST API + PostgreSQL + Nginx reverse proxy. All orchestrated via Turborepo and pnpm workspaces. Shared packages (@acme/api-client, @acme/tconfig) eliminate duplication.
📁 Workspace & Turbo Setup
Root package.json drives scripts and delegations to Turborepo. pnpm-workspace.yaml defines apps/* and packages/*.
{
"name": "acme-platform",
"private": true,
"packageManager": "pnpm@10.0.0",
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"lint": "turbo run lint",
"generate:client": "bash scripts/generate-client.sh"
},
"devDependencies": {
"turbo": "^2.0.0",
"typescript": "^5.0.0"
}
}
# pnpm-workspace.yaml
packages:
- apps/*
- packages/*
// turbo.json
{
"schema": "https://turbo.build/schema.json",
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"dev": { "cache": false, "persistent": true },
"lint": {},
"test": {}
}
}
web rebuilds. Backend untouched? No rebuild. Massive time savings on CI/local.⚛️ Frontend App (apps/web)
// apps/web/package.json
{
"name": "web",
"scripts": { "dev": "vite", "build": "tsc && vite build" },
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"acme/api-client": "workspace:*"
}
}
// apps/web/src/App.tsx
import { useEffect, useState } from 'react';
import { UsersService } from '@acme/api-client';
function App() {
const [users, setUsers] = useState([]);
useEffect(() => {
UsersService.getUsers().then(setUsers).catch(console.error);
}, []);
return (<div><h1>Users</h1>{users.map(user => <div key={user.id}>{user.name}</div>)}</div>);
}
export default App;
FastAPI Backend (apps/api)
# apps/api/app/main.py
from fastapi import FastAPI
from app.routes.users import router
app = FastAPI(title="Acme API")
app.include_router(router)
# apps/api/app/routes/users.py
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter(prefix="/users")
class User(BaseModel):
id: int
name: str
@router.get("/", response_model=list[User])
def get_users():
return [User(id=1, name="Michael"), User(id=2, name="Sarah")]
The Real Power: Generated API Client
#!/bin/bash
# scripts/generate-client.sh
curl http://localhost:8000/openapi.json -o packages/api-client/openapi.json
npx openapi-typescript packages/api-client/openapi.json --output packages/api-client/types.ts
packages/api-client/index.ts exports fully typed methods. When backend adds email: str, frontend immediately gets type errors for missing fields. No manual sync, no stale types and this alone prevents countless production bugs.
🐳 Dockerized Local Dev & Production Parity
# docker-compose.yml (simplified)
services:
api:
build: infra/docker/api.Dockerfile
ports: ["8000:8000"]
volumes: ["./apps/api:/app"]
web:
build: infra/docker/web.Dockerfile
ports: ["3000:3000"]
postgres:
image: postgres:17
environment: { POSTGRES_DB: acme }
# infra/docker/api.Dockerfile
FROM python:3.13-slim
WORKDIR /app
COPY apps/api/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY apps/api .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
make dev spins up entire stack. New engineers clone and run with no “it works on my machine” friction.Shared Config & Centralized Standards
// packages/tconfig/base.json
{ "compilerOptions": { "target": "ES2022", "strict": true, "jsx": "react-jsx" } }
// apps/web/tsconfig.json
{ "extends": "@acme/tconfig/base.json", "compilerOptions": { "baseUrl": "." } }
Shared ESLint, prettier, TS config across all apps. No drift, no cross‑repo inconsistency.
🔁 CI/CD with GitHub Actions
# .github/workflows/ci.yml
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm build
- run: pnpm test
- name: Generate API client & lint
run: pnpm generate:client && pnpm lint
🧠 10 Production‑Ready Best Practices
- Never share runtime secrets – isolate
.envper app. - Workspace dependencies:
"workspace:*"ensures local linking, never version ranges. - Avoid circular dependencies – apps must not import each other directly.
- Strict TypeScript everywhere (
"strict": true). - Infrastructure as code – keep infra configs in root
/infra, not inside apps. - Health checks –
curl -f http://localhost:8000/healthfor startup race conditions. - Automate client generation – run on every API change or CI preflight.
- Env validation – frontend check
VITE_API_URL, backend Pydantic settings. - Dependency boundaries – never
apps/webimportsapps/api. Shared code lives inpackages/. - Pin tool versions – avoid
"latest", use exact semver in package.json and actions.
⚠️ Avoiding Build‑Time Failures
pnpm install and use "workspace:*".CMD ["uvicorn", "app.main:app"]"outputs": ["dist/**"].Deployment Strategy
Option 1 — Unified deployment: Single pipeline builds both Docker images, suitable for tightly coupled teams. Option 2 — Independent deployments: Monorepo does NOT force monolith deployment. You can deploy frontend (Vercel/Netlify) and backend separately while sharing types.
Why Mature Teams Love This
Monorepos drastically reduce coordination overhead, integration bugs, stale dependency hell, duplicated configs, and API mismatch friction. Combined with Turborepo caching, local development stays fast and reliable.
Final Recommendation
For teams owning a full‑stack product with React + TypeScript + FastAPI + PostgreSQL + Docker, this monorepo blueprint is production‑grade, scalable, and used in modern engineering orgs. It’s especially beneficial when:
- One team owns frontend & backend evolution.
- API contracts change frequently and require immediate type safety.
- Rapid iteration and developer experience is a top priority.
- You value refactoring safety and atomic changes.
One clone. make dev. Everything works. That’s the promise.
Built with production patterns — Turborepo, openapi-typescript, and Docker. Senior‑engineer approved. 🚀

Comments
Post a Comment