pnpm-workspace.yaml
```yaml
packages:
- "apps/*"
- "packages/*"
```
Root package.json
```json
{
"name": "my-saas",
"private": true,
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"test": "turbo test",
"lint": "turbo lint",
"typecheck": "turbo typecheck",
"clean": "turbo clean && rm -rf node_modules"
},
"devDependencies": {
"turbo": "^2.0.0",
"typescript": "^5.4.0"
},
"packageManager": "pnpm@9.0.0"
}
```
turbo.json
```json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["*/.env.local"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/", ".next/", "!.next/cache/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["^build"]
},
"typecheck": {
"dependsOn": ["^build"]
},
"lint": {
"dependsOn": ["^build"]
},
"clean": {
"cache": false
}
}
}
```
tsconfig.base.json
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true
}
}
```
Shared Types Package
```json
// packages/types/package.json
{
"name": "@myapp/types",
"version": "0.0.1",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.4.0"
},
"dependencies": {
"zod": "^3.23.0"
}
}
```
```json
// packages/types/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/*/"]
}
```
```typescript
// packages/types/src/index.ts
export * from './user';
export * from './schemas';
```
```typescript
// packages/types/src/user.ts
export interface User {
id: string;
email: string;
name: string;
role: 'admin' | 'user' | 'guest';
createdAt: Date;
}
export interface CreateUserInput {
email: string;
name: string;
role?: 'admin' | 'user' | 'guest';
}
```
```typescript
// packages/types/src/schemas.ts
import { z } from 'zod';
export const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(1),
role: z.enum(['admin', 'user', 'guest']),
createdAt: z.coerce.date(),
});
export const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1),
role: z.enum(['admin', 'user', 'guest']).default('user'),
});
```
App Package Using Shared Types
```json
// apps/web/package.json
{
"name": "@myapp/web",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@myapp/types": "workspace:*",
"next": "^14.0.0",
"react": "^18.0.0"
}
}
```
```typescript
// apps/web/app/api/users/route.ts
import type { User, CreateUserInput } from '@myapp/types';
import { CreateUserSchema } from '@myapp/types';
export async function POST(request: Request) {
const body = await request.json();
// Validate with shared schema
const input = CreateUserSchema.parse(body);
// Create user...
const user: User = await createUser(input);
return Response.json(user);
}
```
Shared Utils Package
```json
// packages/utils/package.json
{
"name": "@myapp/utils",
"version": "0.0.1",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"devDependencies": {
"typescript": "^5.4.0"
}
}
```
```typescript
// packages/utils/src/index.ts
export function formatDate(date: Date): string {
return date.toISOString().split('T')[0];
}
export function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-');
}
export function sleep(ms: number): Promise {
return new Promise(resolve => setTimeout(resolve, ms));
}
```