# Node.js Backend — Developer Guide

**Version:** 1.1  
**Date:** September 30, 2026  
**Stack:** Node.js, TypeScript, Express, Prisma, MySQL, Redis (Bull), JWT  
**API contract:** [openapi.yaml](../docs/openapi.yaml)  
**Related:** [REQUIREMENTS.md](../docs/REQUIREMENTS.md), [USE_CASES.md](../docs/USE_CASES.md), [angular.md](../frontend/angular.md), [chrome-app.md](../chrome-app/chrome-app.md)

---

## 1. Overview

The leaves backend is a **TypeScript Express API** sharing the MySQL database `salaryslip` with salary-slip and review. It exposes REST endpoints under `/api/v1` for leave, attendance, team shifts, calendar, and HR admin.

### Design patterns (mandatory)

| Pattern | Role in this project |
|---------|----------------------|
| **Singleton** | One shared instance for DB, cache, config, logger |
| **Facade** | Simple entry points for complex domains (leave, attendance, shift) |
| **Builder** | Step-by-step construction of leave requests, shift schedules, emails |
| **Abstract Factory** | Pluggable families: notifications, auth validators, job processors |

Controllers stay thin; business rules live in facades + domain services; persistence in repositories.

---

## 2. Repository layout

```
backend/
├── .env                          # From salary-slip template; see REQUIREMENTS §2.1
├── package.json
├── tsconfig.json
├── prisma/
│   └── schema.prisma             # New leaves tables + @@map to existing tables
├── src/
│   ├── index.ts                  # Bootstrap
│   ├── container.ts              # Wires facades + NotificationFactory
│   ├── config/
│   │   └── ConfigSingleton.ts
│   ├── infrastructure/
│   │   ├── database/PrismaSingleton.ts
│   │   ├── cache/RedisSingleton.ts
│   │   └── logger/LoggerSingleton.ts
│   ├── factories/
│   │   ├── NotificationFactory.ts      # Abstract Factory
│   │   ├── AuthValidatorFactory.ts
│   │   └── JobProcessorFactory.ts
│   ├── builders/
│   │   ├── LeaveRequestBuilder.ts
│   │   ├── TeamShiftScheduleBuilder.ts
│   │   └── EmailPayloadBuilder.ts
│   ├── facades/
│   │   ├── AuthFacade.ts
│   │   ├── LeaveFacade.ts
│   │   ├── BackdatedLeaveFacade.ts
│   │   ├── AttendanceFacade.ts
│   │   ├── TeamShiftFacade.ts
│   │   ├── CompensationFacade.ts
│   │   ├── CalendarFacade.ts
│   │   └── HrAdminFacade.ts
│   ├── controllers/              # Thin HTTP: parse request, call one facade
│   ├── routes/api.ts             # Mounts controllers under /api/v1
│   ├── domain/
│   │   ├── leave/                # Sandwich, accrual, balances
│   │   ├── attendance/           # 9-hour rule, late / early-leave
│   │   ├── shift/
│   │   ├── team/                 # L10 resolution
│   │   └── employee/
│   ├── repositories/             # Prisma CRUD (leave, holiday, auth tokens)
│   ├── services/                 # App settings, team membership, auto-weekday attendance
│   ├── adapters/                 # Salary-slip sync
│   ├── middleware/
│   │   ├── employeeAuth.ts
│   │   ├── hrAuth.ts
│   │   ├── apiKeyAuth.ts         # attendanceApiKey + calendarApiKeyOrBearer
│   │   └── errorHandler.ts
│   ├── jobs/scheduledJobs.ts     # Accrual, shift reminders, EOD absent, comp-off expiry
│   └── types/
└── dist/
```

---

## 3. Singleton pattern

Use Singleton for **expensive or global** resources. Implement with a private constructor + `getInstance()`.

### 3.1 PrismaSingleton

