The PRMS backend is a NestJS 11 application backed by TypeORM and MySQL 8. It runs as an AWS Lambda function in production (via Serverless Framework) or as a Docker container, and locally withDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/AllianceBioversityCIAT/onecgiar_pr/llms.txt
Use this file to discover all available pages before exploring further.
npm run start:dev. This guide covers the module layout, routing, migration workflow, response conventions, and deployment options.
Technology stack
| Component | Technology |
|---|---|
| Framework | NestJS 11 |
| ORM | TypeORM 0.3 |
| Database | MySQL 8 |
| Runtime | Node.js 20 |
| Deployment | AWS Lambda (Serverless Framework) or Docker (multi-stage build) |
| Auth | Custom auth: <JWT> header, AWS Cognito + Active Directory (LDAP) |
| Message queue | RabbitMQ (reporting-metadata-export-queue) |
| Real-time | Pusher SDK + WebSockets |
Module structure
Every feature domain lives underonecgiar-pr-server/src/api/<feature>/ and follows the standard NestJS module layout:
api/results/ and api/ipsr/) nest dozens of related sub-modules using this same shape.
API surface
Routes are mounted insrc/main.routes.ts and src/api/modules.routes.ts. The top-level mounts are:
| Path prefix | Purpose |
|---|---|
/api/* | Main application traffic (results, IPSR, QA, notifications, admin, etc.) |
/auth/* | JWT issue and verify, AD/Cognito flows, roles |
/clarisa/* | CLARISA catalog cache and scheduled sync |
/toc/* | Theory of Change trees and result→ToC mappings |
/type-one-report | PMU consolidated report |
/contribution-to-indicators/* | Indicator-level contributions (top-level, not under /api/) |
/logs/* | DynamoDB operational logs |
/api/bilateral/*— headless typed payload surface, also throttler-excluded/api/platform-report/*— headless payload surface for platform reports
Build and start commands
Run all commands from theonecgiar-pr-server/ directory.
Migration workflow
Schema changes are made exclusively through TypeORM migrations.synchronize: false and migrationsRun: false are both set in src/config/orm.config.ts — the ORM never auto-syncs.
Generate a migration from entity changes
src/migrations/.Review the generated migration
Open the new file and confirm both the
up and down methods are correct. Every migration must have a working down. Do not edit a migration after it has been applied to master.Migration files live in
src/migrations/ and are excluded from test coverage and test discovery. Never squash old migrations.Adding a new feature module
Create the module folder
Create
src/api/<feature>/ with the standard layout: module.ts, controller.ts, service.ts, repository.ts, dto/, and entities/.Implement controller, service, repository, entities, and DTOs
Follow the conventions below. Validate all input DTOs with
class-validator. Decorate every endpoint with @ApiTags and @ApiOperation for Swagger.Response patterns
Standard envelope
Apply@UseInterceptors(ResponseInterceptor) on controllers whose endpoints return plain objects. The interceptor wraps the response in the standard envelope:
Raw arrays (bilateral and platform-report)
Endpoints under/api/bilateral/* and /api/platform-report/* return plain arrays or typed objects without the standard envelope. The ResponseInterceptor passes arrays through unchanged. These payload shapes are under contract in onecgiar-pr-server/docs/bilateral-result-summaries.en.md and must not change without updating that document’s change log.
Error responses
HttpExceptionFilter catches all HttpException instances and returns:
HttpException (or NestJS built-in variants) from services. Never let error stacks reach the client body.
Key shared utilities
| Utility / class | Location | Use |
|---|---|---|
BaseServiceSimple | src/shared/entities/base-service.ts | Upsert and soft-delete logic for join tables. Extend instead of re-implementing. |
BaseDeleteService | src/shared/entities/base-service.ts | Soft-delete (is_active = false) with audit column writes. |
BaseEntity | src/shared/entities/base-entity.ts | Default base for entities: is_active, created_date, last_updated_date, created_by, last_updated_by. |
returnFormatService | src/shared/extendsGlobalDTO/returnServices.dto.ts | Canonical service return shape { response, message, status } consumed by ResponseInterceptor. |
@UserToken() | src/shared/decorators/user-token.decorator.ts | Decode the JWT payload from the auth header into a TokenDto. |
@Roles + ValidRoleGuard | src/shared/decorators/, src/shared/guards/ | Role-gated endpoints. |
Auth header
PRMS uses a customauth: <JWT> header, not Authorization: Bearer. The JwtMiddleware in src/auth/Middlewares/jwt.middleware.ts verifies it, re-signs a fresh token, and returns it on the response so the Angular interceptor can update local storage for rolling sessions.
Deployment
- Lambda
- Docker
The
serverless.yaml at the root of onecgiar-pr-server/ deploys a single Lambda function with dist/lambda.handler as the entry point.Watch bundle size. The
serverless-plugin-optimize and serverless-plugin-typescript plugins are active. Avoid pulling in new heavy dependencies without measuring the cold-start impact.