Skip to main content

Metabase Module — Architecture

Overview

The Metabase module (apps/backend/src/metabase/) manages Metabase dashboard configurations and generates JWT-signed embed URLs for multi-tenant dashboard embedding in the FlowPOS platform.

It is not the Metabase server itself — it's the backend layer that:

  • Stores dashboard metadata (code, name, description, business scoping)
  • Maps dashboards to environment-specific Metabase dashboard IDs (staging vs production)
  • Controls which roles and business modules can see which dashboards
  • Associates dashboards with UI forms for page-level embedding
  • Generates secure JWT embed URLs with locked business_id parameters

Domain Entities

EntityTablePurpose
Dashboardmetabase_dashboardCore entity — human-readable code, name, description, optional business scoping
Dashboard Instancemetabase_dashboard_instanceMaps a dashboard to a numeric Metabase dashboard ID per environment (staging/production)
Dashboard Modulemetabase_dashboard_moduleAssociates dashboards with business modules (e.g. inventory, sales)
Dashboard Formmetabase_dashboard_formAssociates dashboards with UI form paths for page-level embedding
Dashboard Rolemetabase_dashboard_roleRBAC — controls which user roles can access which dashboards

Architecture (Hexagonal)

domain/
├── metabase-dashboard-repository.domain.ts # Dashboard repository port
├── metabase-dashboard-instance-repository.domain.ts # Instance repository port
├── metabase-dashboard-module-repository.domain.ts # Module repository port
├── metabase-dashboard-form-repository.domain.ts # Form repository port
├── metabase-dashboard-role-repository.domain.ts # Role repository port
└── metabase-embedding.types.ts # EmbedUrlParams interface

application/
└── metabase-dashboard.service.ts # Orchestrates all use cases

infrastructure/
├── metabase-dashboard.repository.ts # Kysely adapter for dashboards
├── metabase-dashboard-instance.repository.ts # Kysely adapter for instances
├── metabase-dashboard-module.repository.ts # Kysely adapter for modules
├── metabase-dashboard-form.repository.ts # Kysely adapter for forms
├── metabase-dashboard-role.repository.ts # Kysely adapter for roles
└── metabase-embedding.service.ts # JWT signing + URL generation

interfaces/
├── metabase.controller.ts # HTTP routes
├── dtos/ # Request DTOs with validation
└── query/ # Pagination/sorting query objects

Key Use Cases

1. Dashboard Embed URL Generation

The primary use case. Given a dashboard code and a business ID:

  1. Look up the dashboard by code
  2. Verify the dashboard is active and accessible to the business
  3. Check role-based access (if user role is available)
  4. Resolve the environment-specific Metabase dashboard ID (staging vs production, based on DEPLOY_ENV)
  5. Sign a JWT with { resource: { dashboard: <id> }, params: { business_id: <uuid> } }
  6. Return the full embed URL: <METABASE_SITE_URL>/embed/dashboard/<jwt>#bordered=true&titled=true

The embed URL endpoint also accepts numeric Metabase dashboard IDs directly for backward compatibility.

2. Dashboard CRUD

Standard CRUD for managing dashboard metadata. Dashboards are identified by:

  • UUID (id) — primary key
  • Code (code) — human-readable slug (e.g. sales-overview)

3. Sub-entity Management

Each sub-entity (instance, module, form, role) follows a standard pattern:

  • List all for a dashboard
  • Get one by composite key (dashboard + secondary key)
  • Create (with uniqueness check)
  • Update
  • Delete

API Endpoints

Dashboard CRUD

MethodPathDescription
POST/reports/dashboardCreate dashboard
GET/reports/dashboardList dashboards (paginated, filterable)
GET/reports/dashboard/code/:codeGet by code
GET/reports/dashboard/:idGet by UUID
PATCH/reports/dashboard/:idUpdate dashboard
DELETE/reports/dashboard/:idDelete dashboard

Embed URL

MethodPathDescription
GET/reports/dashboard/:code/embed-url?businessId=<uuid>Generate embed URL (accepts code or numeric ID)

Dashboard Instances

MethodPathDescription
GET/reports/dashboard/:id/instancesList instances
GET/reports/dashboard/:id/instance/:environmentGet instance
POST/reports/dashboard/:id/instance/:environmentCreate instance
PATCH/reports/dashboard/:id/instance/:environmentUpdate instance
DELETE/reports/dashboard/:id/instance/:environmentDelete instance

Dashboard Modules

MethodPathDescription
GET/reports/dashboard/:id/modulesList modules
GET/reports/dashboard/:id/module/:moduleIdGet module
POST/reports/dashboard/:id/module/:moduleIdCreate module
PATCH/reports/dashboard/:id/module/:moduleIdUpdate module
DELETE/reports/dashboard/:id/module/:moduleIdDelete module

Dashboard Forms

MethodPathDescription
GET/reports/dashboard/by-form/:formPathGet dashboards by form path
GET/reports/dashboard/:id/formsList forms
GET/reports/dashboard/:id/form/:formIdGet form
POST/reports/dashboard/:id/form/:formIdCreate form
PATCH/reports/dashboard/:id/form/:formIdUpdate form
DELETE/reports/dashboard/:id/form/:formIdDelete form

Dashboard Roles

MethodPathDescription
GET/reports/dashboard/:id/rolesList roles
GET/reports/dashboard/:id/role/:roleNameGet role
POST/reports/dashboard/:id/role/:roleNameCreate role
PATCH/reports/dashboard/:id/role/:roleNameUpdate role
DELETE/reports/dashboard/:id/role/:roleNameDelete role

Configuration

Required environment variables:

VariableDescriptionDefault
METABASE_SITE_URLMetabase instance base URLrequired
METABASE_EMBED_SECRET_KEYSecret key for JWT signingrequired
METABASE_TOKEN_EXPIRY_SECONDSJWT token lifetime3600 (1 hour)
DEPLOY_ENVDetermines which instance environment to use

Design Decisions

  1. Single embed URL endpoint — Instead of separate routes for code vs numeric ID, the GET /reports/dashboard/:code/embed-url endpoint auto-detects the identifier type. Numeric values are treated as direct Metabase IDs; strings are resolved as dashboard codes.

  2. Environment-based instance resolution — Dashboards map to different Metabase dashboard IDs per environment. The DEPLOY_ENV env var determines which instance to use at embed URL generation time.

  3. Role-based access as data — Instead of hardcoding role checks in code, role access is stored in the metabase_dashboard_role table. This allows runtime configuration of which roles can see which dashboards.

  4. Form-path association — Dashboards are associated with UI form paths (e.g. /sales, /inventory) via the metabase_dashboard_form join table. This enables page-level dashboard embedding in the frontend.