Modules system

Modules Overview

Non-invasive scaffolding that prepares the codebase for pluggable backend and frontend modules without breaking existing functionality.

Overview

  • Backend ModuleLoader discovers and installs modules based on env flags.
  • Frontend FrontendModuleLoader aggregates routes and navigation from modules.
  • Lightweight ModuleRegistry and event bus for cross-module coordination.
  • Fully backward compatible: existing routes keep working.

Backend

  • src/modules/loader.ts: reads MODULES_ENABLED and FEATURE_*\n flags, imports src/modules/<id>/index, runs checkDeps(), calls install() and optional onBootstrap().
  • src/modules/registry.ts: BasicModuleRegistry and shared moduleRegistry.
  • src/modules/events.ts: tiny event bus (Node EventEmitter).
  • src/modules/types.ts: contracts for Module, ModuleContext, EnvSpec, MigrationSpec.
  • src/index.ts: integrates the loader after DB init and auth seeding.

Module contract (backend)

// src/modules/example/index.ts
export function createModule(config?: any): Module {
  return {
    id: 'example',
    version: '0.0.1',
    enabled: () => true,
    async install(app, ctx) {
      app.get('/api/example/health', (_req, res) => res.json({ ok: true }));
    },
  };
}

Frontend

  • client/src/modules/loader.ts: reads MODULES_ENABLED and FEATURE_*\n from window.__envs__ (fallback process.env), imports client/src/modules/<id>/index, aggregates routes and navItems.
  • client/src/modules/types.ts: FrontendModule, FrontendModuleNavItem.
  • src/index.ts: injects MODULES_ENABLED for the SPA.

Module contract (frontend)

// client/src/modules/example/index.ts
export function createFrontendModule(): FrontendModule {
  return {
    id: 'example',
    routes: [{ path: '/example', element: ExamplePage }],
    navItems: [{ to: '/example', label: 'Example' }],
  };
}

Environment Variables

  • MODULES_ENABLED: CSV of modules to load, e.g., auth,billing,analytics.
  • FEATURE_<MODULE>: Optional boolean to enable/disable a module; truthy: 1,true,on,yes.
  • Existing system vars: AUTH_JWT_SECRET, AUTH_ACCESS_TTL, AUTH_REFRESH_TTL, VITE_API_URL, etc.

For SPA, MODULES_ENABLED is injected into window.__envs__.

Enable Auth module (Phase 1)

Enable the pluggable Auth module without changing endpoints:

MODULES_ENABLED=auth
# Optional; default is enabled if unspecified
FEATURE_AUTH=true

# Existing auth vars
AUTH_JWT_SECRET=...
AUTH_ACCESS_TTL=15m
AUTH_REFRESH_TTL=7d
# Optional owner bootstrap
AUTH_BOOTSTRAP_OWNER_EMAIL=owner@example.com
AUTH_BOOTSTRAP_OWNER_PASSWORD=change-me
AUTH_BOOTSTRAP_OWNER_NAME=Owner
  • If enabled: module mounts /api/auth/* and seeds in onBootstrap().
  • If not enabled: legacy wiring in src/index.ts continues to mount routes and seed on startup.

Directory Structure

src/
  modules/
    types.ts
    events.ts
    registry.ts
    loader.ts
    <module-id>/
      index.ts
client/src/
  modules/
    types.ts
    loader.ts
    <module-id>/
      index.ts

Quickstart

  1. Create src/modules/example/index.ts and client/src/modules/example/index.ts.
  2. Set env:
    MODULES_ENABLED=example
    FEATURE_EXAMPLE=true
  3. Restart the server. The backend installs routes; the frontend discovers UI.

Troubleshooting

  • Missing module code is skipped with a debug message in dev.
  • FEATURE_<MODULE>=false will skip installation even if listed.
  • Check console logs: [modules] (backend) and [frontend-modules] (frontend).

Roadmap

  • Phase 1: extract existing Auth into a module (auth).
  • Add modules like billing, analytics, flags, notify, storage incrementally.