Luhmera
Partner and software architect – present
Overview
Multi-tenant SaaS platform for clinics, with a NestJS + PostgreSQL back-end and a React front-end. I owned the architecture and end-to-end implementation: tenant isolation, reliable asynchronous processing, auditing, real time, security and testing.
- Tenant isolation proven by integration tests against real Postgres and Redis.
- Reliable event delivery with a transactional outbox and BullMQ queues.
- On the back end: 37 modules, about 315 HTTP endpoints, 62 versioned migrations and about 730 test cases, plus a front end with over 100 routes.
Architecture
Browser · React SPA
- HTTPS / WebSocket
NestJS API · N instances
Guards: auth · tenant · RBAC
Data and storage
PostgreSQL
Redis
Cache · Rate limit · BullMQ queues · Socket.io adapter
S3
Presigned URLs
- BullMQ queues
Asynchronous processing
Workers
write results to PostgreSQL
PDF · Puppeteer
Multi-tenancy and isolation
- One tenant per clinic
- Each clinic is a tenant, with data isolated by tenant_id and enforced by guards at the start of every request.
- Proven isolation
- An integration suite goes through the real HTTP stack (guards, pipes, filters) on real Postgres and Redis and proves that one clinic can never read or change another's data.
- Access revoked instantly
- Removing or demoting a user takes effect on the very next request, without waiting for the token to expire.
- Platform administration
- Platform staff have a separate tenant-administration area with controlled access.
Events, outbox and queues
- Transactional outbox
- Transactional outbox: the message is written in the same transaction as the business operation, so nothing is published if it fails.
- Relay woken by Postgres
- A relay moves messages to BullMQ, woken by Postgres LISTEN/NOTIFY, with a one-second safety poll.
- Several instances, no duplicates
- Batches locked with FOR UPDATE SKIP LOCKED, so several instances can relay at once without double-publishing.
- Idempotent publishing
- Idempotent publishing (job id = message id), preserved ordering and automatic retry on failure.
- Per-domain queues
- Per-domain BullMQ queues (audit, invoices, notifications, etc.), processed by workers in dedicated processes.
- Five workers
- Five BullMQ workers (audit, encounters, dictionary, invoices and notifications). A flag turns workers and the relay on or off per process, allowing API-only replicas.
- No double issuing
- The invoice worker uses the document id as the idempotency key at the provider, so a job retry never issues twice; the provider is injectable, with a fake one for tests.
Outbox
Client
- request
API
- BEGIN · COMMIT
PostgreSQL
writes the entity and the outbox message in one transaction
- NOTIFY outbox (or polling every 1 s)
Relay
SELECT ... FOR UPDATE SKIP LOCKED
- publishes (jobId = message id)
BullMQ
- processes
Worker
writes the result to PostgreSQL
private async publishBatch(): Promise<number> {
return this.dataSource.transaction(async (manager) => {
const rows: PendingRow[] = await manager.query(
`SELECT id, queue, job_name, payload FROM infra.outbox
WHERE published_at IS NULL
ORDER BY seq
LIMIT $1
FOR UPDATE SKIP LOCKED`,
[OUTBOX_BATCH_SIZE],
);
const published: string[] = [];
for (const row of rows) {
try {
await withTimeout(
this.queues.publish(row.queue, row.job_name, row.payload, { jobId: row.id }),
PUBLISH_TIMEOUT_MS,
);
published.push(row.id);
} catch (error) {
await manager.query(
'UPDATE infra.outbox SET attempts = attempts + 1, last_error = $2 WHERE id = $1',
[row.id, (error as Error).message],
);
break;
}
}
if (published.length > 0) {
await manager.query('UPDATE infra.outbox SET published_at = now() WHERE id = ANY($1)', [
published,
]);
}
return published.length === rows.length ? rows.length : 0;
});
}Audit trail
- Written together with the write
- Every write (create, update, delete, restore) goes through a base repository that enqueues the audit event in the outbox, in the same transaction as the operation: the event exists if, and only if, the write committed, and it survives a crash or a Redis outage before reaching the queue.
- What each event stores
- The event records who did it (a user or the system), which record, the operation, what changed, when the change happened (not when it was processed) and a correlation id shared by every write of one user action.
- A delta, not a diff
- On update the delta is exactly what the caller passed; on create, the initial snapshot. There is no computed diff and no extra read of the record.
- Failures leave a trace too
- Failed writes leave a trace too: an event with success=false and the error message, written on its own connection (Postgres has already aborted the original transaction) without hiding the real error. It can be turned off per call.
- Audited export
- Exporting the clinic data is an audited act too (EXPORT operation): the event stores the author and the counts, never the exported data.
- Ordered, idempotent worker
- The audit worker runs with concurrency 1, so each record version (MAX + 1) follows commit order, and the INSERT is idempotent on (id, occurred_at): a queue redelivery duplicates nothing.
- Append-only, partitioned table
- The table is append-only: no soft delete, never edited or removed. It is partitioned by month on occurred_at (primary key (id, occurred_at)), with a default partition as a safety net and an idempotent job, under an advisory lock, that guarantees the current month and the next three. The migration converted the existing table by copying the data and has a rollback.
- Secrets masked twice
- Secrets (passwords, hashes, tokens, certificates, credentials) are masked at any depth of the JSON on write and masked again on read, to cover older events.
- Two ways to read
- Reading comes in two forms: the history of one record, which replays the changes in order to show the previous value of each field, and the list of the latest changes of a type, with (occurred_at, id) cursor pagination and filters for period (default: 90 days in the clinic calendar, which limits the partitions read), operation and author.
- Only what may be audited
- The query only returns successful events of the caller clinic and for 30 record types from an explicit list; any other type is rejected. Record names come from the creation snapshot, even for records deleted later, and role ids become role names.
- Content protected by permission
- Content is protected by permission: whoever has audit.read sees who did what and when, but the fields of sensitive records only show for whoever may also read the record itself. A user and their membership in the clinic form a single timeline.
- History panel on the front end
- On the front end, a History button (clock) on lists and rows opens a panel with period and operation filters, the previous value struck through next to the new one, a lock when content is hidden and loading of more items. The button only shows for whoever has audit.read.
- Tests on real Postgres
- Integration tests against real Postgres cover the previous-value rebuild, hiding failed writes, isolation between clinics, per-record-type permission, the cursor, the user timeline, partitions and the worker through the queue; a unit test covers masking.
From write to audit record
Request
one correlation id for every write of the action
- COMMIT
Base repository
writes the record and the outbox event in the same transaction
Failed write (planned step)
success=false event on its own connection, and the original error is rethrown
- LISTEN/NOTIFY, with a safety poll
Outbox relay
- jobId = event id
Audit queue
- INSERT ... ON CONFLICT DO NOTHING
Audit worker
concurrency 1: version = MAX + 1 in commit order; a redelivery is a no-op
audit_events
append-only, partitioned by month on occurred_at
From request to history panel
Request with the audit.read permission
Query filters
Clinic
Allowed types
explicit list; any other type is rejected
Period and cursor
only the partitions of the period are read
- successful events
Rebuild and masking
replays the changes in order to find the previous value; secrets masked again
- paged JSON
History panel
content hidden without read permission on the record
export async function writeAuditEvent(
manager: EntityManager,
event: AuditEventInput,
): Promise<void> {
await manager.query(
`INSERT INTO infra.audit_events
(id, tenant_id, entity, entity_id, operation, changes, actor_id, version, success, correlation_id, occurred_at)
SELECT $1::uuid, $2::uuid, $3::varchar, $4::varchar, $5::infra.audit_events_operation_enum, $6::jsonb,
$7::varchar, COALESCE(MAX(version), 0) + 1, $8::boolean, $9::varchar, $10::timestamptz
FROM infra.audit_events
WHERE entity = $3::varchar AND entity_id = $4::varchar
ON CONFLICT (id, occurred_at) DO NOTHING`,
[/* id, tenantId, entity, entityId, operation, changes, actorId, success, correlationId, occurredAt */],
);
}const SENSITIVE_KEY = /password|secret|token|hash|pfx|certificate|credential|credenciais/i;
export function redactSensitive(value: Record<string, unknown>): Record<string, unknown> {
const walk = (node: unknown): unknown => {
if (Array.isArray(node)) return node.map(walk);
if (node === null || typeof node !== 'object') return node;
return Object.fromEntries(
Object.entries(node as Record<string, unknown>).map(([key, child]) => [
key,
SENSITIVE_KEY.test(key) && child !== null && child !== undefined ? REDACTED : walk(child),
]),
);
};
return walk(value) as Record<string, unknown>;
}await this.dataSource.transaction(async (manager) => {
await manager.query(`SELECT pg_advisory_xact_lock(hashtext('audit_events_partitions'))`);
for (let offset = 0; offset <= AUDIT_PARTITION_MONTHS_AHEAD; offset++) {
const from = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + offset, 1));
const to = new Date(Date.UTC(from.getUTCFullYear(), from.getUTCMonth() + 1, 1));
const name = `audit_events_${from.toISOString().slice(0, 7).replace('-', '_')}`;
await manager.query(
`CREATE TABLE IF NOT EXISTS "infra"."${name}" PARTITION OF "infra"."audit_events" FOR VALUES FROM ('${from.toISOString()}') TO ('${to.toISOString()}')`,
);
}
});Screenshots of a development environment, with fictitious data.



