Multi-tenant backend platform for identity, organizations, users, tenant-scoped RBAC, immutable auditability and operational observability. The codebase remains a modular monolith with DDD, Clean Architecture and explicit internal boundaries.
Current Phase: Phase 5 — Quality, Observability & Engineering
Notion reference: Nexus Platform
- Node.js 24 LTS
- TypeScript (
strict) - NestJS
- PostgreSQL
pgwith explicit repositories- Argon2
- JWT
- Pino
- OpenTelemetry
- Docker
- GitHub Actions
- production-oriented validation at the HTTP boundary with DTOs,
ValidationPipenormalization and a stable error contract - a single API error format with semantic snake_case codes and
correlation_idmirrored inx-correlation-id - structured logs with
timestamp,level,message,module,correlation_id,tenant_idanduser_id - request-level correlation propagation across HTTP, use cases, database access, internal events and audit rows
- manual telemetry instrumentation for HTTP entrypoints, critical use cases,
DatabaseExecutor,InternalEventBusand authorization flow - scrape-friendly Prometheus metrics at
GET /metrics - explicit pagination and reasonable limits for
GET /audit-logsandGET /organizations/:id/memberships - broader unit, integration and functional coverage for identity, organizations, users, access-control, audit logs, validation and observability
GET /healthGET /metricsPOST /identity/accountsPOST /identity/loginPOST /identity/logoutPOST /organizationsGET /organizations/:idPATCH /organizations/:id/inactivePOST /organizations/:id/membershipsGET /organizations/:id/memberships?limit=<1-100>&offset=<0-1000>POST /rolesGET /rolesPOST /roles/:id/permissionsGET /permissionsPOST /users/:id/rolesGET /audit-logs?tenantId=<tenant>&limit=<1-100>&offset=<0-1000>
Authenticated User
-> tenant context resolved from the active session
-> active organization validation
-> active membership validation
-> roles assigned inside the tenant
-> permissions resolved from those roles
-> allow / deny
- Every tenant receives a default
organization_adminrole. - The role is granted the full default permission catalog for the current phase.
- Existing active memberships were backfilled with
organization_adminin the Phase 3 migration. - New organizations bootstrap the creator membership and the
organization_adminassignment in the same transaction flow.
organization:vieworganization:deactivatemembership:createmembership:viewrole:createrole:viewpermission:viewrole:grant-permissionrole:assignuser:createuser:updateaudit:view
All non-success HTTP responses now follow the same payload shape:
{
"error": "permission_denied",
"message": "Permission denied",
"correlation_id": "2d2dbbc8-b6dc-4c8f-a76e-b8578dd8d6d8"
}Rules applied in Phase 5:
- semantic codes are stable and snake_case
- request validation fails early with
invalid_request - functional errors stay explicit
- technical failures return generic
internal_error x-correlation-idis always returned and matchescorrelation_idin error payloads- authentication and logging never expose password, token or stack details at the HTTP boundary
HTTP request
-> correlation id resolved at the request boundary
-> guards resolve principal + tenant + permission
-> module executes the main use case
-> audited modules publish an internal event in-band
-> audit-logs subscriber appends an immutable row
-> GET /audit-logs reads by tenant with RBAC protection and pagination
login_successlogin_failedlogoutorganization_createdorganization_deactivateduser_createdmembership_assignedrole_createdpermission_grantedrole_assignedauthorization_denied
GET /audit-logsrequiresaudit:viewtenantIdin the query must match the active tenant from the authenticated session- optional filters:
userId,action,from,to - pagination defaults to
limit=50andoffset=0 - public limits are
limit <= 100andoffset <= 1000 - ordering stays
timestamp DESC, id DESC - bootstrap-session or failed-login rows may persist
tenantId = nullwhen no tenant context exists
Every HTTP request and main operational event emits structured logs aligned around:
timestamplevelmessagemodulecorrelation_idtenant_iduser_id
Sensitive headers and values such as Authorization, cookies, tokens, passwords and error stacks are sanitized at the boundary.
- incoming requests reuse
x-correlation-idwhen present or generate a new UUID - the correlation id flows into guards, use cases, internal events and persisted audit rows
- manual OpenTelemetry spans cover HTTP entrypoints, critical use cases,
DatabaseExecutor,InternalEventBusand authorization decisions
GET /metrics exposes Prometheus-compatible metrics including:
nexus_http_requests_totalnexus_http_request_duration_msnexus_module_failures_totalnexus_identity_logins_totalnexus_authorization_decisions_totalnexus_audit_operations_totalnexus_audit_operation_duration_ms
- Unit: domain rules, use cases, error mapping, validation mapping, logging and telemetry helpers
- Integration: real PostgreSQL flows through Testcontainers for login, tenant resolution, authorization, audit immutability, pagination and indexes
- Functional: end-to-end HTTP scenarios for authenticated tenant access, permission denial, audit correlation, metrics exposure and validation failures
src/
bootstrap/
config/
errors/
http/
logging/
persistence/
telemetry/
modules/
identity/
application/
domain/
infrastructure/
organizations/
application/
domain/
infrastructure/
users/
application/
domain/
infrastructure/
access-control/
application/
domain/
infrastructure/
audit-logs/
application/
domain/
infrastructure/
shared/
auth/
events/
tenancy/
request-correlation/
domain/
test/
unit/
integration/
functional/
docs/
migrations/
Use .env.example as the baseline:
APP_PORT=3000
AUTH_JWT_SECRET=change-me-in-production
AUTH_JWT_EXPIRES_IN_MINUTES=480
DB_HOST=postgres
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=nexus
NODE_ENV=developmentcp .env.example .env
npm install
npm run db:migrate
npm run devThe application also applies pending SQL migrations during bootstrap, so npm run db:migrate is the explicit operational command and app startup remains the safety net.
cp .env.example .env
npm run docker:up
curl http://localhost:3000/health
curl http://localhost:3000/metrics
npm run docker:logs
npm run docker:downThe existing Makefile mirrors these flows with make up, make down, make run, make test, make test-unit, make test-integration and make lint, but the package scripts remain the primary operational commands.
npm run lint
npm run build
npm run test:unit
npm run test:integration
npm run test:functional
npm run ciintegration and functional suites use Testcontainers and require a running Docker daemon. When Docker is unavailable, those suites are skipped locally; CI runs them with Docker enabled.
- Modular Monolith remains the deployment model.
identityowns authentication, sessions and the authenticated principal.organizationsowns tenant lifecycle and tenant-scoped membership flows.usersowns the global user record plusmemberships.access-controlowns roles, permissions, user-role assignments and the final authorization decision.audit-logsowns the append-only audit trail plus tenant-scoped query access.- internal events are synchronous and in-process; they exist to decouple audit persistence, not to hide primary business flow.
- PostgreSQL access stays explicit through repositories and SQL, without ORM.
- audit and membership queries are tenant-aware, paginated and backed by explicit composite indexes.