Monorepo Blueprint

Production-Ready React + FastAPI Monorepo Blueprint | Senior Engineer Series

Production‑Ready React + FastAPI Monorepo

React 19 + TS FastAPI + SQLAlchemy Turborepo · pnpm Docker + Nginx OpenAPI → TypeScript
Real‑world blueprint: unified type safety, atomic full‑stack changes, and CI/CD‑ready architecture. One repo : zero friction.

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.

Atomic changes
One PR: schema update → regenerate client → UI + E2E tests. No version mismatch.
Generated Type Safety
OpenAPI → TypeScript client. Removes manual typing & runtime bugs.
Unified tooling
Single lint, format, CI, Docker, and make dev for the whole platform.
Turbo‑charged caching
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.

FrontendReact 19, Vite, TypeScript, Tailwind-ready
BackendFastAPI, SQLAlchemy, Pydantic, Uvicorn
DatabasePostgreSQL 17^
Package Managerpnpm (workspaces) + Turborepo
Container / ProxyDocker Compose + Nginx
API Client Generationopenapi-typescript + auto‑generated types
CI/CDGitHub Actions, build caching, health checks

📁 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": {}
  }
}
💡 Turbo impact: Change only frontend? Only 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.

Senior insight: Monorepo allows you to evolve your API contract and frontend usage in one commit, with CI validating everything. This eliminates “backend changed, frontend broke next week” anti‑pattern.

🐳 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"]
One command: 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 .env per 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 checkscurl -f http://localhost:8000/health for 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/web imports apps/api. Shared code lives in packages/.
  • Pin tool versions – avoid "latest", use exact semver in package.json and actions.

⚠️ Avoiding Build‑Time Failures

Problem 1: "Module not found" → workspace not linked. Fix: pnpm install and use "workspace:*".
Problem 2: OpenAPI generation fails → backend not running. Start API before generating client.
Problem 3: Docker container exits → CMD misconfiguration. Use explicit CMD ["uvicorn", "app.main:app"]
Problem 4: Turbo cache wrong → declare "outputs": ["dist/**"].
Problem 5: Frontend crashes at runtime → missing runtime env validation. Add guard clauses.
Problem 6: Frontend/backend type mismatch → forgot client regeneration. Add to CI hooks.

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.

🔄 Production flow: Developer changes schema → FastAPI updates → OpenAPI regenerated → TS client updated → Frontend adapts → Tests pass → Docker images built → CI validates everything → Atomic deploy.

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