rwadurian/backend/services/backup-service/docs/DEVELOPMENT.md

11 KiB

Backup Service Development Guide

Prerequisites

  • Node.js: v20.x or higher
  • npm: v10.x or higher
  • Docker: For running PostgreSQL locally
  • WSL2: (Windows users) For running Docker and tests

Quick Start

1. Clone and Install

# Navigate to service directory
cd backend/services/backup-service

# Install dependencies
npm install

2. Environment Setup

Copy the example environment file:

cp .env.example .env

Configure the following environment variables:

# Database Configuration
DATABASE_URL="postgresql://postgres:password@localhost:5433/rwa_backup?schema=public"

# Server Configuration
APP_PORT=3002
APP_ENV="development"

# Service-to-Service Authentication
SERVICE_JWT_SECRET="your-super-secret-service-jwt-key-min-32-chars"
ALLOWED_SERVICES="identity-service,recovery-service"

# Encryption
BACKUP_ENCRYPTION_KEY="your-256-bit-encryption-key-in-hex-64-chars"
BACKUP_ENCRYPTION_KEY_ID="key-v1"

# Rate Limiting
MAX_RETRIEVE_PER_DAY=3
MAX_STORE_PER_MINUTE=10

# Audit
AUDIT_LOG_RETENTION_DAYS=365

3. Start Database

# Start PostgreSQL container
docker-compose up -d

# Verify database is running
docker-compose ps

4. Setup Database Schema

# Generate Prisma client
npm run prisma:generate

# Run migrations
npm run prisma:migrate

5. Start Development Server

# Start with hot-reload
npm run start:dev

# Or start in debug mode
npm run start:debug

The service will be available at http://localhost:3002.


Project Scripts

Development

Script Description
npm run start Start the service
npm run start:dev Start with hot-reload
npm run start:debug Start with debugger
npm run start:prod Start production build
npm run build Build for production

Database

Script Description
npm run prisma:generate Generate Prisma client
npm run prisma:migrate Run development migrations
npm run prisma:migrate:prod Run production migrations
npm run prisma:studio Open Prisma Studio GUI

Testing

Script Description
npm run test Run all tests
npm run test:unit Run unit tests only
npm run test:e2e:mock Run E2E tests with mocks
npm run test:e2e:db Run E2E tests with real DB
npm run test:cov Generate coverage report
npm run test:watch Run tests in watch mode

Docker

Script Description
npm run docker:build Build Docker image
npm run docker:up Start Docker compose stack
npm run docker:down Stop Docker compose stack
npm run db:test:up Start test database
npm run db:test:down Stop test database

Code Structure

Adding a New Feature

Follow the DDD + Hexagonal architecture pattern:

1. Domain Layer (Core Business Logic)

Create entities and value objects first:

// src/domain/entities/new-entity.entity.ts
export class NewEntity {
  // Properties
  private id: bigint;
  private data: string;

  // Factory method
  static create(params: CreateParams): NewEntity {
    // Validation and creation logic
    return new NewEntity(params);
  }

  // Domain methods
  performAction(): void {
    // Business logic
  }
}

2. Repository Interface

Define the data access contract:

// src/domain/repositories/new-entity.repository.interface.ts
export interface NewEntityRepository {
  save(entity: NewEntity): Promise<NewEntity>;
  findById(id: bigint): Promise<NewEntity | null>;
  // ... other methods
}

3. Application Layer (Use Cases)

Create commands/queries and handlers:

// src/application/commands/create-new-entity/create-new-entity.command.ts
export class CreateNewEntityCommand {
  constructor(
    public readonly data: string,
    // ... other fields
  ) {}
}

// src/application/commands/create-new-entity/create-new-entity.handler.ts
@Injectable()
export class CreateNewEntityHandler {
  constructor(
    @Inject('NewEntityRepository')
    private readonly repository: NewEntityRepository,
  ) {}

  async execute(command: CreateNewEntityCommand): Promise<Result> {
    const entity = NewEntity.create({ data: command.data });
    await this.repository.save(entity);
    return { id: entity.id.toString() };
  }
}

4. Infrastructure Layer

Implement the repository:

// src/infrastructure/persistence/repositories/new-entity.repository.impl.ts
@Injectable()
export class NewEntityRepositoryImpl implements NewEntityRepository {
  constructor(private readonly prisma: PrismaService) {}

  async save(entity: NewEntity): Promise<NewEntity> {
    const data = await this.prisma.newEntity.create({
      data: this.toDatabase(entity),
    });
    return this.toDomain(data);
  }

  // Mapping methods
  private toDatabase(entity: NewEntity) { /* ... */ }
  private toDomain(data: PrismaModel) { /* ... */ }
}

5. API Layer

Create controller and DTOs:

// src/api/dto/request/create-new-entity.dto.ts
export class CreateNewEntityDto {
  @IsNotEmpty()
  @IsString()
  data: string;
}

