Bluewoo HRMS
Micro-Step Build PlanBuilding Blocks

Phase 00: Empty Shell

Three applications that start and connect - monorepo, Next.js, NestJS, Prisma, Docker

Phase 00: Empty Shell

Goal: Three applications that start and connect. Nothing works yet except "hello world" level responses.

AttributeValue
Steps01-08
Estimated Time4-6 hours
DependenciesEmpty directory or new git repo
Completion GateAll three services running, health endpoint returns JSON with database connection status

Phase Context (READ FIRST)

What This Phase Accomplishes

  • Three applications that start and connect
  • Health endpoint verifies database connection
  • NOTHING ELSE

What This Phase Does NOT Include

  • Authentication (Phase 01)
  • Multi-tenancy (Phase 01)
  • Any business logic
  • Any UI beyond "hello world"
  • Any API beyond health check

Bluewoo Anti-Pattern Reminder

This phase intentionally has NO:

  • Shared libraries between apps (just workspace reference)
  • GraphQL (REST only)
  • Event-driven architecture
  • Caching layers
  • Microservices patterns

If the AI suggests adding any of these, REJECT and continue with the spec.


Step 01: Create Monorepo Structure

Input

  • Empty directory or new git repo
  • Node.js 20+ installed
  • npm 10+ (comes with Node.js)

Constraints

  • DO NOT add any dependencies beyond typescript
  • DO NOT create any additional directories
  • DO NOT modify the workspace structure after creation
  • ONLY create: package.json, .gitignore, .nvmrc

Task

# Create project directory
mkdir hrms && cd hrms

# Initialize git
git init

# Create directory structure
mkdir -p apps/web apps/api packages/database

# Create root package.json with npm workspaces
cat > package.json << 'EOF'
{
  "name": "hrms",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "dev": "npm run dev --workspaces --if-present",
    "build": "npm run build --workspaces --if-present",
    "lint": "npm run lint --workspaces --if-present",
    "test": "npm test --workspaces --if-present"
  },
  "devDependencies": {
    "typescript": "^5.7.3"
  },
  "engines": {
    "node": ">=20.0.0"
  }
}
EOF

# Create .gitignore
cat > .gitignore << 'EOF'
node_modules
.env
.env.local
dist
.next
.turbo
*.log
.DS_Store
EOF

# Create .nvmrc
echo "20" > .nvmrc

# Install dependencies
npm install

Gate

ls apps/web apps/api packages/database
# Should show all three folders exist

cat package.json
# Should show workspaces configuration

Common Errors

ErrorCauseFix
npm ERR! code ENOWORKSPACESWorkspaces not configuredEnsure package.json has "workspaces" field
ENOENT: no such file or directoryWrong directoryEnsure you're in the hrms root directory

Rollback

# If Gate fails, start over
cd ..
rm -rf hrms

Lock

After this step, these files are locked (do not modify without ADR):

  • package.json (workspaces configuration)

Checkpoint

Before proceeding to Step 02:

  • ls apps/web apps/api packages/database shows all folders
  • cat package.json shows workspaces config
  • No errors in console
  • Type "GATE 01 PASSED" to continue

Step 02: Create Next.js App Shell

Input

  • Step 01 complete
  • Monorepo structure exists

Constraints

  • DO NOT add any UI libraries (Tailwind, shadcn/ui come in Phase 01)
  • DO NOT add any routing beyond the root page
  • DO NOT modify files from Step 01
  • ONLY create files in apps/web/

Task

cd apps/web

# Create package.json
cat > package.json << 'EOF'
{
  "name": "@hrms/web",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "15.3.4",
    "react": "^19.1.0",
    "react-dom": "^19.1.0"
  },
  "devDependencies": {
    "@types/node": "^22.15.29",
    "@types/react": "^19.1.6",
    "@types/react-dom": "^19.1.5",
    "typescript": "^5.7.3"
  }
}
EOF

# Create next.config.ts
cat > next.config.ts << 'EOF'
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  reactStrictMode: true,
}