Real time
- Namespaces and rooms
- Real-time updates with Socket.io, organized by namespaces and rooms.
- Horizontal scaling
- With the Redis adapter, an event emitted on any API instance reaches users connected to every other instance, enabling horizontal scaling.
- Decoupled emitting
- Any service can emit to a room without depending on the gateway module.
Real time
Service on instance 1
Real-time hub
Redis adapter
Instance 1 · sockets
Instance 2 · sockets
Instance N · sockets
Security
- Identity
- Authentication with JWT, TOTP-based MFA (authenticator app with QR code) and Argon2 password hashing.
- Secrets at rest
- MFA secrets and integration credentials encrypted at rest with AES-256-GCM.
- RBAC checked by a test
- Resource/action RBAC, plus a test that fails if any API route lacks a declared permission.
- Edge protection
- Redis-backed distributed rate limiting, Helmet and Zod input validation; configuration validated at startup.
- Brute force in two layers
- Brute-force protection in two layers: one limit per IP + account pair and a higher one per account alone, so the legitimate owner is not locked out by someone attacking from another IP.
- Sessions in Redis
- Sessions in Redis store only the refresh-token hash and have an absolute maximum age: refreshing does not extend a session forever. The access token carries only ids; permissions come from a lookup of the live session.
- Token in the header only
- The token travels only in the Authorization header, never in a cookie, which removes the CSRF surface by design. Per-environment CORS and CSP through Helmet.
Screenshots of a development environment, with fictitious data.

