Skip to content

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.

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 are responsible for:

  • routing and HTTP method mapping
  • Swagger decorators (@ApiTags, @ApiBearerAuth, @ApiProperty on DTOs)
  • authentication / authorization decorators (@Public, @RequirePermissions, @CurrentUser)
  • extracting DTOs and params, then calling services

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.

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
  • skips @Public()
  • extracts Bearer token
  • verifies signature and expiry
  • checks Redis jti denylist
  • resolves permissions, idClient, idCaisse, idActiviteScope
  • attaches AuthUser to request.user
  • reads @RequirePermissions metadata
  • allows if empty
  • otherwise checks all listed permissions are in user.permissions
  • rejects @Backpressure() routes with 503 when waiting + active jobs exceed BACKPRESSURE_QUEUE_DEPTH (default 100; 0 disables)

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

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.

@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;
}