Modules system
Modules Overview
Non-invasive scaffolding that prepares the codebase for pluggable backend and frontend modules without breaking existing functionality.
Overview
- Backend
ModuleLoaderdiscovers and installs modules based on env flags. - Frontend
FrontendModuleLoaderaggregates routes and navigation from modules. - Lightweight
ModuleRegistryand event bus for cross-module coordination. - Fully backward compatible: existing routes keep working.
Backend
src/modules/loader.ts: readsMODULES_ENABLEDandFEATURE_*\nflags, importssrc/modules/<id>/index, runscheckDeps(), callsinstall()and optionalonBootstrap().src/modules/registry.ts:BasicModuleRegistryand 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_ENABLEDandFEATURE_*\nfromwindow.__envs__(fallbackprocess.env), importsclient/src/modules/<id>/index, aggregatesroutesandnavItems.client/src/modules/types.ts:FrontendModule,FrontendModuleNavItem.src/index.ts: injectsMODULES_ENABLEDfor 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.tscontinues 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.tsandclient/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>=falsewill 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.