11 KiB
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
- Modify
prisma/schema.prisma - Create migration:
npx prisma migrate dev --name descriptive_name - Test locally
- Commit migration files
- 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
- Create DTO in
src/api/dto/request/ - Add validation decorators
- Create handler in
src/application/commands/orqueries/ - Add method to controller
- Write unit tests
- Write E2E tests
Adding a New Environment Variable
- Add to
.env.examplewith description - Add to
src/config/index.ts - Update this documentation
- Update deployment configs
Updating Database Schema
- Modify
prisma/schema.prisma - Run
npx prisma migrate dev --name description - Update domain entities if needed
- Update repository mappings
- 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:
- Check if Docker is running:
docker ps - Check DATABASE_URL in
.env - 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
- Never commit secrets to git
- Use environment variables for all configs
- Validate all inputs with class-validator
- Sanitize logs (no sensitive data)
- Use parameterized queries (Prisma handles this)
Performance
- Use database indexes for frequently queried fields
- Implement pagination for list endpoints
- Use connection pooling (Prisma default)
- Cache frequently accessed data if needed
Code Quality
- Write unit tests for all handlers
- Write E2E tests for all endpoints
- Use TypeScript strict mode
- Follow DDD principles
- Keep controllers thin, logic in handlers