API conventions
The SIM API is versioned by URI, validated with class-validator, and returns a uniform error envelope. Controllers are kept thin; orchestration lives in services and SQL lives in repositories.
Versioning and base paths
Section titled “Versioning and base paths”Controllers declare both module path and version:
@Controller({ path: 'residence', version: '1' })All protected routes are prefixed with /api/v1. /metrics is excluded from the api prefix and is public.
Controllers
Section titled “Controllers”Controllers are responsible for:
- routing and HTTP method mapping
- Swagger decorators (
@ApiTags,@ApiBearerAuth,@ApiPropertyon DTOs) - authentication / authorization decorators (
@Public,@RequirePermissions,@CurrentUser) - extracting DTOs and params, then calling services
DTOs and validation
Section titled “DTOs and validation”DTOs are classes, value-imported into controllers so emitDecoratorMetadata works.
export class CreerClientDto { @IsString() @IsNotEmpty() nom: string;
@IsOptional() @IsString() @Matches(DATE_PATTERN) date_naissance?: string | null;}Global validation pipe:
app.useGlobalPipes( new ValidationPipe({ transform: true, whitelist: true }),);Generated enum unions are type-only, so runtime validation uses hand-written @IsIn arrays.
Common decorators
Section titled “Common decorators”| Decorator | Purpose |
|---|---|
@Public() |
Skip JWT on this route |
@RequirePermissions('MODULE.ACTION') |
Required permission codes |
@CurrentUser() |
Inject request.user as AuthUser |
@Backpressure() |
Mark route for job-queue depth check |
Guards
Section titled “Guards”JwtAuthGuard
Section titled “JwtAuthGuard”- skips
@Public() - extracts Bearer token
- verifies signature and expiry
- checks Redis
jtidenylist - resolves permissions,
idClient,idCaisse,idActiviteScope - attaches
AuthUsertorequest.user
PermissionsGuard
Section titled “PermissionsGuard”- reads
@RequirePermissionsmetadata - allows if empty
- otherwise checks all listed permissions are in
user.permissions
BackpressureGuard
Section titled “BackpressureGuard”- rejects
@Backpressure()routes with503whenwaiting + activejobs exceedBACKPRESSURE_QUEUE_DEPTH(default 100;0disables)
Error envelope
Section titled “Error envelope”AllExceptionsFilter returns a uniform JSON shape:
{ "success": false, "statusCode": 400, "code": "DATA_ERROR", "message": "...", "details": [...], "requestId": "...", "path": "/api/v1/client/clients", "timestamp": "..."}PostgreSQL SQLSTATE mappings:
| SQLSTATE | HTTP status | code |
|---|---|---|
23505 |
409 | CONFLICT |
23503 |
422 | FK_VIOLATION |
23502 / 23514 |
400 | DATA_ERROR |
22P02 |
400 | DATA_ERROR |
Logging
Section titled “Logging”pino logs in JSON. requestId comes from the x-request-id header or randomUUID(). The response echoes x-request-id. Sensitive fields are redacted: authorization, cookie, *_token, mot_de_passe.
Common patterns by controller
Section titled “Common patterns by controller”@Post()@RequirePermissions('CLIENT.CREER')async creer( @Body() dto: CreerClientDto, @CurrentUser() user: AuthUser,) { return this.service.creer(dto, user.id);}For list endpoints, query DTOs extend ListQueryDto:
export class ListerClientsDto extends ListQueryDto { @IsOptional() @IsString() type_client?: string;
@IsOptional() @IsString() recherche?: string;}