はじめに
monorepoは設定が決まるまで、かなり大変な作業ですが、一度うまくいけば、まとめて管理できるメリットがあり、少人数のプロジェクトにはむいていると思います。
うまくいった設定を以下にメモし、今後の設定の見直しなどに利用しようと思います。
アプリ全体の設定
- ルートで、pnpm build, pnpm devで開発できるように設定
./package.json
{
"name": "my-app",
"private": true,
"packageManager": "pnpm@9.4.0",
"scripts": {
"dev": "turbo run dev --parallel",
"build": "turbo run build",
"lint": "turbo run lint",
"test": "turbo run test"
},
"devDependencies": {
"turbo": "latest",
"typescript": "^5.9.2"
}
}
./pnpm-workspace.yaml
packages:
- 'apps/*'
- 'libs/adapters'
./tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"baseUrl": ".",
"paths": {
"@app/adapters": ["libs/adapters/src/index.ts"],
"@app/adapters/*": ["libs/adapters/src/*"]
},
"declaration": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"files": [],
"references": [
{ "path": "./libs/adapters" },
{ "path": "./apps/api" }
]
}
./turbo.json
{
"$schema": "https://turborepo.org/schema.json",
"tasks": {
"dev": {
"cache": false,
"persistent": true
},
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"lint": {
"outputs": []
},
"test": {
"outputs": []
}
}
}
apiアプリ
./apps/api/package.json
{
"name": "api",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "tsc -b",
"format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"",
"start": "nest start",
"dev": "nest start --watch",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/apps/ai-avatar-backend/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./apps/ai-avatar-backend/test/jest-e2e.json"
},
"dependencies": {
"@app/adapters": "workspace:*",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"firebase-admin": "^13.5.0",
"openai": "^5.19.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^22.10.7",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"turbo": "^2.5.6",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": ".",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "./coverage",
"testEnvironment": "node",
"roots": [
"<rootDir>/apps/",
"<rootDir>/libs/"
]
}
}
./apps/api/tsconfig.json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": "./",
"paths": {
"@app/adapters/*": ["../../libs/adapters/dist/*"],
"@app/adapters": ["../../libs/adapters/dist"]
},
"module": "nodenext",
"moduleResolution": "nodenext",
"target": "es2017",
"lib": ["es2017", "dom"],
"declaration": true,
"composite": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noEmit": false,
"incremental": true,
"tsBuildInfoFile": "./dist/.tsbuildinfo"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist",
"**/*.spec.ts"
],
"references": [
{ "path": "../../libs/adapters" }
]
}
./apps/api/nest-cli.json
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"tsConfigPath": "tsconfig.json"
}
}
webアプリ
./app/web/package.json
{
"name": "web",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build --turbopack",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"firebase": "^12.2.1",
"next": "15.5.2",
"react": "19.1.0",
"react-dom": "19.1.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.5.2",
"tailwindcss": "^4",
"typescript": "^5"
}
}
./apps/web/tsconfig.json
// apps/web/tsconfig.json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "preserve",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"incremental": true,
"resolveJsonModule": true,
"plugins": [
{
"name": "next"
}
],
"target": "ES2017",
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"module": "esnext",
"esModuleInterop": true,
"moduleResolution": "node",
"isolatedModules": true
},
"include": [
"**/*.ts",
"**/*.tsx",
"next-env.d.ts",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
],
"references": [
{
"path": "../../libs/adapters"
}
]
}
@app/adaptersアプリ
./lib/adapters/package.json
{
"name": "@app/adapters",
"version": "1.0.0",
"description": "Shared adapters library for the monorepo.",
"private": true,
"files": ["dist"],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc -b",
"clean": "rimraf dist"
},
"dependencies": {
"@nestjs/common": "^11.1.6",
"firebase-admin": "^13.5.0",
"openai": "^5.19.1"
},
"devDependencies": {
"@nestjs/testing": "^11.1.6",
"@types/jest": "^30.0.0",
"rimraf": "^6.0.1",
"typescript": "^5.9.2"
}
}
./lib/adapters/tsconfig.json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true, // プロジェクト参照用
"declaration": true, // .d.ts も出力
"emitDeclarationOnly": false, // .js も出す
"outDir": "./dist",
"rootDir": "./src",
"noEmit": false, // emit 禁止を無効化
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}