export default nextConfig
EOF

# Create tsconfig.json
cat > tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "paths": { "@/*": ["./*"] }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
EOF

# Create app directory structure
mkdir -p app

# Create app/layout.tsx
cat > app/layout.tsx << 'EOF'
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'HRMS',
  description: 'Human Resource Management System',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
EOF

# Create app/page.tsx
cat > app/page.tsx << 'EOF'
export default function Home() {
  return (
    <main style={{ padding: '2rem' }}>
      <h1>HRMS</h1>
      <p>Human Resource Management System</p>
    </main>
  )
}
EOF

# Go back to root and install
cd ../..
npm install

Gate

cd apps/web && npm run dev
# Open browser to http://localhost:3000
# Should show "HRMS" heading and "Human Resource Management System" text
# Press Ctrl+C to stop

Common Errors

ErrorCauseFix
Module not found: reactDependencies not installedRun npm install from root
Port 3000 already in useAnother process using portKill process: lsof -ti:3000 | xargs kill
next: command not foundNot in apps/web directorycd apps/web first

Rollback

# Undo Step 02
rm -rf apps/web/*

Lock

After this step, these files are locked:

  • apps/web/package.json
  • apps/web/next.config.ts

Checkpoint

Before proceeding to Step 03:

  • npm run dev shows Next.js page at localhost:3000
  • Page shows "HRMS" heading
  • No console errors
  • Type "GATE 02 PASSED" to continue

Step 03: Create NestJS App Shell

Input

  • Step 02 complete
  • Next.js app runs on port 3000

Constraints

  • DO NOT add any middleware or guards
  • DO NOT add any additional endpoints
  • DO NOT add database connection (that's Step 07)
  • DO NOT modify files from Steps 01-02
  • ONLY create files in apps/api/

Task

cd apps/api

# Create package.json
cat > package.json << 'EOF'
{
  "name": "@hrms/api",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "nest start --watch",
    "build": "nest build",
    "start": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
    "test": "jest"
  },
  "dependencies": {
    "@nestjs/common": "^10.5.0",
    "@nestjs/core": "^10.5.0",
    "@nestjs/platform-express": "^10.5.0",
    "@nestjs/throttler": "^5.0.0",
    "helmet": "^7.1.0",
    "reflect-metadata": "^0.2.2",
    "rxjs": "^7.8.1"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.5.0",
    "@nestjs/schematics": "^10.2.4",
    "@nestjs/testing": "^10.5.0",
    "@types/express": "^5.0.1",
    "@types/node": "^22.15.29",
    "typescript": "^5.7.3"
  }
}
EOF

# Create nest-cli.json
cat > nest-cli.json << 'EOF'
{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true
  }
}
EOF

# Create tsconfig.json
cat > tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "ES2021",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "strictBindCallApply": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true
  }
}
EOF

# Create src directory
mkdir -p src

# Create src/main.ts
cat > src/main.ts << 'EOF'
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import helmet from 'helmet';
import { AppModule } from './app.module';
import { GlobalExceptionFilter } from './common/filters/global-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Security headers (XSS, clickjacking, MIME sniffing protection)
  app.use(helmet());

  // CORS configuration
  app.enableCors({
    origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
    credentials: true,
  });

  // Global validation pipe (whitelist strips unknown properties)
  app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));

  // Global exception filter (handles Prisma errors, prevents stack trace leaks)
  app.useGlobalFilters(new GlobalExceptionFilter());

  await app.listen(3001);
  console.log('HRMS API running on http://localhost:3001');
}
bootstrap();
EOF

# Create src/app.module.ts
cat > src/app.module.ts << 'EOF'
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { AppController } from './app.controller';

@Module({
  imports: [
    // Rate limiting: 100 requests per minute per IP
    ThrottlerModule.forRoot([{
      ttl: 60000,
      limit: 100,
    }]),
  ],
  controllers: [AppController],
  providers: [
    { provide: APP_GUARD, useClass: ThrottlerGuard },
  ],
})
export class AppModule {}
EOF

# Create common/filters directory
mkdir -p src/common/filters

# Create src/common/filters/global-exception.filter.ts
cat > src/common/filters/global-exception.filter.ts << 'EOF'
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
  Logger,
} from '@nestjs/common';
import { Response } from 'express';

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(GlobalExceptionFilter.name);

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();

    let status = HttpStatus.INTERNAL_SERVER_ERROR;
    let message = 'Internal server error';

    if (exception instanceof HttpException) {
      status = exception.getStatus();
      const exceptionResponse = exception.getResponse();
      message = typeof exceptionResponse === 'string'
        ? exceptionResponse
        : (exceptionResponse as any).message || exception.message;
    }

    // Log unexpected errors (don't leak stack traces to client)
    if (status === HttpStatus.INTERNAL_SERVER_ERROR) {
      this.logger.error('Unhandled exception', exception);
    }

    response.status(status).json({
      data: null,
      error: { message, statusCode: status },
    });
  }
}
EOF

# Create src/app.controller.ts
cat > src/app.controller.ts << 'EOF'
import { Controller, Get } from '@nestjs/common';

@Controller()
export class AppController {
  @Get()
  getHello(): string {
    return 'HRMS API Running';
  }
}
EOF

# Go back to root and install
cd ../..
npm install

Gate

cd apps/api && npm run dev
# Wait for "HRMS API running on http://localhost:3001"

# In another terminal:
curl http://localhost:3001
# Should return: HRMS API Running

Common Errors

ErrorCauseFix
Cannot find module '@nestjs/core'Dependencies not installedRun npm install from root
Port 3001 already in useAnother process using portKill process: lsof -ti:3001 | xargs kill
nest: command not foundNestJS CLI not availableDependencies should be local, check package.json

Rollback

# Undo Step 03
rm -rf apps/api/*

Lock

After this step, these files are locked:

  • apps/api/nest-cli.json
  • apps/api/tsconfig.json

Checkpoint

Before proceeding to Step 04:

  • curl localhost:3001 returns "HRMS API Running"
  • Console shows "HRMS API running on http://localhost:3001"
  • No errors in logs
  • Type "GATE 03 PASSED" to continue

Step 04: Create Database Package with Prisma

Input

  • Step 03 complete
  • NestJS app runs on port 3001

Constraints

  • DO NOT add any models beyond HealthCheck
  • DO NOT run prisma db push yet (that's Step 06)
  • DO NOT connect to database yet
  • DO NOT modify files from Steps 01-03
  • ONLY create files in packages/database/
  • USE Prisma 5.x (NOT 6.x)

Task

cd packages/database

# Create package.json
cat > package.json << 'EOF'
{
  "name": "@hrms/database",
  "version": "0.1.0",
  "private": true,
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "scripts": {
    "build": "tsc",
    "db:generate": "prisma generate",
    "db:push": "prisma db push",
    "db:studio": "prisma studio",
    "db:migrate": "prisma migrate dev"
  },
  "dependencies": {
    "@prisma/client": "^5.22.0"
  },
  "devDependencies": {
    "prisma": "^5.22.0",
    "typescript": "^5.7.3"
  }
}
EOF

# Create tsconfig.json
cat > tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "target": "ES2021",
    "module": "commonjs",
    "lib": ["ES2021"],
    "declaration": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
EOF

# Create prisma directory
mkdir -p prisma

# Create prisma/schema.prisma
cat > prisma/schema.prisma << 'EOF'
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// Minimal model for Phase 00 - just to verify connection
model HealthCheck {
  id        String   @id @default(uuid())
  status    String
  checkedAt DateTime @default(now())
}
EOF

# Create src directory
mkdir -p src

# Create src/index.ts
cat > src/index.ts << 'EOF'
import { PrismaClient } from '@prisma/client';

export const prisma = new PrismaClient();
export { PrismaClient };
export * from '@prisma/client';
EOF

# Create .env.example
cat > .env.example << 'EOF'
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/hrms?schema=public"
EOF

# Go back to root and install
cd ../..
npm install

Gate

cat packages/database/prisma/schema.prisma
# Should show HealthCheck model with id, status, checkedAt fields

Common Errors

ErrorCauseFix
Cannot find module 'prisma'Prisma not installedRun npm install from root
Prisma schema validation errorSchema syntax errorCheck schema.prisma for typos

Rollback

# Undo Step 04
rm -rf packages/database/*

Lock

After this step, these files are locked:

  • packages/database/package.json

Checkpoint

Before proceeding to Step 05:

  • cat packages/database/prisma/schema.prisma shows HealthCheck model
  • Schema has id, status, checkedAt fields
  • No syntax errors
  • Type "GATE 04 PASSED" to continue

Step 05: Create Docker Compose for PostgreSQL

Input

  • Step 04 complete
  • Docker installed and running

Constraints

  • DO NOT add any services beyond PostgreSQL
  • DO NOT add Redis, MongoDB, or other databases
  • DO NOT modify files from Steps 01-04
  • ONLY create: docker-compose.yml in project root
  • USE PostgreSQL 17 (NOT older versions)

Task

# In project root, create docker-compose.yml
cat > docker-compose.yml << 'EOF'
services:
  postgres:
    image: postgres:17
    container_name: hrms-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: hrms
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:
EOF

Gate

docker compose up -d
docker compose ps
# Should show hrms-postgres running and healthy

# Test connection
docker exec hrms-postgres pg_isready -U postgres
# Should return: /var/run/postgresql:5432 - accepting connections

Common Errors

ErrorCauseFix
Cannot connect to Docker daemonDocker not runningStart Docker Desktop
port is already allocatedPort 5432 in useStop other PostgreSQL: docker stop $(docker ps -q)
image not foundNetwork issueCheck internet connection, retry docker compose up

Rollback

# Undo Step 05
docker compose down -v
rm docker-compose.yml

Lock

After this step, these files are locked:

  • docker-compose.yml

Checkpoint

Before proceeding to Step 06:

  • docker compose ps shows hrms-postgres running
  • docker exec hrms-postgres pg_isready -U postgres shows accepting connections
  • No errors in Docker logs
  • Type "GATE 05 PASSED" to continue

Step 06: Connect Prisma to PostgreSQL

Input

  • Step 05 complete
  • PostgreSQL running in Docker

Constraints

  • DO NOT modify the schema (just push existing schema)
  • DO NOT add migrations yet (use db push for now)
  • DO NOT modify files from Steps 01-05
  • ONLY create: .env file in packages/database/

Task

cd packages/database

# Create .env file (copy from example)
cp .env.example .env

# Push schema to database
npm run db:push

# Generate Prisma client
npm run db:generate

cd ../..

Gate

cd packages/database && npm run db:studio
# Should open Prisma Studio in browser
# Should show HealthCheck table (empty)
# Press Ctrl+C to stop

Common Errors

ErrorCauseFix
P1001: Can't reach databasePostgreSQL not runningRun docker compose up -d
P1000: Authentication failedWrong credentialsCheck DATABASE_URL matches docker-compose.yml
Error: ENOENT .env.env file missingRun cp .env.example .env

Rollback

# Undo Step 06
cd packages/database
rm .env
# Note: db:push created a table, but it's okay to leave it

Lock

After this step, these files are locked:

  • packages/database/.env.example

Checkpoint

Before proceeding to Step 07:

  • npm run db:studio opens Prisma Studio
  • HealthCheck table visible (empty is OK)
  • No connection errors
  • Type "GATE 06 PASSED" to continue

Step 07: Connect API to Database

Input

  • Step 06 complete
  • Prisma can connect to PostgreSQL

Constraints

  • DO NOT add any business logic
  • DO NOT add any endpoints (that's Step 08)
  • DO NOT modify Prisma schema
  • DO NOT modify files from Steps 01-06 (except apps/api/package.json)
  • ONLY create files in apps/api/src/prisma/

Task

cd apps/api

# Add database package as dependency
cat > package.json << 'EOF'
{
  "name": "@hrms/api",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "nest start --watch",
    "build": "nest build",
    "start": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
    "test": "jest"
  },
  "dependencies": {
    "@hrms/database": "workspace:*",
    "@nestjs/common": "^10.5.0",
    "@nestjs/core": "^10.5.0",
    "@nestjs/platform-express": "^10.5.0",
    "reflect-metadata": "^0.2.2",
    "rxjs": "^7.8.1"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.5.0",
    "@nestjs/schematics": "^10.2.4",
    "@nestjs/testing": "^10.5.0",
    "@types/express": "^5.0.1",
    "@types/node": "^22.15.29",
    "typescript": "^5.7.3"
  }
}
EOF

# Create prisma module directory
mkdir -p src/prisma

# Create src/prisma/prisma.module.ts
cat > src/prisma/prisma.module.ts << 'EOF'
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}
EOF

# Create src/prisma/prisma.service.ts
cat > src/prisma/prisma.service.ts << 'EOF'
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@hrms/database';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}
EOF

# Create src/prisma/index.ts
cat > src/prisma/index.ts << 'EOF'
export * from './prisma.module';
export * from './prisma.service';
EOF

# Update app.module.ts to import PrismaModule
cat > src/app.module.ts << 'EOF'
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { PrismaModule } from './prisma';

@Module({
  imports: [PrismaModule],
  controllers: [AppController],
  providers: [],
})
export class AppModule {}
EOF

# Create .env file for API
cat > .env << 'EOF'
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/hrms?schema=public"
EOF

# Go back to root and install
cd ../..
npm install

Gate

cd apps/api && npm run dev
# Watch logs - should NOT see database connection errors
# Should see "HRMS API running on http://localhost:3001"

Common Errors

ErrorCauseFix
Cannot find module '@hrms/database'npm install not runRun npm install from root
PrismaClient is not definedPrisma client not generatedRun cd packages/database && npm run db:generate
Connection refusedPostgreSQL not runningRun docker compose up -d
Cannot find module './prisma'Import path wrongCheck import { PrismaModule } from './prisma'
Nest can't resolve dependenciesPrismaModule not importedVerify PrismaModule in AppModule imports

Rollback

# Undo Step 07
rm -rf apps/api/src/prisma
rm apps/api/.env
git checkout -- apps/api/src/app.module.ts
git checkout -- apps/api/package.json

Lock

After this step, these files are locked:

  • apps/api/src/prisma/*

Checkpoint

Before proceeding to Step 08:

  • API starts without errors
  • Console shows "HRMS API running on http://localhost:3001"
  • No database connection errors in logs
  • Type "GATE 07 PASSED" to continue

Step 08: Create Health Endpoint

Input

  • Step 07 complete
  • API connects to database without errors

Constraints

  • DO NOT add any business logic beyond health check
  • DO NOT add authentication or guards
  • DO NOT add additional endpoints
  • DO NOT modify files from Steps 01-07 (except apps/api/src/app.module.ts)
  • ONLY create files in apps/api/src/health/

Task

cd apps/api

# Create health module directory
mkdir -p src/health

# Create src/health/health.controller.ts
cat > src/health/health.controller.ts << 'EOF'
import { Controller, Get } from '@nestjs/common';
import { HealthService } from './health.service';

@Controller('health')
export class HealthController {
  constructor(private readonly healthService: HealthService) {}

  @Get()
  async check() {
    return this.healthService.check();
  }
}
EOF

# Create src/health/health.service.ts
cat > src/health/health.service.ts << 'EOF'
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma';

@Injectable()
export class HealthService {
  constructor(private readonly prisma: PrismaService) {}

  async check() {
    try {
      // Try a simple query to verify database connection
      await this.prisma.$queryRaw`SELECT 1`;
      return {
        status: 'ok',
        database: 'connected',
        timestamp: new Date().toISOString(),
      };
    } catch (error) {
      return {
        status: 'error',
        database: 'disconnected',
        timestamp: new Date().toISOString(),
      };
    }
  }
}
EOF

# Create src/health/health.module.ts
cat > src/health/health.module.ts << 'EOF'
import { Module } from '@nestjs/common';
import { HealthController } from './health.controller';
import { HealthService } from './health.service';

@Module({
  controllers: [HealthController],
  providers: [HealthService],
})
export class HealthModule {}
EOF

# Create src/health/index.ts
cat > src/health/index.ts << 'EOF'
export * from './health.module';
export * from './health.controller';
export * from './health.service';
EOF

# Update app.module.ts to import HealthModule
cat > src/app.module.ts << 'EOF'
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { PrismaModule } from './prisma';
import { HealthModule } from './health';

@Module({
  imports: [PrismaModule, HealthModule],
  controllers: [AppController],
  providers: [],
})
export class AppModule {}
EOF

cd ../..

Gate

# Ensure PostgreSQL is running
docker compose up -d

# Start API
cd apps/api && npm run dev &

# Wait for startup, then test health endpoint
sleep 5
curl http://localhost:3001/health

# Should return:
# {"status":"ok","database":"connected","timestamp":"2025-..."}

Common Errors

ErrorCauseFix
Cannot GET /healthHealthModule not importedAdd HealthModule to AppModule imports
Nest can't resolve PrismaServicePrismaModule not globalVerify @Global() decorator on PrismaModule
database: disconnectedPostgreSQL not runningRun docker compose up -d

Rollback

# Undo Step 08
rm -rf apps/api/src/health
git checkout -- apps/api/src/app.module.ts

Lock

After this step, these files are locked:

  • apps/api/src/health/*

Checkpoint

PHASE 00 COMPLETE when:

  • curl localhost:3001/health returns {"status":"ok","database":"connected",...}
  • All three services can run simultaneously
  • No errors in any console
  • Type "PHASE 00 COMPLETE" to finish

Phase 00 Complete

Verification Checklist

Run these commands to verify Phase 00 is complete:

# 1. Check monorepo structure
ls apps/web apps/api packages/database

# 2. Check PostgreSQL is running
docker compose ps

# 3. Start all services (in separate terminals or use &)
cd apps/web && npm run dev &
cd apps/api && npm run dev &

# 4. Test endpoints
curl http://localhost:3000  # Should return HTML with "HRMS"
curl http://localhost:3001  # Should return "HRMS API Running"
curl http://localhost:3001/health  # Should return {"status":"ok","database":"connected",...}

Locked Files After Phase 00

docker-compose.yml
apps/web/package.json
apps/web/next.config.ts
apps/api/nest-cli.json
apps/api/tsconfig.json
apps/api/src/prisma/*
apps/api/src/health/*
packages/database/package.json
packages/database/.env.example

Phase Completion Checklist (MANDATORY)

BEFORE MOVING TO NEXT PHASE

Complete ALL items before proceeding. Do NOT skip any step.

1. Gate Verification

  • All step gates passed
  • Final verification commands successful
  • No errors in any console

2. Update PROJECT_STATE.md

# Update these sections:
- Mark Phase 00 as COMPLETED with timestamp
- Add all locked files to "Locked Files" section
- Update "Current Phase" to Phase 01
- Add session log entry

3. Update WHAT_EXISTS.md

# Add these items:
## Database Models
- HealthCheck (id, status, checkedAt)

## API Endpoints
- GET /health - Health check with database connection status

## Established Patterns
- PrismaModule pattern: apps/api/src/prisma/
- HealthModule pattern: apps/api/src/health/

4. Git Tag & Commit

git add PROJECT_STATE.md WHAT_EXISTS.md
git commit -m "chore: complete Phase 00 - Empty Shell"
git tag phase-00-empty-shell

5. Verify State Files

cat PROJECT_STATE.md | grep "Phase 00"
# Should show COMPLETED

cat WHAT_EXISTS.md | grep "HealthCheck"
# Should show the model

Next Phase

After verification, proceed to Phase 01: Multi-Tenant Auth


Last Updated: 2025-11-30