Skip to content

Error Code Convention

The backend returns structured error responses with a stable code field. The frontend uses this code as the i18n key to display localized error messages. The optional params field provides interpolation values.

Response Shape

Every error response has at least these 5 base fields:

{
  "statusCode": 404,
  "code": "NOT_FOUND",
  "message": "Student not found",
  "timestamp": "2026-03-10T12:00:00.000Z",
  "path": "/api/v1/students/abc-123"
}

Some error codes add typed params or data fields — see Typed Swagger Schemas below.

Field Description
statusCode HTTP status code
code Stable, machine-readable string. Frontend uses this as i18n key
message English human-readable string for logs/debugging
params Optional typed object — interpolation values for the frontend translation template (only on specific DTOs)
data Optional typed payload — structured details like validation errors (only on specific DTOs)
timestamp ISO 8601 timestamp
path Request URL path

Frontend translation example:

{
  "NOT_FOUND": "{{entity}} not found",
  "SETUP_STEP_MISMATCH": "Expected step {{expected}}, received {{received}}",
  "AUTH_TOKEN_REUSE": "Security alert: session was reused. Please log in again."
}

Backend Usage

All error codes are defined in the ErrorCode enum (src/common/constants/error-codes.ts). Services throw coded errors via AppException:

import { AppException, ErrorCode } from '../common';
import { HttpStatus } from '@nestjs/common';

// Simple — code alone is sufficient
throw new AppException(ErrorCode.AUTH_TOKEN_INVALID, 'Invalid token', HttpStatus.UNAUTHORIZED);

// With params — frontend interpolation
throw new AppException(ErrorCode.NOT_FOUND, 'Student not found', HttpStatus.NOT_FOUND, {
  params: { entity: 'student' },
});

// With data — structured payload (import pipeline)
throw new AppException(ErrorCode.IMPORT_VALIDATION_FAILED, 'Import validation failed', HttpStatus.UNPROCESSABLE_ENTITY, {
  data: { errors },
});

Naming convention: Use UPPER_SNAKE_CASE for all error codes. New codes must be added to the ErrorCode enum.

Generic Error Codes

Code Status Params Description
INTERNAL_ERROR 500 Catch-all for unhandled server errors. Real error logged server-side, never leaked.
VALIDATION_FAILED 400 Class-validator failures or custom field validation. See Validation Errors.
RATE_LIMITED 429 Rate limit exceeded. Global: 10 req/60s. Login: 5 req/60s.
HTTP_<status> varies Fallback for standard NestJS HttpException subclasses without a custom code.

Authentication Error Codes

Code Status Params When
INVALID_CREDENTIALS 401 Email exists but password does not match.
AUTH_TOKEN_INVALID 401 JWT expired, malformed, or revoked. Refresh token invalid/expired. User record not found.
AUTH_TOKEN_REUSE 401 Refresh token replay detected — entire token family revoked. Frontend should force re-login.
AUTH_REFRESH_MISSING 401 No refresh token cookie in request.
AUTH_TENANT_INVALID 401 Invalid tenant selection during multi-tenant login.
TENANT_SUSPENDED 403 Tenant is not active (suspended or trial-expired).
USER_DEACTIVATED 403 User account is deactivated.

Permission Error Codes

Code Status Params When
INSUFFICIENT_SCOPE 403 User lacks the required scope+action for the route (ScopeGuard).
ACTION_NOT_PERMITTED 403 User does not have the required action permission (ActionGuard).
NO_AUTHENTICATED_USER 403 Permission guard ran without authenticated user. Configuration error.
FORBIDDEN_FIELDS 403 Write request contains scope groups outside the user's writable scopes (FieldWriteGuard).

Database Error Codes

Code Status Params When
NOT_FOUND 404 { entity } Record not found. entity is the lowercased model/entity name (e.g. "student", "teacher").
CONFLICT 409 { entity } Unique constraint violation. entity from Prisma model name when available.
FOREIGN_KEY_VIOLATION 400 Foreign key constraint failed.
NULL_CONSTRAINT_VIOLATION 400 A required (non-nullable) field received null.
REQUIRED_RELATION_VIOLATION 400 A required relation is missing.
DATABASE_TIMEOUT 503 Database connection pool timeout. Clients should retry.
DATABASE_ERROR 400 Unmapped Prisma error code.

Setup Wizard Error Codes

Code Status Params When
SETUP_STEP_MISMATCH 409 { expected, received } Frontend's currentStep doesn't match backend state. User should reload.
SETUP_INVALID_NAVIGATION 400 Cannot navigate to target step (only backward, same, or next allowed).
SETUP_DATA_REQUIRED 400 Data is required for forward navigation.
SETUP_STEP_INCOMPLETE 400 { step } Step not complete, can't advance.
SETUP_VALIDATION_FAILED 400 { reason, field? } Step-specific business rule validation failed. See reason values below.