```typescript
// src/infrastructure/database/PrismaSingleton.ts
import { PrismaClient } from '@prisma/client';

export class PrismaSingleton {
  private static instance: PrismaClient;

  private constructor() {}

  static getInstance(): PrismaClient {
    if (!PrismaSingleton.instance) {
      PrismaSingleton.instance = new PrismaClient({
        log: process.env.NODE_ENV === 'development' ? ['warn', 'error'] : ['error'],
      });
    }
    return PrismaSingleton.instance;
  }
}
```

### 3.2 ConfigSingleton

Loads and validates `.env` once (PORT, DATABASE_URL, JWT_SECRET, SMTP, Redis, API keys).

### 3.3 RedisSingleton

Single `ioredis` connection for Bull queues and optional cache.

### 3.4 LoggerSingleton

Structured JSON logger (or pino wrapper) used across facades and jobs.

**Rule:** Never `new PrismaClient()` outside `PrismaSingleton`. Inject via constructor in tests with a mock.

---

## 4. Facade pattern

A **Facade** exposes a small API over multiple services, repositories, and factories. **Routes/controllers call facades only** — not repositories directly.

### 4.1 LeaveFacade

**Responsibilities:** UC-LEAVE-001, UC-LEAVE-002, UC-LEAVE-006, UC-LEAVE-007

```typescript
// src/facades/LeaveFacade.ts (sketch)
export class LeaveFacade {
  constructor(
    private leaveRepo: LeaveRepository,
    private teamService: TeamResolutionService,
    private sandwichCalculator: SandwichLeaveCalculator,
    private balanceService: LeaveBalanceService,
    private notificationFactory: NotificationFactory,
  ) {}

  async applyLeave(employeeId: string, dto: CreateLeaveDto): Promise<LeaveRequest> {
    // 1. Validate dates >= today (Builder pre-check)
    // 2. Type rules: casual 14d, emergency quarterly
    // 3. sandwichCalculator.compute(dto)
    // 4. LeaveRequestBuilder → entity
    // 5. Persist PENDING; notify manager via NotificationFactory
  }

  async approveLeave(approverEmployeeId: string, leaveId: string, comment?: string): Promise<LeaveRequest> {
    // Manager/HR check; deduct balance; APPROVED; calendar sync
  }
}
```

### 4.2 BackdatedLeaveFacade

**Responsibilities:** UC-LEAVE-003–005 — peer approval, mandatory reasons, mark leave.

### 4.3 AttendanceFacade

**Responsibilities:** UC-ATT-001–004 — login/logout, late count, insufficient hours, off-day hook.

### 4.4 TeamShiftFacade

**Responsibilities:** UC-SHIFT-001–004 — delegate, draft, submit, 2-peer approve, compliance.

### 4.5 CalendarFacade

**Responsibilities:** UC-CAL-002 — approved leave only; no reasons in response.

### Facade diagram

```
HTTP Controller
      │
      ▼
┌─────────────┐     ┌──────────────────┐
│ LeaveFacade │────►│ LeaveRepository  │
└─────────────┘     │ TeamResolution   │
      │             │ SandwichCalc     │
      │             │ NotificationFactory
      ▼
  LeaveRequestBuilder
```

---

## 5. Builder pattern

Use **Builder** when an object has many optional fields or validation steps before persistence.

### 5.1 LeaveRequestBuilder