// src/api/controllers/new-entity.controller.ts
@Controller('new-entity')
@UseGuards(ServiceAuthGuard)
export class NewEntityController {
  constructor(
    private readonly service: NewEntityApplicationService,
  ) {}

  @Post()
  async create(@Body() dto: CreateNewEntityDto) {
    return this.service.create(dto);
  }
}

Environment Variables

Required Variables

Variable Description Example
DATABASE_URL PostgreSQL connection string postgresql://...
SERVICE_JWT_SECRET JWT secret (min 32 chars) your-secret-key...
ALLOWED_SERVICES Comma-separated service list identity-service,recovery-service
BACKUP_ENCRYPTION_KEY 256-bit key in hex (64 chars) 0123456789abcdef...
BACKUP_ENCRYPTION_KEY_ID Key identifier for rotation key-v1

Optional Variables

Variable Default Description
APP_PORT 3002 Server port
APP_ENV development Environment mode
MAX_RETRIEVE_PER_DAY 3 Rate limit for retrieves
MAX_STORE_PER_MINUTE 10 Rate limit for stores
AUDIT_LOG_RETENTION_DAYS 365 Audit log retention

Database Management

Prisma CLI Commands

# Generate Prisma client after schema changes
npx prisma generate

# Create a new migration
npx prisma migrate dev --name migration_name

# Apply migrations in production
npx prisma migrate deploy

# Reset database (development only)
npx prisma migrate reset

# Open Prisma Studio
npx prisma studio

# Push schema without migrations (development)
npx prisma db push

Schema Changes Workflow

  1. Modify prisma/schema.prisma
  2. Create migration: npx prisma migrate dev --name descriptive_name
  3. Test locally
  4. Commit migration files
  5. Apply in staging/production: npx prisma migrate deploy

Debugging

VSCode Launch Configuration

Add to .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Nest.js",
      "runtimeArgs": [
        "-r",
        "ts-node/register",
        "-r",
        "tsconfig-paths/register"
      ],
      "args": ["${workspaceFolder}/src/main.ts"],
      "envFile": "${workspaceFolder}/.env"
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Jest Tests",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["--runInBand", "--config", "jest.config.js"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}

Debug Logging

Enable verbose logging:

// In main.ts or app.module.ts
app.useLogger(['log', 'error', 'warn', 'debug', 'verbose']);

Code Style

TypeScript Configuration

The project uses strict TypeScript settings:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}

Naming Conventions

Type Convention Example
Files kebab-case backup-share.entity.ts
Classes PascalCase BackupShareEntity
Interfaces PascalCase with I prefix IBackupShareRepository
Functions camelCase storeBackupShare()
Constants UPPER_SNAKE_CASE MAX_RETRY_COUNT
Environment UPPER_SNAKE_CASE DATABASE_URL

File Organization

feature/
├── feature.command.ts      # Command object
├── feature.handler.ts      # Command handler
├── feature.spec.ts         # Unit tests
└── index.ts                # Barrel export

Common Development Tasks

Adding a New Endpoint

  1. Create DTO in src/api/dto/request/
  2. Add validation decorators
  3. Create handler in src/application/commands/ or queries/
  4. Add method to controller
  5. Write unit tests
  6. Write E2E tests

Adding a New Environment Variable

  1. Add to .env.example with description
  2. Add to src/config/index.ts
  3. Update this documentation
  4. Update deployment configs

Updating Database Schema

  1. Modify prisma/schema.prisma
  2. Run npx prisma migrate dev --name description
  3. Update domain entities if needed
  4. Update repository mappings
  5. Write migration tests

Troubleshooting

Common Issues

Prisma Client Not Generated

Error: @prisma/client did not initialize yet

Solution:

npm run prisma:generate

Database Connection Failed

Error: Can't reach database server

Solution:

  1. Check if Docker is running: docker ps
  2. Check DATABASE_URL in .env
  3. Restart database: docker-compose restart backup-db

Port Already in Use

Error: listen EADDRINUSE: address already in use :::3002

Solution:

# Find and kill the process
netstat -ano | findstr :3002
taskkill /PID <PID> /F

TypeScript Compilation Errors

# Clear build cache
rm -rf dist
npm run build

Getting Help

  • Check existing issues on GitHub
  • Review NestJS documentation
  • Review Prisma documentation
  • Ask in team Slack channel

Best Practices

Security

  1. Never commit secrets to git
  2. Use environment variables for all configs
  3. Validate all inputs with class-validator
  4. Sanitize logs (no sensitive data)
  5. Use parameterized queries (Prisma handles this)

Performance

  1. Use database indexes for frequently queried fields
  2. Implement pagination for list endpoints
  3. Use connection pooling (Prisma default)
  4. Cache frequently accessed data if needed

Code Quality

  1. Write unit tests for all handlers
  2. Write E2E tests for all endpoints
  3. Use TypeScript strict mode
  4. Follow DDD principles
  5. Keep controllers thin, logic in handlers