About mono-repo-typescript
A skill for designing TypeScript monorepos managed with pnpm workspaces. It covers package boundaries, shared configuration, build orchestration and Docker Compose development setups for projects spanning services, apps and libraries.
Key features
- 01Organizes packages by application and role
- 02Configures pnpm workspaces and catalogs
- 03Guides TypeScript project references and aliases
- 04Coordinates builds and package dependencies
- 05Covers Docker Compose development setup
Use cases
- 01Starting a TypeScript project with multiple packages
- 02Structuring shared libraries and services
- 03Standardizing package configuration and builds
- 04Setting up a local multi-service environment
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ beecode-rs/ claude-skills --skill mono-repo-typescriptAfter installing, type/mono-repo-typescriptin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
LICENSE.txtreferences/backend-packages.mdfrontend-packages.mdinfrastructure.mdscripts/build-esm-fix.jsclean.jsDetails
From the project READMETypeScript Monorepo Architecture
Purpose
Guide the structure and orchestration of TypeScript monorepos with multiple packages including microservices, web apps, mobile apps, and shared libraries. Focus on high-level architecture, build orchestration, and development environment.
Scope boundaries:
- This skill: Monorepo structure, package organization, Docker Compose, build orchestration
- writing-clean-ts skill: Individual package code patterns (services, repositories, controllers)
- test-typescript skill: Testing patterns and coverage
When to Use
- Setting up a new monorepo with pnpm workspaces
- Structuring packages folder (microservices, web, mobile, libs)
- Configuring shared libraries (common, node-common, react-common)
- Setting up ES modules with path aliases
- Centralizing configuration (TypeScript, ESLint, Vitest)
- Orchestrating Docker Compose for development
- Managing inter-package dependencies
- Starting ANY new TypeScript project that might grow to multiple packages
When NOT to Use
- Single-package projects → Use
writing-clean-tsskill instead - Yarn or npm workspaces → This skill is pnpm-specific
- Non-TypeScript monorepos → Patterns here assume TypeScript throughout
- Existing monorepo with different conventions → Adapt patterns as needed
Quick Decision Guide
Choosing the right package type:
| You need... | Create... | Dependencies |
|---|---|---|
| Shared business models, utilities | packages/common/ | None (pure) |
| Database, logging, messaging (Node.js) | packages/node-common/ | common |
| Shared React components, hooks | packages/react-common/ | common |
| Backend API/microservice | packages/node-{domain}/ | common, node-common |
| Web application (Vite) | packages/react-{name}/ | common, react-common |
| Mobile app (Expo) | packages/mobile-app/ | common, react-common |
| Marketing/public site | packages/website/ | common, react-common |
Dependency flow:
common → node-common → node-{service}
common → react-common → react-{app} / mobile-app
Monorepo Structure Overview
project-root/
├── packages/ # All packages live here
│ ├── common/ # Shared business models, utilities
│ ├── node-common/ # Node.js-specific utilities
│ ├── react-common/ # React shared components
│ ├── config/ # Shared ESLint, Prettier, TypeScript configs
│ ├── node-core/ # Backend microservice
│ ├── node-auth/ # Auth microservice
│ ├── react-admin/ # Admin web app
│ ├── react-core/ # Main web app
│ ├── mobile-app/ # Mobile app (React Native/Expo)
│ └── website/ # Marketing site (Next.js)
├── config/ # Root-level shared configs
│ ├── typescript/ # TypeScript base configs
│ ├── vitest/ # Vitest base configs
│ └── eslint/ # ESLint base configs
├── resource/
│ ├── script/ # Build and utility scripts
│ ├── docker/ # Dockerfiles
│ └── secret/ # Encrypted secrets
├── package.json # Root orchestration
├── pnpm-workspace.yaml # Workspace and catalog config
├── tsconfig.json # TypeScript project references
├── compose.yml # Docker Compose for development
└── .env.example # Environment template
Root Orchestration
package.json
{
"name": "project-name",
"type": "module",
"private": true,
"engines": {
"node": ">=22 <23",
"pnpm": ">=10 <11"
},
"scripts": {
"build": "tsc -b && pnpm -r build",
"clean": "pnpm -r clean",
"lint": "prettier . && eslint . && pnpm -r lint",
"test": "pnpm -r test",
"watch": "tsc -b -w",
"ws-build": "pnpm --filter",
"ws-test": "pnpm --filter"
},
"devDependencies": {
"@types/node": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:",
"eslint": "catalog:",
"prettier": "catalog:"
},
"pnpm": {
"overrides": {
"@internal/msh-*": "link:../external-msh-monorepo/packages/$1"
}
}
}
Note: The
pnpm.overridessection shows how to link packages from an external monorepo. This is an example for a specific project setup - remove if not needed.
pnpm-workspace.yaml
packages:
- packages/*
catalog:
typescript: ^5.7
vitest: ^3.0
eslint: ^9.22
prettier: ^3.5
"@types/node": ^22
zod: ^3.24
express: ^5.0
react: ^19
"@mui/material": ^7
catalogProtocols:
# Example for external monorepo linking:
# msh-internal: link:
Note: Use
catalog:in package.json to reference these versions. This ensures consistency across all packages.
tsconfig.json (Project References)
{
"compilerOptions": {
"composite": true
},
"references": [
{ "path": "packages/common" },
{ "path": "packages/node-common" },
{ "path": "packages/react-common" },
{ "path": "packages/node-core" },
{ "path": "packages/react-admin" }
],
"files": []
}
Package Types Overview
| Package | Purpose | Dependencies |
|---|---|---|
common | Shared business models, utilities | None (pure) |
node-common | Node.js infrastructure (DB, logging, MQ) | common |
react-common | Shared React components, hooks | common |
node-* | Backend microservices | common, node-common |
react-* | Frontend web apps | common, react-common |
mobile-app | React Native/Expo app | common, react-common |
For detailed package configurations, see:
- Backend Packages - common, node-common, microservices
- Frontend Packages - react-common, web apps, mobile
ES Modules & Path Aliases
The #src/* Pattern
Each package uses path aliases for clean imports:
tsconfig.json:
{
"compilerOptions": {
"paths": {
"#src": ["./src"],
"#src/*": ["./src/*"]
}
}
}
package.json:
{
"imports": {
"#src/*": "./dist/*.js"
}
}
Usage in code:
import { someUtil } from '#src/util/some-util.js'
import { UserModel } from '#src/business/model/user/user-model.js'
Why this matters: The #src/* alias works both at compile-time (TypeScript paths) and runtime (Node.js subpath imports), so built code works without path rewriting issues.
Build Process
# 1. Clean dist
node ../../resource/script/npm/clean.js .
# 2. Compile TypeScript
tsc -p ./tsconfig.build.json
# 3. Resolve aliases (tsc-alias)
tsc-alias -p ./tsconfig.build.json
Inter-Package Dependencies
Dependency Hierarchy
@app/common (core models, utilities)
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
node-common node-core react-common
│ │ │
│ │ │
└───────────────┴───────────────┘
│
▼
Frontend Apps
(react-admin, react-core, mobile-app)
Workspace Dependencies
In package.json:
{
"dependencies": {
"@app/common": "workspace:*",
"@app/node-common": "workspace:*",
"@app/react-common": "workspace:*",
"@app/config": "workspace:*"
}
}
Catalog Dependencies
In package.json:
{
"dependencies": {
"zod": "catalog:",
"express": "catalog:",
"typeorm": "catalog:"
},
"devDependencies": {
"typescript": "catalog:",
"vitest": "catalog:"
}
}
Docker Compose Development
For full Docker setup, see Infrastructure Reference
Quick Start
# compose.yml - minimal example
services:
node-core:
build:
context: .
dockerfile: resource/docker/Dockerfile.node-base
command: pnpm run serve
working_dir: /app/packages/node-core
volumes:
- ./:/app:ro
depends_on:
pg: { condition: service_healthy }
labels:
- "traefik.http.routers.node-core.rule=PathPrefix(`/api/core`)"
Common Commands
docker compose up -d # Start all services
docker compose logs -f node-core # View logs
docker compose up -d --build node-core # Rebuild
docker compose down # Stop all
Process
1. Analyze Requirements
- Identify package types needed (services, apps, libs)
- Determine shared code boundaries
- Plan dependency hierarchy
2. Structure Packages
- Create packages in
./packages/folder - Set up common libs first (common, node-common, react-common)
- Configure package.json with proper exports and workspace deps
- Create tsconfig.json extending base configs
3. Configure Root
- Set up pnpm-workspace.yaml with catalog
- Configure root package.json scripts
- Create tsconfig.json with project references
- Set up centralized config in config/
4. Set Up Docker
- Create compose.yml with all services
- Configure Traefik routing
- Set up health checks
- Configure volume mounts for development
5. Verify Setup
pnpm install # Install dependencies
pnpm build # Build all packages
pnpm test # Run tests
docker compose up -d # Start development environment
Adding a New Package
Use this checklist when creating a new package:
Backend Package
- Create directory:
packages/node-{name}/ - Create
package.jsonwith:name:@app/node-{name}type:moduleexportsandimportsfor#src/*- Dependencies:
@app/common,@app/node-commonasworkspace:* - Build scripts
- Create
tsconfig.jsonextending../../config/typescript/tsconfig.base.json - Create
tsconfig.build.jsonfor production builds - Create
src/withapp/server-app.tsentry point - Add to root
tsconfig.jsonreferences - Add to
compose.ymlwith Traefik labels - Run
pnpm install
Frontend Package
- Create directory:
packages/react-{name}/ - Create
package.jsonwith:name:@app/react-{name}type:module- Dependencies:
@app/common,@app/react-commonasworkspace:* - Vite config for dev server
- Create
tsconfig.jsonextending../../config/typescript/tsconfig.react.base.json - Create
vite.config.tswithvite-tsconfig-pathsplugin - Create
src/withmain.tsxentry point - Add to root
tsconfig.jsonreferences - Add to
compose.ymlif needed - Run
pnpm install
Best Practices
Package Naming
- Use
@app/prefix for internal packages - Backend:
node-{domain}(e.g.,node-core,node-auth) - Frontend:
react-{purpose}(e.g.,react-admin,react-core) - Shared:
common,node-common,react-common
Version Management
- Use
catalog:for shared dependency versions - Use
workspace:*for internal packages - Keep versions consistent across packages
Build Order
- Build common libs first
- Build platform-specific libs second
- Build apps/services last
- Use TypeScript project references for correct order
Development Workflow
- Use Docker Compose for full stack development
- Use volume mounts for hot reload
- Use Traefik for routing between services
- Use health checks for service dependencies
Common Pitfalls
- Circular dependencies: Never let common depend on platform-specific libs
- Mixed module systems: All packages should use ES modules (
"type": "module") - Missing exports: Always define
exportsfield in package.json - Incorrect paths: Ensure tsconfig paths match package.json imports
- Catalog drift: Keep pnpm-workspace.yaml catalog versions consistent
- Missing .js extensions: ES modules require
.jsin import paths
Troubleshooting
ERR_PNPM_NO_MATCHING_VERSION
Cause: Package references a catalog: dependency that doesn't exist in pnpm-workspace.yaml
Fix: Add the missing package to the catalog, or use explicit version:
# pnpm-workspace.yaml
catalog:
your-missing-package: ^1.0.0
TypeScript project reference errors
Cause: A package is referenced in root tsconfig.json but doesn't exist or has errors
Fix:
- Ensure package exists in
packages/folder - Run
pnpm installto link workspace packages - Build dependencies first:
pnpm --filter @app/common build
Cannot find module '#src/...'
Cause: Path alias not configured correctly or package not built
Fix:
- Check
tsconfig.jsonhaspathsconfigured - Check
package.jsonhasimportsconfigured - Run
pnpm buildin the package - Ensure you're using
.jsextension in imports
ES module errors in built code
Cause: TypeScript emits imports without .js extensions
Fix: Run tsc-alias after build, or use the bundled fix script:
node scripts/build-esm-fix.js packages/your-package
Docker volume permission errors
Cause: Container runs as different user than host
Fix:
- Add
USER nodeto Dockerfile after install - Or run:
docker compose exec node-core chown -R node:node /app
Tests fail with "Cannot find module"
Cause: Vitest config doesn't resolve path aliases
Fix: Use vite-tsconfig-paths in vitest config:
import tsconfigPaths from 'vite-tsconfig-paths'
export default {
test: { ... },
plugins: [tsconfigPaths()]
}
Workspace package not found
Cause: workspace:* dependency not installed
Fix:
- Run
pnpm installat root - Check package name matches exactly in
package.json - Verify package is in
packages/folder
Bundled Scripts
This skill includes helper scripts in scripts/:
clean.js- Removesdist/folderbuild-esm-fix.js- Adds.jsextensions to ES module imports
Copy these to resource/script/npm/ in your project and reference in package.json:
{
"scripts": {
"build": "node ../../resource/script/npm/clean.js . && tsc -p ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json"
}
}
References
- Backend Packages - Detailed config for common, node-common, microservices
- Frontend Packages - Detailed config for react-common, web apps, mobile
- Infrastructure - Docker Compose, build scripts, centralized configs