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
: readsMODULES_ENABLED
andFEATURE_*\n
flags, importssrc/modules/<id>/index
, runscheckDeps()
, callsinstall()
and optionalonBootstrap()
.src/modules/registry.ts
:BasicModuleRegistry
and sharedmoduleRegistry
.src/modules/events.ts
: tiny event bus (NodeEventEmitter
).src/modules/types.ts
: contracts forModule
,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
: readsMODULES_ENABLED
andFEATURE_*\n
fromwindow.__envs__
(fallbackprocess.env
), importsclient/src/modules/<id>/index
, aggregatesroutes
andnavItems
.client/src/modules/types.ts
:FrontendModule
,FrontendModuleNavItem
.src/index.ts
: injectsMODULES_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 inonBootstrap()
. - 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
- Create
src/modules/example/index.ts
andclient/src/modules/example/index.ts
. - Set env:
MODULES_ENABLED=example FEATURE_EXAMPLE=true
- 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.