Monorepo Structure Skill
Overview
This skill covers the monorepo architecture, workspace management, and development workflows for the Game Critique project using Turborepo and Yarn workspaces.
Monorepo Architecture
Project Structure
Game-Critique/
├── .github/ # GitHub configs and workflows
│ └── skills/ # Copilot skills
├── apps/ # Application workspaces
│ ├── api/ # NestJS GraphQL API
│ ├── native/ # Expo React Native app
│ └── web/ # React admin panel
├── packages/ # Shared packages
│ └── typescript-config/ # Shared TypeScript configs
├── package.json # Root package.json
├── turbo.json # Turborepo configuration
├── yarn.lock # Yarn lockfile
└── compose.yml # Docker Compose setup
Package Manager: Yarn 4.5.3
Workspace Configuration
// package.json (root)
{
"private": true,
"workspaces": [
"./apps/*",
"./packages/*"
],
"packageManager": "yarn@4.5.3",
"engines": {
"node": ">=18"
}
}
Yarn Commands
# Install dependencies for all workspaces
yarn install
# Add dependency to specific workspace
yarn workspace api add @nestjs/graphql
yarn workspace web add @tanstack/react-query
yarn workspace native add tamagui
# Add dev dependency
yarn workspace api add -D jest
# Remove dependency
yarn workspace api remove @nestjs/graphql
# Run script in workspace
yarn workspace api dev
yarn workspace web build
# Run script in all workspaces
yarn workspaces foreach run build
yarn workspaces foreach run test
# Interactive upgrade
yarn upgrade-interactive
Turborepo Configuration
Basic Setup
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"tasks": {
"build": {
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"dependsOn": ["^build"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"cache": false,
"persistent": false
},
"lint": {
"outputs": []
},
"clean": {
"cache": false
}
}
}
Task Orchestration
Build Dependencies:
^buildmeans "build dependencies first"- Ensures packages are built before apps that depend on them
Caching:
cache: falsefor dev servers and testspersistent: truefor long-running processes- Outputs specified for build artifacts
Turborepo Commands
# Run tasks across all workspaces
turbo run build # Build all apps
turbo run dev # Start all dev servers
turbo run test # Run all tests
turbo run lint # Lint all code
# Run task for specific workspace
turbo run build --filter=api
turbo run dev --filter=web
turbo run test --filter=native
# Run task for multiple workspaces
turbo run dev --filter=web --filter=api
# Force run (ignore cache)
turbo run build --force
# Clear cache
turbo run clean
Root Scripts
// package.json (root)
{
"scripts": {
"dev": "turbo run dev",
"dev:web": "turbo run dev --filter=web --filter=api",
"dev:api": "turbo run dev --filter=api",
"build": "turbo run build",
"test": "turbo run test",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path .gitignore",
"clean": "turbo run clean && rm -rf node_modules"
}
}
Shared Packages
TypeScript Configuration Package
packages/
typescript-config/
├── package.json
├── base.json # Base config
├── nestjs.json # NestJS-specific
├── nextjs.json # Next.js-specific
└── react-native-library.json
Package.json:
{
"name": "@repo/typescript-config",
"version": "0.0.0",
"private": true,
"files": [
"base.json",
"nestjs.json",
"nextjs.json",
"react-native-library.json"
]
}
Base Config:
// packages/typescript-config/base.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
}
}
NestJS Config:
// packages/typescript-config/nestjs.json
{
"extends": "./base.json",
"compilerOptions": {
"module": "commonjs",
"target": "ES2021",
"lib": ["ES2021"],
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"incremental": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"strictPropertyInitialization": false
}
}
Using Shared Config
// apps/api/tsconfig.json
{
"extends": "@repo/typescript-config/nestjs.json",
"compilerOptions": {
"outDir": "./dist",
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Workspace Dependencies
Internal Workspace References
// apps/api/package.json
{
"name": "api",
"dependencies": {
"@repo/typescript-config": "*"
}
}
Version Management
- Use
*for internal workspace dependencies - Yarn resolves to local workspace automatically
- Changes to packages immediately available to apps
Development Workflows
Starting Development Environment
Full Stack:
# Start all services
yarn dev
# API + Web only
yarn dev:web
# API only
yarn dev:api
Individual Apps:
# API with database
cd apps/api
yarn start:db # Start PostgreSQL + Redis
yarn dev # Start API in watch mode
# Web admin panel
cd apps/web
yarn dev # Starts on port 5173
# Native mobile app
cd apps/native
yarn dev-local # With local API
yarn dev # With production API
Building for Production
# Build all apps
yarn build
# Build specific app
cd apps/api && yarn build
cd apps/web && yarn build
# Build with Turborepo
turbo run build --filter=api
turbo run build --filter=web
Code Quality
# Lint all workspaces
yarn lint
# Format all code
yarn format
# Workspace-specific
cd apps/api && yarn lint
cd apps/web && yarn format
Environment Variables
Organization
apps/
api/
.env
.env.local
.env.example
web/
.env
.env.local
native/
.env.local
.env.production
Loading Environment Variables
API (NestJS):
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
],
})
export class AppModule {}
Web (Vite):
// Access with import.meta.env
const apiUrl = import.meta.env.VITE_API_URL;
Native (Expo):
// Access with process.env.EXPO_PUBLIC_*
const apiUrl = process.env.EXPO_PUBLIC_GRAPHQL_ENDPOINT;
Turbo Global Dependencies
// turbo.json
{
"globalDependencies": ["**/.env.*local"]
}
This ensures Turbo cache is invalidated when env files change.
Docker Compose Integration
Root Compose File
# compose.yml (root)
name: game-critique
include:
- path: apps/api/compose.yml
API Services
# apps/api/compose.yml
services:
postgres:
image: postgres:15
ports:
- "5432:5432"
environment:
POSTGRES_USER: game_critique
POSTGRES_PASSWORD: password
POSTGRES_DB: game_critique
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
Docker Commands
# Start services (from root or apps/api)
docker-compose up -d
# Stop services
docker-compose down
# View logs
docker-compose logs -f postgres
docker-compose logs -f redis
# Reset databases
docker-compose down -v # Removes volumes
Git Hooks with Husky
Setup
// package.json (root)
{
"scripts": {
"prepare": "husky install"
},
"lint-staged": {
"*.{ts,tsx}": [
"prettier --write"
]
}
}
Pre-commit Hook
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
GraphQL Schema Sharing
Configuration
# graphql.config.yml (root)
schema: "apps/api/src/schema.gql"
Workflow
- API generates schema on startup
- Schema file at
apps/api/src/schema.gql - Web and Native reference this schema for codegen
- IDE uses schema for autocomplete
// apps/web/codegen-dev.ts
const config: CodegenConfig = {
schema: "http://localhost:3001/graphql", // Or file path
documents: "src/**/*.graphql",
// ...
};
// apps/native/codegen.ts
const config: CodegenConfig = {
schema: process.env.EXPO_PUBLIC_GRAPHQL_ENDPOINT,
documents: ["modules/**/*.graphql"],
// ...
};
Adding a New Workspace
1. Create Workspace Directory
mkdir -p apps/new-app
cd apps/new-app
2. Initialize Package
yarn init -p
3. Configure Package.json
{
"name": "new-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "...",
"build": "...",
"test": "..."
},
"dependencies": {
"@repo/typescript-config": "*"
}
}
4. Install Dependencies
# From root
yarn install
5. Add Turbo Task (if needed)
// turbo.json
{
"tasks": {
"new-task": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
Creating Shared Packages
1. Create Package Structure
mkdir -p packages/shared-utils
cd packages/shared-utils
2. Setup Package
// packages/shared-utils/package.json
{
"name": "@repo/shared-utils",
"version": "0.0.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"devDependencies": {
"@repo/typescript-config": "*",
"typescript": "^5.0.0"
}
}
3. TypeScript Config
// packages/shared-utils/tsconfig.json
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
4. Export Functions
// packages/shared-utils/src/index.ts
export function formatDate(date: Date): string {
return date.toISOString();
}
export function slugify(text: string): string {
return text.toLowerCase().replace(/\s+/g, '-');
}
5. Build Package
cd packages/shared-utils
yarn build
6. Use in Apps
// apps/api/package.json
{
"dependencies": {
"@repo/shared-utils": "*"
}
}
// apps/api/src/some-file.ts
import { formatDate, slugify } from '@repo/shared-utils';
Troubleshooting
Dependency Issues
# Clear all node_modules
yarn clean
# Reinstall everything
yarn install
# Rebuild packages
turbo run build
Turbo Cache Issues
# Clear Turbo cache
turbo run build --force
# Or delete cache directory
rm -rf node_modules/.cache/turbo
Workspace Not Found
# Check workspace configuration
yarn workspaces list
# Verify package.json workspace paths
# Ensure package names match
Port Conflicts
# Find process using port
lsof -i :3000
lsof -i :5173
# Kill process
kill -9 <PID>
# Or change port in config
TypeScript Errors
# Rebuild TypeScript projects
turbo run build --filter=@repo/typescript-config
turbo run build
# Restart TypeScript server in IDE
# VSCode: Cmd+Shift+P -> "TypeScript: Restart TS Server"
Best Practices
1. Use Workspace Protocol
// Use * for internal dependencies
"dependencies": {
"@repo/typescript-config": "*" // ✅ Good
"@repo/typescript-config": "1.0.0" // ❌ Bad
}
2. Keep Root Clean
- Only workspace orchestration in root
- App-specific code in workspaces
- Shared utilities in packages
3. Consistent Naming
- Apps: descriptive names (api, web, native)
- Packages: scoped with @repo/* prefix
- Scripts: consistent across workspaces
4. Proper Caching
- Enable cache for builds
- Disable cache for dev servers
- Specify outputs for cached tasks
5. Environment Isolation
- Each app manages its own env vars
- Use different prefixes (VITE_, EXPO_PUBLIC_)
- Never commit .env.local files
6. Build Order
- Use
dependsOnfor build dependencies - Shared packages build before apps
- Parallelize independent builds
7. Version Pinning
- Pin external dependencies to exact versions
- Use workspace protocol for internal deps
- Document major version upgrades
Development Commands Reference
# Root level
yarn install # Install all dependencies
yarn dev # Start all apps
yarn dev:web # Start web + api
yarn dev:api # Start api only
yarn build # Build all apps
yarn lint # Lint all code
yarn format # Format all code
yarn clean # Clean all workspaces
# Workspace level
yarn workspace api dev
yarn workspace web build
yarn workspace native test
# Turbo commands
turbo run build
turbo run dev --filter=api
turbo run test --filter=web --filter=api
turbo run build --force
# Docker
docker-compose up -d
docker-compose down
docker-compose logs -f
# Yarn workspaces
yarn workspaces list
yarn workspaces foreach run build
yarn workspace api add lodash
CI/CD Considerations
Caching Strategy
# .github/workflows/ci.yml
- name: Setup Turbo cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
Affected Apps Only
# Build only changed apps
turbo run build --filter=[HEAD^1]
# Test affected apps
turbo run test --filter=[main...HEAD]
Parallel Jobs
strategy:
matrix:
app: [api, web, native]
steps:
- run: turbo run build --filter=${{ matrix.app }}
Converted and distributed by TomeVault — claim your Tome and manage your conversions.