SETUP_VALIDATION_FAILED reason values

Frontend uses compound key SETUP_VALIDATION_FAILED.{reason} for translation:

reason When Example field
date_range_invalid startDate >= endDate academicYear, periods
period_out_of_bounds period outside year range periods
overlap overlapping periods of same type periods
duplicate_name non-unique name (case-insensitive) departments, grades, periods
ordinal_gap non-sequential ordinal positions departments, grades
missing_departments not all tenant depts represented departments
missing_year no academic year found
invalid_department department not owned by tenant departments

Import Validation Error Codes

Top-level envelope uses IMPORT_VALIDATION_FAILED (422). Each error inside data.errors[] carries an ImportErrorCode for i18n.

{
  "statusCode": 422,
  "code": "IMPORT_VALIDATION_FAILED",
  "message": "Import validation failed",
  "data": {
    "errors": [
      { "code": "FIELD_INVALID", "column": "gender", "rows": "2-5, 8", "allowedValues": ["MALE", "FEMALE"] },
      { "code": "HEADERS_MISSING", "column": "headers", "rows": "1", "params": ["last_name"], "allowedValues": ["first_name", "last_name"] }
    ]
  }
}
Code Description
HEADERS_MISSING Expected columns not found. params lists missing names.
FIELD_REQUIRED A required field is empty.
FIELD_MAX_LENGTH Value exceeds maximum length.
FIELD_INVALID Value doesn't match allowed options. allowedValues lists valid options.

Validation Errors

Produced by the global ValidationPipe (class-validator). Returns structured field-level errors:

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "Validation failed",
  "data": {
    "errors": [
      { "field": "email", "rule": "isEmail" },
      { "field": "anagraphic.firstName", "rule": "isNotEmpty" },
      { "field": "anagraphic.firstName", "rule": "maxLength", "params": { "max": "100" } }
    ]
  }
}

Each error has: - field — dot-notation path to the field - rule — class-validator constraint name (e.g. isEmail, isNotEmpty, maxLength, minLength) - params — optional constraint parameters for interpolation (e.g. { max: "100" })

Frontend maps rule to a translated message: validation.isEmail → "Must be a valid email address".

Custom Fields Error Codes

Custom field validation errors use VALIDATION_FAILED with English messages. Custom field CRUD errors:

Code Status When
VALIDATION_FAILED 400 Wrong type, unknown field key, missing required field, invalid SELECT value, SELECT missing options, duplicate options.
CONFLICT 409 Duplicate fieldKey within same tenant+entity.
FORBIDDEN_FIELDS 403 User lacks WRITE access on target entity+scope.
NOT_FOUND 404 Definition not found, or scope not found in permission catalogue.

Typed Swagger Schemas

Swagger documents 7 distinct error response shapes. Each is a DTO class in src/common/dto/ — the base carries no params/data, subclasses add strongly typed fields.

# DTO Codes params type data type
1 ErrorResponseDto (base) 20 simple codes (AUTH_*, RATE_LIMITED, INSUFFICIENT_SCOPE, etc.)
2 EntityErrorResponseDto NOT_FOUND, CONFLICT EntityErrorParams { entity }
3 StepMismatchErrorResponseDto SETUP_STEP_MISMATCH StepMismatchErrorParams { expected, received }
4 StepIncompleteErrorResponseDto SETUP_STEP_INCOMPLETE StepIncompleteErrorParams { step }
5 SetupValidationErrorResponseDto SETUP_VALIDATION_FAILED SetupValidationErrorParams { reason, field? }
6 ValidationErrorResponseDto VALIDATION_FAILED ValidationErrorData { errors: ValidationFieldErrorDto[] }
7 ImportErrorResponseDto IMPORT_VALIDATION_FAILED ImportValidationData { errors: ImportValidationErrorDto[] }

Source files: - Base + params: src/common/dto/error-response.dto.ts, src/common/dto/error-params.dto.ts - Subclasses: src/common/dto/typed-error-responses.dto.ts - Validation data: src/common/dto/validation-error-response.dto.ts, src/common/dto/import-validation-error.dto.ts

Runtime note: The AllExceptionsFilter continues to produce plain JSON objects. The typed DTOs are purely Swagger schema documentation — the filter's output already matches the DTO structures.

AppException Constructor

class AppException<TData = undefined> extends HttpException {
  constructor(
    code: ErrorCode,
    message: string,
    statusCode?: HttpStatus,          // default: 500
    options?: { data?: TData; params?: Record<string, string> },
  );
}