Skip to content

System Architecture

Overview

Floh is a three-tier application consisting of an Angular frontend, a Fastify REST API backend, and a relational database with Redis for job queuing.

Component Diagram

┌─────────────────┐     ┌──────────────────────┐     ┌──────────────┐
│  Angular 21 UI  │────▶│  Fastify REST API     │────▶│ PostgreSQL / │
│  (PrimeNG)      │     │  (Node.js 24)         │     │ MySQL        │
└─────────────────┘     │                        │     └──────────────┘
                        │  ┌─────────────────┐  │     ┌──────────────┐
                        │  │ Workflow Engine  │  │────▶│ Redis        │
                        │  │ BullMQ Scheduler │  │     │ (BullMQ)     │
                        │  └─────────────────┘  │     └──────────────┘
                        │                        │     ┌──────────────┐
                        │  ┌─────────────────┐  │────▶│ SMTP Server  │
                        │  │ Notification Svc │  │     └──────────────┘
                        │  └─────────────────┘  │     ┌──────────────┐
                        │                        │────▶│ OIDC Provider│
                        └──────────────────────┘     └──────────────┘

Backend Modules

Module Responsibility
auth OIDC JWT validation, RBAC guards
users User CRUD, role assignment
workflows Definition CRUD, run lifecycle
tasks Step/task management
approvals Approval routing and decisions
notifications Email and in-app notifications
connectors Connector registry and execution
scheduler Cron-based workflow triggers
audit Immutable audit logging
reports Aggregate statistics queries
health System health check

Data Flow: Workflow Execution

  1. User starts a workflow run via POST /workflows/:id/start
  2. The engine creates step records from the definition
  3. Each step is executed based on its type:
  4. action — runs immediately, stores result
  5. approval — creates approval records, pauses workflow
  6. notification — sends email and in-app notification (internal users resolved by ID; external addresses supported)
  7. connector — invokes registered connector with timeout
  8. condition — evaluates expression, sets branch variable
  9. On step completion, the engine checks transitions to determine the next step
  10. On error, the engine follows the onError strategy (stop/skip/retry)
  11. When all steps complete, the run is marked completed

Escalation Flow

  1. An approval step is created with a timeout
  2. BullMQ schedules a delayed job for the deadline
  3. If the deadline passes without a decision, the escalation fires
  4. The step is reassigned to the escalation target
  5. Both parties are notified

Database Schema

13 tables: user, role, permission, user_role, role_permission, workflow_definition, workflow_run, workflow_step, approval, notification, connector_definition, audit_log, scheduled_trigger.

All primary keys are UUIDs. JSON data (steps, variables, config) is stored as TEXT columns and serialized/deserialized in the repository layer.

Public Portal Architecture

The public portal enables external users (invitees, task assignees, approvers) to interact with Floh without direct access to the firewalled admin interface.

                         ┌───── Firewall ─────┐
                         │                     │
┌──────────────┐   ┌─────────────┐   ┌──────────────────────┐   ┌──────────────┐
│  Portal SPA  │──▶│ Portal BFF  │──▶│  Fastify REST API     │──▶│ PostgreSQL / │
│  (Angular)   │   │ (Fastify    │   │  (Node.js 24)         │   │ MySQL        │
│              │   │  stateless  │   │                        │   └──────────────┘
│ port 4201    │   │  proxy)     │   │                        │   ┌──────────────┐
│              │   │ port 3001   │   │                        │──▶│ Redis        │
└──────────────┘   └─────────────┘   └──────────────────────┘   └──────────────┘

Portal BFF (Backend-for-Frontend)

The Portal BFF is a stateless Fastify proxy that sits outside the firewall. It has no direct database, Redis, or OIDC connections, minimizing its attack surface.

Responsibility Implementation
Route whitelisting Only a predefined set of user-facing API patterns are forwarded
Scope enforcement scope=all is stripped from tasks/approvals so portal users only see their own
Header injection Adds X-Portal-Origin so the internal server redirects to the portal after auth
Rate limiting @fastify/rate-limit protects against abuse
Security headers @fastify/helmet with strict CSP
CORS Locked to the portal frontend URL

Portal SPA

A minimal Angular application with only the routes external users need:

  • /welcome — landing page
  • /dashboard — pending invitations, tasks, and approvals
  • /tasks — task inbox (tasks and approvals, always scope=mine)
  • /invitations/respond — accept or decline invitations
  • /auth/callback — OIDC callback handler

The portal SPA has no sidebar, admin panel, workflow designer, or any administrative functionality.

Portal-Aware Auth Redirects

When the BFF proxies auth requests, it injects an X-Portal-Origin header. The internal server checks this header against ALLOWED_PORTAL_ORIGINS and redirects to the portal frontend (instead of the admin frontend) after login, logout, and errors.

For more details, see the Portal Guide.

Deployment

Docker Compose

The default stack includes: - postgres — PostgreSQL 16 - redis — Redis 7 - server — Fastify API (port 3000) - web — Angular app via nginx (port 80) - mailhog — SMTP test server (port 8025)

Use docker-compose.mysql.yml as an override for MySQL deployments.

Portal Compose

The portal extends the default stack with: - portal-bff — Stateless proxy (port 3001) - portal-web — Portal SPA via nginx (port 4201)

docker compose -f docker/docker-compose.yml -f docker/docker-compose.portal.yml up

Environment Configuration

All configuration is via environment variables. See .env.example for the full list.