```typescript
// src/builders/LeaveRequestBuilder.ts
export class LeaveRequestBuilder {
  private data: Partial<LeaveRequestEntity> = {};

  forEmployee(employeeId: string): this {
    this.data.employeeId = employeeId;
    return this;
  }

  type(leaveType: LeaveType): this {
    this.data.leaveType = leaveType;
    return this;
  }

  dates(start: Date, end: Date): this {
    this.data.startDate = start;
    this.data.endDate = end;
    return this;
  }

  duration(fullOrHalf: LeaveDuration, slot?: HalfDaySlot): this {
    this.data.duration = fullOrHalf;
    this.data.halfDaySlot = slot;
    return this;
  }

  computedDays(days: number): this {
    this.data.leaveDays = days;
    return this;
  }

  reason(text: string): this {
    this.data.reason = text;
    return this;
  }

  status(status: LeaveStatus): this {
    this.data.status = status;
    return this;
  }

  build(): LeaveRequestEntity {
    this.validate();
    return this.data as LeaveRequestEntity;
  }

  private validate(): void {
    if (!this.data.employeeId || !this.data.leaveType) {
      throw new ValidationError('Incomplete leave request');
    }
  }
}
```

### 5.2 TeamShiftScheduleBuilder

Builds monthly shift: `effectiveMonth`, flags, `memberAssignments[]`, validates conflicts before `build()`.

### 5.3 EmailPayloadBuilder

Fluent builder for SMTP templates (OTP, shift reminder, backdated approval, lateness penalty).

**Rule:** Facades orchestrate builders; builders do not access DB.

---

## 6. Abstract Factory pattern

Use **Abstract Factory** to create **families** of related objects without naming concrete classes in facades.

### 6.1 NotificationFactory

```typescript
// src/factories/NotificationFactory.ts
export interface EmailNotification {
  send(): Promise<void>;
}

export interface PushNotification {
  send(): Promise<void>;
}

export abstract class NotificationFactory {
  abstract createEmail(template: NotificationTemplate, ctx: unknown): EmailNotification;
  abstract createPush(template: NotificationTemplate, ctx: unknown): PushNotification;
}

export class SmtpNotificationFactory extends NotificationFactory {
  createEmail(template: NotificationTemplate, ctx: unknown): EmailNotification {
    const payload = new EmailPayloadBuilder().forTemplate(template).withContext(ctx).build();
    return new SmtpEmailNotification(payload, ConfigSingleton.getInstance());
  }

  createPush(): PushNotification {
    return new NoOpPushNotification(); // Chrome extension handles push via its own channel
  }
}
```

Templates: `OTP`, `LEAVE_SUBMITTED`, `LEAVE_APPROVED`, `BACKDATE_PEER_APPROVAL`, `SHIFT_REMINDER_T5`, `LATE_PENALTY`, `OFF_DAY_COMP_CHOICE`.

### 6.2 AuthValidatorFactory

Produces validators for: `EmployeeOtpValidator`, `HrPasswordValidator`, `AttendanceApiKeyValidator`, `CalendarApiKeyValidator`.

### 6.3 JobProcessorFactory

Creates Bull job handlers: `AccrualProcessor`, `ShiftReminderProcessor`, `EndOfDayAbsentProcessor`.

**Extensibility:** Swap `SmtpNotificationFactory` for `MockNotificationFactory` in tests via factory injection at app bootstrap.

---

## 7. Team & employee ID resolution

Every facade that touches approvals or shifts must use **TeamResolutionService**:

1. Input: `employees.employee_id` (login string).
2. Resolve PK: `employees.id`.
3. Load `l10_team_members`; pick primary team (lowest `team_id` or `app_settings` override).
4. Output: `{ teamId, leadEmployeeId, memberEmployeeIds[] }`.

