Monorepo Foundations

Monorepo Foundations

Our project utilizes a monorepo architecture managed by pnpm and Turborepo to streamline development, ensure consistency, and optimize build processes across multiple applications and shared packages.

Core Concepts

  • pnpm Workspaces: Manages dependencies across the entire project, allowing shared libraries and efficient installation.
  • Turborepo: Provides intelligent build caching and task orchestration, significantly speeding up builds and tests.

Package Management

  • pnpm is the designated package manager (v10.6.5 or higher).
  • Use workspace:* syntax in package.json files to link internal packages.
  • Install all dependencies from the root directory:
    pnpm install

Workspace Structure

The monorepo is organized into two primary directories at the root:

  • apps/: Contains deployable applications.
    • web/: The main web application (Vite + React Router).
    • native/: The React Native mobile application (Expo).
  • packages/: Contains shared libraries, utilities, and configurations used across applications.
    • api/: Backend API server (Hono) and client types.
    • api-hooks/: TanStack Query hooks generated from the API spec.
    • auth/: Authentication logic and utilities.
    • config/: Shared configurations (ESLint, TypeScript, etc.).
    • database/: Drizzle ORM schema, client, and migration utilities.
    • email/: Email templates and sending logic.
    • i18n/: Internationalization setup.
    • payments/: Payment provider integrations (e.g., Stripe).
    • push-notifications/: Push notification setup.
    • storage/: Cloud storage utilities.
    • typescript-config/: Base TypeScript configurations.

Common Commands

Execute these commands from the root of the monorepo:

# Install all dependencies pnpm install # Start all applications in development mode pnpm dev # Build all applications and packages pnpm build # Run linters across the codebase pnpm lint # Run tests for all packages pnpm test # Clean all build artifacts and node_modules pnpm clean # Start a specific application (e.g., web) pnpm --filter web dev # Run a command within a specific package (e.g., generate DB migrations) pnpm --filter @repo/database db:generate

Adding Dependencies

Always use pnpm with the --filter flag to add dependencies to specific packages:

# Add a production dependency to 'web' app pnpm --filter web add react-map-gl # Add a development dependency to 'api' package pnpm --filter @repo/api add -D @types/node

Add shared development tools (like linters, test runners) to the root package.json:

# Add husky as a root development dependency pnpm add -Dw husky

Creating New Packages

  1. Create the new directory under packages/ (e.g., packages/new-utility).
  2. Initialize a package.json (e.g., pnpm init). Ensure the package name is scoped (e.g., @repo/new-utility).
  3. Set up tsconfig.json, extending from @repo/typescript-config/base.json.
  4. Add necessary build/dev scripts to its package.json.
  5. Update pnpm-workspace.yaml if necessary (usually automatic for packages/*).
  6. Configure its pipeline tasks in the root turbo.json.

Turborepo (turbo.json)

Turborepo orchestrates tasks defined in the pipeline section of turbo.json. Key aspects include:

  • Task Dependencies (dependsOn): Defines the execution order (e.g., build depends on ^build, meaning build dependencies first).
  • Caching (cache, outputs): Specifies which files/outputs to cache, speeding up subsequent runs.
  • Inputs: Defines files/env variables that influence a task's output. Changes to inputs invalidate the cache.

Refer to the root turbo.json for the specific pipeline configuration.