Skip to main content
The AdonisJS Starter Kit uses a monorepo architecture powered by TurboRepo and pnpm workspaces to manage multiple packages and applications efficiently.

Why Monorepo?

A monorepo structure provides several benefits:

Code Sharing

Share components, utilities, and configurations across apps without publishing to npm

Atomic Changes

Make changes across multiple packages in a single commit

Consistent Tooling

Use the same TypeScript, ESLint, and Prettier configs everywhere

Faster Builds

TurboRepo caches build outputs and only rebuilds what changed

pnpm Workspace Configuration

The monorepo is configured using pnpm workspaces, defined in pnpm-workspace.yaml:
pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"
This tells pnpm to treat all directories under apps/ and packages/ as workspace members.

Workspace Protocol

Packages can reference each other using the workspace:* protocol:
apps/web/package.json
{
  "dependencies": {
    "@workspace/ui": "workspace:*"
  }
}
The workspace:* protocol ensures you always use the local version of the package during development, and pnpm resolves it to the correct version when publishing.

TurboRepo Configuration

TurboRepo manages the build pipeline and caching. The configuration is in turbo.json:
turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": [".next/**", "!.next/cache/**", "build/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "check-types": {
      "dependsOn": ["^check-types"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Understanding Tasks

Purpose: Compile TypeScript and build production assets
  • dependsOn: ["^build"]: Build dependencies first (packages before apps)
  • inputs: Include default files and .env* files in cache key
  • outputs: Cache the build artifacts for faster subsequent builds
pnpm build  # Runs turbo build
Purpose: Run ESLint across all workspaces
  • dependsOn: ["^lint"]: Lint dependencies first
  • Cached by default (runs instantly if nothing changed)
pnpm lint  # Runs turbo lint
Purpose: Run TypeScript type checking
  • dependsOn: ["^check-types"]: Check dependencies first
  • Results are cached for faster checks
turbo check-types
Purpose: Start development servers
  • cache: false: Never cache dev server output
  • persistent: true: Keep the process running (don’t exit after completion)
pnpm dev  # Runs turbo dev

Task Dependencies

The ^ prefix in dependsOn means “run this task on dependencies first”:
TurboRepo automatically determines the build order based on package dependencies in package.json.

Root Package Configuration

The root package.json defines workspace-level scripts:
package.json
{
  "name": "adonisjs-starter-kit",
  "version": "0.0.2",
  "private": true,
  "scripts": {
    "build": "turbo build",
    "dev": "turbo dev",
    "lint": "turbo lint",
    "format": "prettier --write \"**/*.{ts,tsx,md}\"",
    "docker:prod": "docker compose -f docker-compose.yaml -f docker-compose.prod.yaml up -d"
  },
  "devDependencies": {
    "@workspace/eslint-config": "workspace:*",
    "@workspace/typescript-config": "workspace:*",
    "prettier": "^3.6.2",
    "turbo": "^2.5.8",
    "typescript": "~5.9.3"
  },
  "packageManager": "[email protected]",
  "engines": {
    "node": ">=20"
  }
}
The packageManager field locks the pnpm version. Make sure you have pnpm 10.18.0 or higher installed.

Package Manager: pnpm

Why pnpm?

pnpm offers several advantages over npm and yarn:
  • Disk Space Efficiency: Packages are stored once in a global store, linked via symlinks
  • Speed: Faster installation due to content-addressable storage
  • Strict Dependencies: Prevents access to undeclared dependencies
  • Monorepo Support: First-class workspace support

Common pnpm Commands

# Install all workspace dependencies
pnpm install

# Add a dependency to a specific workspace
pnpm add react --filter web

# Add a dev dependency to root
pnpm add -D prettier -w

pnpm Filtering

The --filter flag (or -F) runs commands in specific workspaces:
# By package name
pnpm --filter web dev
pnpm --filter @workspace/ui build

# By directory
pnpm --filter ./apps/web dev

# Multiple packages
pnpm --filter "./packages/*" build
Package names are defined in each workspace’s package.json. The web app uses "name": "web", so you filter with --filter web.

Build Caching

TurboRepo caches task outputs to speed up subsequent runs:
# First run - actually executes tasks
pnpm build
# >>> FULL TURBO ⏱  (takes 30s)

# Second run - uses cache
pnpm build
# >>> >>> FULL TURBO ✓ (instant, from cache)

Cache Invalidation

TurboRepo automatically invalidates cache when:
  • Source files change
  • Dependencies change
  • Environment variables change (if specified in inputs)
  • Task configuration changes

Force Rebuild

To bypass the cache and force a rebuild:
turbo build --force

Workspace Organization

Apps

The apps/ directory contains runnable applications:
  • web: Main AdonisJS application with Inertia.js frontend
  • Future apps could include: API servers, admin dashboards, documentation sites, etc.

Packages

The packages/ directory contains shared libraries:
Purpose: Shared React components (ShadCN)
{
  "name": "@workspace/ui",
  "exports": {
    "./globals.css": "./src/styles/globals.css",
    "./lib/*": "./src/lib/*.ts",
    "./components/*": "./src/components/*.tsx",
    "./hooks/*": "./src/hooks/*.ts"
  }
}
Used in apps/web:
import { Button } from '@workspace/ui/components/button'
import { cn } from '@workspace/ui/lib/utils'

Adding New Workspaces

Creating a New App

# 1. Create directory
mkdir apps/admin
cd apps/admin

# 2. Initialize package.json
pnpm init

# 3. Update name
# Edit package.json: "name": "admin"

# 4. Install dependencies
pnpm install

# 5. Use shared packages
pnpm add @workspace/ui @workspace/eslint-config

Creating a New Package

# 1. Create directory
mkdir packages/utils
cd packages/utils

# 2. Initialize package.json
pnpm init

# 3. Set name with @workspace scope
# Edit package.json: "name": "@workspace/utils"

# 4. Define exports
# Add "exports" field to package.json

# 5. Use in apps
cd ../../apps/web
pnpm add @workspace/utils
After adding new workspaces, run pnpm install at the root to update the workspace links.

Next Steps

Modular Architecture

Learn how modules organize code within the web application

Build docs developers (and LLMs) love