A production-quality NestJS + TypeScript service for asynchronous, idempotent report generation designed for multi-instance (Kubernetes) execution.
This service provides a robust backend for generating reports asynchronously with strong guarantees:
- Request-level idempotency: Duplicate client requests with the same
Idempotency-Keyreturn the same report job - Execution-level idempotency: Exactly-once report artifact creation, even with retries, crashes, or multiple workers
- Multi-instance safe: Uses database-level locking to safely coordinate work across multiple worker instances
- API Server (
main.ts): REST API for creating and querying reports - Worker Service (
worker.ts): Background worker that processes pending reports - Database: PostgreSQL with Prisma ORM for persistence
Reports progress through the following states:
PENDING → RUNNING → COMPLETED
↓
FAILED (after max attempts)
- PENDING: Report job created, waiting for worker
- RUNNING: Worker has claimed the job and is generating the report
- COMPLETED: Report generated successfully, artifact available
- FAILED: Report generation failed after maximum retry attempts
The worker uses PostgreSQL's SELECT ... FOR UPDATE SKIP LOCKED pattern to safely claim jobs:
- Worker queries for
PENDINGreports with no lock or stale locks - Uses
FOR UPDATE SKIP LOCKEDto atomically claim one job - Updates report to
RUNNINGwithlocked_atandlocked_byfields - Processes the report and creates artifact
- Updates report to
COMPLETED
This ensures:
- Only one worker can claim a specific job at a time
- Multiple workers can process different jobs concurrently
- No race conditions in multi-instance deployments
The system implements idempotency at multiple levels:
- Client provides optional
Idempotency-Keyheader - Key is stored in
reports.idempotency_keywith UNIQUE constraint - Duplicate requests with same key return existing report (200 OK)
- Race conditions handled via database constraint violations
- Priority: Highest - checked first before semantic deduplication
- Automatically reuses existing COMPLETED reports with identical business semantics
- Matching criteria:
(tenantId, type, params)must be identical - Only COMPLETED reports are reused (PENDING/RUNNING reports are not reused)
- Prevents unnecessary duplicate work and storage
- Priority: Secondary - checked if no idempotency key match
Example:
Request 1: POST /reports { tenantId: "A", type: "USAGE_SUMMARY", params: {...} }
→ Creates report, worker completes it
Request 2: POST /reports { tenantId: "A", type: "USAGE_SUMMARY", params: {...} }
→ Returns existing COMPLETED report (semantic deduplication)
report_artifacts.report_idhas UNIQUE constraint- Only one artifact can exist per report
- If artifact insert fails due to unique constraint:
- Another worker already created the artifact
- Current worker converges state to
COMPLETED - No duplicate artifacts created
Workers implement lease-based locking:
- Each claimed job has
locked_attimestamp - If
locked_atis older thanWORKER_STALE_LOCK_TIMEOUT_MS(default: 5 minutes), lock is considered stale - Stale locks are periodically recovered:
- Status reset to
PENDING - Lock fields cleared
- Job becomes available for other workers
- Status reset to
This handles:
- Worker crashes during execution
- Network partitions
- Long-running jobs that exceed timeout
id: UUID primary keytenant_id: UUID, tenant identifiertype: Report type (USAGE_SUMMARY, BILLING_EXPORT, AUDIT_SNAPSHOT)params: JSONB, report parametersstatus: PENDING | RUNNING | COMPLETED | FAILEDattempts: Number of execution attemptsidempotency_key: Optional unique key for request idempotencylocked_at: Timestamp when job was claimedlocked_by: Worker instance identifiercreated_at,updated_at: Timestamps
id: UUID primary keyreport_id: UUID, unique foreign key to reports (enforces exactly-once)content_type: MIME type of artifactcontent: BYTEA, artifact binary contentsize_bytes: Size of artifactchecksum: SHA-256 checksumcreated_at: Timestamp
id: UUID primary keyreport_id: UUID, foreign key to reportsattempt: Attempt numberstarted_at,finished_at: Execution timestampserror: Error message if execution failed
Create a new report job.
Headers:
Idempotency-Key(optional): Idempotency key for duplicate request prevention
Request Body:
{
"tenantId": "550e8400-e29b-41d4-a716-446655440000",
"type": "USAGE_SUMMARY",
"params": {
"from": "2024-01-01",
"to": "2024-01-31",
"format": "CSV"
}
}Response:
201 Created: New report created200 OK: Report already exists (idempotent response)
Get report status and metadata.
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"tenantId": "...",
"type": "USAGE_SUMMARY",
"params": {...},
"status": "COMPLETED",
"attempts": 1,
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z",
"artifact": {
"id": "...",
"contentType": "text/csv",
"sizeBytes": 1024,
"checksum": "...",
"createdAt": "..."
}
}Download report artifact.
Response:
200 OK: Artifact streamed with appropriate Content-Type409 Conflict: Report not completed404 Not Found: Report or artifact not found
List reports for a tenant.
Query Parameters:
status(optional): Filter by statustype(optional): Filter by typelimit(optional, default: 20): Page sizecursor(optional): Pagination cursor
Health check endpoint.
Scenario: Worker crashes after claiming job but before completion.
Recovery:
- Stale lock timeout expires (default: 5 minutes)
- Another worker detects stale lock
- Resets job to
PENDING - Job is retried by another worker
Scenario: Worker crashes after creating artifact but before updating status to COMPLETED.
Recovery:
- Another worker claims the job
- Attempts to create artifact
- Unique constraint violation detected
- Worker converges state to
COMPLETED - No duplicate artifact created
Scenario: Worker loses database connection during execution.
Recovery:
- Transaction rolls back
- Lock is released
- Job remains
PENDING - Another worker can claim it
- Retry logic handles transient failures
Scenario: Multiple requests arrive simultaneously with same Idempotency-Key.
Recovery:
- First request creates report
- Subsequent requests hit UNIQUE constraint
- Service queries for existing report
- All requests return same report ID
- Only one report row exists
Environment variables (see .env.example):
DATABASE_URL: PostgreSQL connection stringPORT: API server port (default: 3000)WORKER_POLL_INTERVAL_MS: Worker poll interval (default: 5000ms)WORKER_STALE_LOCK_TIMEOUT_MS: Stale lock timeout (default: 300000ms = 5min)WORKER_MAX_ATTEMPTS: Maximum retry attempts (default: 3)WORKER_INSTANCE_ID: Worker instance identifierLOG_LEVEL: Logging level (default: info)NODE_ENV: Environment (development | production)
- Node.js 20+
- Docker and Docker Compose
- npm or yarn
-
Clone and install dependencies:
npm install
-
Start PostgreSQL:
docker-compose up -d
-
Run Prisma migrations:
npm run prisma:migrate
-
Generate Prisma client:
npm run prisma:generate
-
Start API server:
npm run start:dev
-
Start worker (in separate terminal):
npm run build npm run start:worker
-
Access Swagger documentation:
http://localhost:3000/api
Integration tests use Testcontainers to spin up isolated PostgreSQL instances:
# Run all tests
npm test
# Run e2e tests
npm run test:e2e
# Run with coverage
npm run test:cov-
Build the application:
npm run build
-
Run migrations:
npm run prisma:migrate:deploy
-
Start API server:
npm run start:prod
-
Start worker(s):
npm run start:worker
For Kubernetes deployments:
- Deploy API server as a Deployment with multiple replicas
- Deploy worker as a separate Deployment with multiple replicas
- Use the same database connection string for all instances
- Set unique
WORKER_INSTANCE_IDper worker pod (e.g., using pod name)
Structured logging with Pino:
- Correlation IDs via
x-correlation-idheader - Request/response logging
- Worker execution logs
- Error tracking
GET /health: Database connectivity check- Returns
healthyorunhealthystatus
Basic metrics available via logs:
- Report creation rate
- Worker processing rate
- Error rates
- Execution times
For production, consider adding Prometheus metrics endpoint.
The test suite includes:
- Request Idempotency Test: Verifies duplicate requests with same
Idempotency-Keyreturn same report - Multi-Worker Safety Test: Ensures exactly one artifact per report with concurrent workers
- Crash Simulation Test: Verifies recovery after crash between artifact creation and status update
All tests use Testcontainers for isolated database instances.
src/
├── app.module.ts # Root module
├── main.ts # API server entrypoint
├── worker.ts # Worker entrypoint
├── app.controller.ts # Health check controller
├── app.service.ts # Health check service
├── common/ # Shared utilities
│ ├── filters/ # Exception filters
│ └── interceptors/ # Request interceptors
├── prisma/ # Prisma setup
│ ├── prisma.module.ts
│ └── prisma.service.ts
└── reports/ # Reports module
├── reports.module.ts
├── reports.controller.ts # API endpoints
├── reports.service.ts # Business logic
├── report-worker.service.ts # Background worker
└── dto/ # Data transfer objects
MIT