Skip to content
Back to projects

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

  1. Browser · React SPA

  2. HTTPS / WebSocket

    NestJS API · N instances

    Guards: auth · tenant · RBAC

  3. Data and storage

    PostgreSQL

    Redis

    Cache · Rate limit · BullMQ queues · Socket.io adapter

    S3

    Presigned URLs

  4. BullMQ queues

    Asynchronous processing

    Workers

    write results to PostgreSQL

    PDF · Puppeteer

From the browser to the API, with PostgreSQL, Redis and S3 underneath and workers handling asynchronous processing.

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

  1. Client

  2. request

    API

  3. BEGIN · COMMIT

    PostgreSQL

    writes the entity and the outbox message in one transaction

  4. NOTIFY outbox (or polling every 1 s)

    Relay

    SELECT ... FOR UPDATE SKIP LOCKED

  5. publishes (jobId = message id)

    BullMQ

  6. processes

    Worker

    writes the result to PostgreSQL

The message is born in the same transaction as the operation and only then published, with no duplicates even with several relay instances.
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;
  });
}
The outbox relay: reads the batch with FOR UPDATE SKIP LOCKED, publishes with the message id as jobId and, if a publish fails, stops the batch to keep the order and retries on the next pass.

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

  1. Request

    one correlation id for every write of the action

  2. 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

  3. LISTEN/NOTIFY, with a safety poll

    Outbox relay

  4. jobId = event id

    Audit queue

  5. INSERT ... ON CONFLICT DO NOTHING

    Audit worker

    concurrency 1: version = MAX + 1 in commit order; a redelivery is a no-op

  6. audit_events

    append-only, partitioned by month on occurred_at

The event is born in the same transaction as the write and only becomes an audit row after going through the outbox, the queue and the worker.

From request to history panel

  1. Request with the audit.read permission

  2. Query filters

    Clinic

    Allowed types

    explicit list; any other type is rejected

    Period and cursor

    only the partitions of the period are read

  3. successful events

    Rebuild and masking

    replays the changes in order to find the previous value; secrets masked again

  4. paged JSON

    History panel

    content hidden without read permission on the record

Reading filters by clinic, allowed type and period, rebuilds the previous value of each field and hides the content from whoever may not read it.
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 */],
  );
}
The only place that writes the audit trail: the version is MAX + 1 per record and the INSERT is idempotent on (id, occurred_at), so a queue redelivery duplicates nothing.
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>;
}
Masking, applied on write and again on read: it walks the JSON at any depth and replaces the value of keys that look like secrets.
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()}')`,
    );
  }
});
The partition job: under an advisory lock, it idempotently creates the current month and the next three, with no race between replicas.

Screenshots of a development environment, with fictitious data.

Side panel with the users history, with period and change filters and a list of who did what and when.
The history of a list: who did what and when, with period and change-type filters and cursor-based loading.
History panel filtered by deletions, showing that the administrator removed a user access.
The period, change-type and author filters: here only the deletions, which also leave a trace.
History panel of one user, showing the role change from Recepção to Financeiro, with the previous value struck through.
The history of one record: each change shows the previous value struck through next to the new one, and the role id appears as the role name.

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

  1. Service on instance 1

  2. Real-time hub

  3. Redis adapter

  4. Instance 1 · sockets

    Instance 2 · sockets

    Instance N · sockets

An event emitted on any instance reaches the sockets of all the others through the Redis adapter.

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.

List of the clinic users, with name, email, role, join date and the history, edit and remove-access actions.
Role-based access: each person has a role, which is a set of permissions, and the history button only shows for whoever has the audit permission.

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

  1. Route

  2. Valid session?

    no: goes to login

  3. Platform area?

  4. Clinic selected?

  5. Has permission?

  6. yes / no

    Page

    Access denied (planned step)

Every route goes through the guard chain: session, platform area, clinic and permission.

Screenshots of a development environment, with fictitious data.

Command palette open with the search "usu", listing the users screen and the fields on it.
The command palette (Ctrl K): it searches screens and also settings fields.

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);
});
The test that cross-checks the docs against the code: every route declares a permission (or an explicit opt-out) and the documented permission must be the handler one.

Screenshots of a development environment, with fictitious data.

OpenAPI docs published with Scalar, showing the audit history route, a sample call and the response.
The API docs, published with Scalar from the OpenAPI files that the test above keeps faithful to the code.