Plugin architecture for MJServer that enables auto-discovery and lifecycle management of extension modules. Extensions register Express routes, handle their own authentication, and participate in health checks and graceful shutdown — all without modifying MJServer source code.
This package provides two main exports:
BaseServerExtension — Abstract base class that all extensions implement. Defines the Initialize, Shutdown, and HealthCheck lifecycle methods.ServerExtensionLoader — Discovers registered extension classes via MJ's ClassFactory, matches them to config entries, and manages their lifecycle.Extensions are discovered automatically using MemberJunction's standard @RegisterClass + ClassFactory pattern. You register your extension class, add an entry to mj.config.cjs, and MJServer loads it at startup — zero source code changes to MJServer required per new extension.
npm install @memberjunction/server-extensions-core
import { Application } from 'express';
import { RegisterClass } from '@memberjunction/global';
import {
BaseServerExtension,
ServerExtensionConfig,
ExtensionInitResult,
ExtensionHealthResult
} from '@memberjunction/server-extensions-core';
@RegisterClass(BaseServerExtension, 'MyCustomExtension')
export class MyCustomExtension extends BaseServerExtension {
async Initialize(app: Application, config: ServerExtensionConfig): Promise<ExtensionInitResult> {
// Register your Express routes
app.get(config.RootPath + '/hello', (_req, res) => {
res.json({ message: 'Hello from my extension!' });
});
return {
Success: true,
Message: 'Custom extension loaded',
RegisteredRoutes: [`GET ${config.RootPath}/hello`]
};
}
async Shutdown(): Promise<void> {
// Clean up connections, drain requests, release resources
}
async HealthCheck(): Promise<ExtensionHealthResult> {
return { Healthy: true, Name: 'MyCustomExtension' };
}
}
mj.config.cjsmodule.exports = {
// ... other MJServer config ...
serverExtensions: [
{
Enabled: true,
DriverClass: 'MyCustomExtension',
RootPath: '/api/my-extension',
Settings: {
apiKey: process.env.MY_EXTENSION_API_KEY,
// Any extension-specific settings
}
}
]
};
Ensure your extension package is imported in your application so the @RegisterClass decorator fires at module load time. Add it as a dependency in your MJAPI project.
BaseServerExtensionAbstract base class for all server extensions.
| Method | Description |
|---|---|
Initialize(app, config) |
Called once at MJServer startup. Register routes, open connections. |
Shutdown() |
Called during graceful shutdown (SIGTERM/SIGINT). Clean up resources. |
HealthCheck() |
Called periodically. Return health status quickly (< 100ms). |
OnConfigurationChange?(config) |
Optional. Called when config changes at runtime. |
ServerExtensionLoaderManages the extension lifecycle.
| Method | Description |
|---|---|
LoadExtensions(app, configs) |
Discover and initialize all enabled extensions from config. |
HealthCheckAll() |
Run health checks on all loaded extensions. |
ShutdownAll() |
Shut down all extensions in reverse order (LIFO). |
Extensions |
Read-only array of loaded extension instances. |
ExtensionCount |
Number of currently loaded extensions. |
ServerExtensionConfiginterface ServerExtensionConfig {
Enabled: boolean; // Skip loading if false
DriverClass: string; // Must match @RegisterClass key
RootPath: string; // URL prefix for extension routes
Settings: Record<string, unknown>; // Extension-specific config
}
ExtensionInitResultinterface ExtensionInitResult {
Success: boolean;
Message: string;
RegisteredRoutes?: string[];
}
ExtensionHealthResultinterface ExtensionHealthResult {
Healthy: boolean;
Name: string;
Details?: Record<string, unknown>;
}
serverExtensions[] from mj.config.cjsServerExtensionLoader uses ClassFactory.CreateInstance(BaseServerExtension, driverClass) to find the registered classInitialize(app, config)config.RootPathGET /health/extensions for aggregate health checksShutdownAll() calls each extension's Shutdown() in reverse orderExtensions handle their own authentication by default. Common patterns:
@memberjunction/server if you want to reuse MJServer's built-in authThis is opt-in — extensions are not forced to use MJServer's auth middleware.
npm run test # Run all tests
npm run test:watch # Watch mode
@memberjunction/messaging-adapters — Slack and Teams adapters built on this framework@memberjunction/server — MJServer that loads and manages extensions
See