Front-end
- SPA with a guard chain
- React SPA with Vite and React Router, with a per-route guard chain: session, platform area, clinic and permission.
- Server and global state
- Server state with TanStack Query (tuned caching and retry policy); light global state with Jotai.
- Standardized forms
- Forms built on react-hook-form + Zod through an in-house hook that standardizes loading, validation and submission.
- Design system and themes
- Design system on Radix/shadcn and Tailwind, with per-clinic configurable visual themes.
- UX details
- UX details: command palette, delete with undo, unsaved-changes guard and paginated lists.
- Declarative routing
- Declarative routing: a route table, a guard runner and a layout runner, with composed guards (session, platform, clinic, permission). It uses the React Router data router to block navigation with unsaved changes.
- Token outside React
- Token state lives outside React, in a subscribable store read through useSyncExternalStore, so the axios interceptor does not depend on hooks. In-house hooks standardize paginated lists, debounce, form loading and edit-loss protection.
Front-end guards
Route
Valid session?
no: goes to login
Platform area?
Clinic selected?
Has permission?
- yes / no
Page
Access denied (planned step)
Screenshots of a development environment, with fictitious data.

Quality and delivery
- Tests on real containers
- Integration tests with Testcontainers (PostgreSQL and Redis), Supertest and Faker-generated data; front-end e2e tests with Cypress.
- Migrations always in sync
- A test that keeps migrations in sync with the entities.
- Documented, monitored API
- OpenAPI docs published with Scalar; database and Redis health checks with Terminus.
- PDFs and files
- Server-side PDF generation with Puppeteer and S3 storage via presigned URLs.
- Docker and AWS
- Docker, and AWS deployment with CloudFormation.
- OpenAPI cross-checked with the code
- A test cross-checks the OpenAPI docs against the code: every route must be documented, and the permission declared in the document must equal the handler decorator; a route with neither a permission nor an explicit opt-out fails the test.
- CI and delivery
- CI with lint, typecheck, build, isolation and migration tests and secret scanning (gitleaks pinned by hash), cancelling stale runs and with minimal permissions. Deploy uses GitHub OIDC and SSM, with no static credentials.
- Versioned Git hooks
- Versioned Git hooks: on commit, lint of the changed files only and secret scanning; on push, project typecheck and only the tests related to what changed.
- Operations
- Operations: graceful shutdown hooks, environment variables validated at startup, idempotent migrations and seeds before the API starts, and a command to remove and restore a clinic with explicit confirmation.
it('every endpoint declares a permission or opts out explicitly', () => {
expect(endpoints.filter((e) => e.access === 'MISSING').map(label)).toEqual([]);
});
it.each(endpoints.map((e) => [label(e), e]))('%s', (_name, endpoint) => {
const e = endpoint as EndpointInfo;
const operation = doc.paths[e.path]?.[e.method];
expect(operation).toBeDefined();
const expected = e.access === 'public' || e.access === 'authenticated' ? undefined : e.access;
expect(operation!['x-required-permission']).toBe(expected);
});Screenshots of a development environment, with fictitious data.