See [REQUIREMENTS.md §2.2](../docs/REQUIREMENTS.md#22-team-organization-model-l10-teams).

---

## 8. Layer responsibilities

| Layer | Does | Does not |
|-------|------|----------|
| **Controller** | Parse HTTP, call facade, map status codes | Business rules |
| **Facade** | Orchestrate use case | Raw SQL |
| **Domain service** | Pure rules (sandwich, late, accrual) | HTTP |
| **Builder** | Assemble DTO → entity | Side effects |
| **Repository** | Prisma CRUD | Policy decisions |
| **Factory** | Create notification/auth/job instances | Business logic |

---

## 9. API implementation map

| OpenAPI path | Facade | Use case |
|--------------|--------|----------|
| `/employee/*` | AuthFacade + AuthValidatorFactory | UC-AUTH-001 |
| `/hr/login` | AuthFacade | UC-AUTH-002 |
| `/leaves` | LeaveFacade | UC-LEAVE-001 |
| `/leaves/backdated/*` | BackdatedLeaveFacade | UC-LEAVE-003–005 |
| `/attendance/*` | AttendanceFacade | UC-ATT-* |
| `/teams/*/shifts/*` | TeamShiftFacade | UC-SHIFT-* |
| `/compensation/*` | CompensationFacade | UC-COMP-* |
| `/calendar/*` | CalendarFacade | UC-CAL-002 |
| `/hr/*` | HrAdminFacade | UC-ADM-* |

---

## 10. Background jobs (Bull + RedisSingleton)

| Job | Schedule | Facade / processor |
|-----|----------|-------------------|
| `monthly-accrual` | 1st 00:05 | LeaveFacade.accrueAll |
| `shift-reminder` | Daily check T-5, T-3, T-1 | TeamShiftFacade.sendReminders |
| `end-of-day-absent` | 23:59 weekdays | AttendanceFacade.markAbsents |
| `comp-off-expiry` | Daily | CompensationFacade.expireCredits |

Register processors via **JobProcessorFactory** in `src/index.ts` bootstrap.

---

## 11. Security middleware

| Middleware | Header / input | Routes |
|------------|----------------|--------|
| `employeeAuth` | `Authorization: Bearer` | Employee portal |
| `hrAuth` | HR JWT | `/hr/*` |
| `attendanceApiKey` | `X-Attendance-Api-Key` | `/attendance/login`, `/logout` |
| `calendarApiKeyOrBearer` | `X-Api-Key` or employee JWT | `/calendar/*` |

---

## 12. Error handling

Standard error envelope (matches OpenAPI `Error` schema):

```typescript
{ "code": "CASUAL_NOTICE_VIOLATION", "message": "Casual leave requires 14 days advance notice" }
```

Map domain errors in a central `errorHandler` middleware:

| HTTP | Codes |
|------|-------|
| 400 | Validation, sandwich preview failed, reason too short |
| 401 | Invalid OTP, expired token |
| 403 | Self-approval, wrong team |
| 409 | Attendance vs leave conflict |
| 429 | OTP rate limit |

---

## 13. Local development

```bash
cd backend
cp .env.example .env   # when added; use existing .env
npm install
npx prisma generate
npx prisma migrate dev
npm run dev            # ts-node-dev src/index.ts
```

- API: `http://localhost:3000/api/v1`
- Align with [postman/](../docs/postman/) collection
- Contract tests against [openapi.yaml](../docs/openapi.yaml)

---

## 14. Testing strategy

| Layer | Approach |
|-------|----------|
| Domain (sandwich, late) | Unit tests, no DB |
| Builders | Unit tests for validation |
| Facades | Integration tests with test DB + MockNotificationFactory |
| HTTP | Supertest + Postman/Newman |

---

## 15. Implementation phases

Align with [REQUIREMENTS.md §23](../docs/REQUIREMENTS.md#23-implementation-phases-suggested):

1. **Phase 1:** Singletons, Prisma, TeamResolutionService, AuthFacade, LeaveFacade (read balances)
2. **Phase 2:** Leave + BackdatedLeave facades, CalendarFacade
3. **Phase 3:** AttendanceFacade, TeamShiftFacade, jobs
4. **Phase 4:** HrAdminFacade, salary-slip sync adapter

---

## 16. Document history

| Version | Date | Changes |
|---------|------|---------|
| 1.0 | 2026-06-09 | Initial Node.js guide with Facade, Builder, Abstract Factory, Singleton |
| 1.1 | 2026-09-30 | Layout matches the server: controllers, jobs, domain/attendance, services, adapters |
