Part 4: Hybrid and Advanced Techniques¶
16. Monorepos and Shared Logic¶
“When frontend and backend speak the same language, everything moves faster.”
Why Consider a Monorepo?¶
As your project grows to include:
- A React + Vite frontend
- A FastAPI backend
- Shared validation schemas
- Auth/session logic reused across layers
- Common constants or types (e.g., document status, GPT response formats)
…it becomes inefficient to split them across multiple repos.
A monorepo solves this by keeping everything in one place:
- Easier dev experience
- Shared logic is centralized
- Faster refactoring
- Smoother version control
- Unified CI/CD
You stop duplicating logic—and start aligning systems.
Example Monorepo Folder Layout¶
project-root/
├── apps/
│ ├── frontend/ # React + Vite app
│ └── backend/ # FastAPI app
├── packages/
│ ├── shared-schemas/ # Pydantic + TypeScript shared types
│ ├── utils/ # Shared logic, constants, helpers
│ └── gpt-core/ # Shared GPT prompt logic and formatting
├── .github/
│ └── workflows/
├── docker/
│ ├── docker-compose.yml
├── tsconfig.base.json
├── pyproject.toml / requirements.txt
├── README.md
└── turbo.json / nx.json / package.json
Shared Logic That Actually Matters¶
Shared Concern | Example |
---|---|
Schemas | Upload payloads, response DTOs, user objects |
Types/Enums | DocumentStatus, ModelSource, RoleType |
Prompt templates | GPT input formats shared across frontend + backend |
Utils | Token counters, validators, text normalization |
Constants | Max tokens, file size limits, system settings |
Keeping these in packages/
avoids:
- Duplication
- Divergent validation logic
- Fragile JSON conversions
Setting Up Shared Packages¶
Let’s say you use:
- TypeScript in frontend
- Python in backend
- Shared schemas in both
For TypeScript:¶
Use pnpm workspaces or Turborepo to manage shared packages.
package.json
(root):
{
"private": true,
"workspaces": [
"apps/*",
"packages/*"
]
}
Example: packages/shared-schemas/src/index.ts
export interface ChatRequest {
message: string
model?: "gpt-3.5" | "gpt-4"
}
Then in frontend:
import { ChatRequest } from "@shared-schemas"
For Python (Backend):¶
Use poetry
, pip install -e .
, or a dedicated shared/
module.
Example: packages/shared_schemas/chat.py
from pydantic import BaseModel
class ChatRequest(BaseModel):
message: str
model: str = "gpt-3.5"
Then in backend/app/api/chat.py
:
from shared_schemas.chat import ChatRequest
💡 Want even tighter sync? Use datamodel-code-generator to generate Pydantic models from TypeScript types, or vice versa.
Tools for Managing Monorepos¶
Tool | Use Case |
---|---|
Turborepo | JS monorepo with React + shared packages |
pnpm | Fast dependency management and workspace linking |
Nx | Task orchestration and code boundaries across apps |
Poetry | Python package management for shared backends |
Docker Compose | Dev containers for both frontend + backend |
GitHub Actions | Per-folder CI pipelines and test triggers |
Use monorepo-aware tooling to:
- Avoid rebuilding everything on every commit
- Run only affected tests or builds
- Deploy changed services selectively
Testing Shared Logic¶
Create shared test folders inside packages:
packages/
├── shared-schemas/
│ └── __tests__/
├── gpt-core/
│ └── test_templates.py
Run tests locally or in CI by glob pattern:
pytest packages/gpt-core/
vitest run packages/shared-schemas/
This ensures shared logic stays robust without depending on app-specific tests.
Versioning Shared Logic¶
You have two choices:
- Tightly coupled: Always deploy shared changes with apps (e.g., GPT prompt template update used by both frontend and backend)
- Loosely versioned: Use semver tags, changelogs, and publishing flow (e.g.,
shared-schemas@1.2.3
)
For solo projects or fast-moving MVPs → tight coupling is fine.
For production teams → use versioning tools like:
- Changesets
- Lerna
- Private NPM/PyPI registries
Recap: Folder Responsibilities¶
Folder | Purpose |
---|---|
apps/frontend/ |
Vite, React, TSX UI |
apps/backend/ |
FastAPI app |
packages/shared-schemas/ |
DTOs, request/response validation |
packages/gpt-core/ |
Prompt templates, chunking, completions |
packages/utils/ |
String, token, image utils |
docker/ |
Container orchestration configs |
.github/workflows/ |
CI/CD pipelines (monorepo-aware) |
Summary: Monorepos and Shared Logic¶
Principle | Practice |
---|---|
Keep everything in one place | Use apps/ + packages/ monorepo model |
Share schemas and types safely | Export DTOs from packages/shared-schemas/ |
Avoid copy-paste logic | Extract GPT, OCR, validation logic to shared modules |
Use smart tooling | Turborepo + Poetry + Docker Compose + CI matrix builds |
Test + version shared logic | Keep separate test suites and consider semver tagging |
Monorepos give you the clarity of modularity with the power of shared logic.
This is how real products grow—from prototypes to platforms.