@memberjunction/component-registry-server
REST API server for serving MemberJunction interactive components. Provides a standardized API for discovering, searching, and retrieving components and their specifications.
Overview
Section titled “Overview”The Component Registry Server provides a standardized REST API for discovering and retrieving MemberJunction interactive components. It implements the Component Registry API v1 specification, allowing any MemberJunction system to connect and consume components dynamically.
graph TD
A["Component Registry Server<br/>(Express)"] --> B["GET /api/v1/registry"]
A --> C["GET /api/v1/health"]
A --> D["GET /api/v1/components"]
A --> E["GET /api/v1/components/search"]
A --> F["GET /api/v1/components/:ns/:name"]
A --> G["MemberJunction Database"]
G --> H["MJ: Components"]
G --> I["MJ: Component Registries"]
G --> J["MJ: Component Libraries"]
K["Client SDK"] -->|HTTP| A
L["MJ Explorer"] -->|HTTP| A
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
style B fill:#2d8659,stroke:#1a5c3a,color:#fff
style C fill:#2d8659,stroke:#1a5c3a,color:#fff
style D fill:#2d8659,stroke:#1a5c3a,color:#fff
style E fill:#2d8659,stroke:#1a5c3a,color:#fff
style F fill:#2d8659,stroke:#1a5c3a,color:#fff
style G fill:#7c5295,stroke:#563a6b,color:#fff
style K fill:#b8762f,stroke:#8a5722,color:#fff
style L fill:#b8762f,stroke:#8a5722,color:#fff
Features
Section titled “Features”- REST API v1: Standardized endpoints for component discovery and retrieval
- Extensible Architecture: Base class design allows easy customization via inheritance
- Authentication Support: Override methods to implement custom authentication
- Database Integration: Uses MemberJunction’s entity system for component storage
- CORS Support: Configurable cross-origin resource sharing
- Component Versioning: Automatic selection of latest component versions
- Search Capabilities: Full-text search across component names, titles, and descriptions
Installation
Section titled “Installation”npm install @memberjunction/component-registry-serverQuick Start
Section titled “Quick Start”1. Configure the Server
Section titled “1. Configure the Server”Add configuration to your mj.config.cjs file in the project root:
module.exports = { // Database configuration dbHost: 'localhost', dbDatabase: 'MemberJunction', dbUsername: 'your-username', dbPassword: 'your-password', mjCoreSchema: '__mj',
// Component Registry configuration componentRegistrySettings: { port: 3200, // Port to run the server on enableRegistry: true, // Enable the registry server registryId: null, // Optional: GUID of registry record requireAuth: false, // Whether to require authentication corsOrigins: ['*'] // Allowed CORS origins }};2. Run the Server
Section titled “2. Run the Server”# Using npm scriptsnpm start
# Or directly via nodenode dist/index.jsThe server will start on the configured port (default: 3200) and be available at:
http://localhost:3200/api/v1API Endpoints
Section titled “API Endpoints”Registry Information
Section titled “Registry Information”GET /api/v1/registry
{ "name": "Local Component Registry", "description": "MemberJunction Component Registry", "version": "v1", "requiresAuth": false}Health Check
Section titled “Health Check”GET /api/v1/health
{ "status": "healthy", "timestamp": "2024-01-01T00:00:00.000Z", "version": "v1", "componentCount": 42}List Components
Section titled “List Components”GET /api/v1/components
{ "components": [ { "namespace": "@memberjunction/dashboards", "name": "revenue-tracker", "version": "1.0.0", "title": "Revenue Tracker Dashboard", "description": "Track revenue metrics over time", "type": "Dashboard", "status": "Published" } ], "total": 1}Search Components
Section titled “Search Components”GET /api/v1/components/search?q=revenue&type=Dashboard
{ "results": [...], "total": 5, "query": "revenue"}Get Component
Section titled “Get Component”GET /api/v1/components/:namespace/:name
{ "id": "550e8400-e29b-41d4-a716-446655440000", "namespace": "@memberjunction/dashboards", "name": "revenue-tracker", "version": "1.0.0", "specification": { "name": "revenue-tracker", "title": "Revenue Tracker Dashboard", "code": "// Component code here", "props": [...], "dataRequirements": [...] }}Optional version parameter: GET /api/v1/components/:namespace/:name?version=1.0.0
Extending the Server
Section titled “Extending the Server”The Component Registry Server is designed to be easily extended through inheritance. The base ComponentRegistryAPIServer class provides virtual methods that can be overridden to customize behavior.
Custom Authentication
Section titled “Custom Authentication”Create a subclass and override the checkAPIKey method:
import { ComponentRegistryAPIServer } from '@memberjunction/component-registry-server';import { Request } from 'express';
class MyAuthenticatedRegistryServer extends ComponentRegistryAPIServer { /** * Implement custom authentication logic */ protected async checkAPIKey(req: Request): Promise<boolean> { // Check for API key in header const apiKey = req.headers['x-api-key'] as string; if (!apiKey) { return false; }
// Validate against your auth provider return await this.validateWithAuthProvider(apiKey); }
private async validateWithAuthProvider(apiKey: string): Promise<boolean> { // Your custom validation logic here // Could check database, external service, JWT, etc. return apiKey === process.env.VALID_API_KEY; }}
// Start your custom serverconst server = new MyAuthenticatedRegistryServer();await server.initialize();await server.start();Custom Component Filtering
Section titled “Custom Component Filtering”Override the getComponentFilter method to control which components are served:
class FilteredRegistryServer extends ComponentRegistryAPIServer { /** * Only serve components from specific registries */ protected getComponentFilter(): string { // Serve local components and components from a specific registry return "(SourceRegistryID IS NULL OR SourceRegistryID = 'abc-123-def')"; }}Using Different Database Tables
Section titled “Using Different Database Tables”Override the route handlers to pull components from different tables or entities:
class CustomTableRegistryServer extends ComponentRegistryAPIServer { /** * Use a different entity/table for components * For example, using a "Custom Components" entity instead of "MJ: Components" */ protected async listComponents(req: Request, res: Response): Promise<void> { try { const rv = new RunView(); const result = await rv.RunView({ EntityName: 'Custom Components', // Your custom entity name ExtraFilter: "IsActive = 1 AND IsPublished = 1", OrderBy: 'CategoryName, ComponentName, Version DESC', ResultType: 'entity_object' });
if (!result.Success) { res.status(500).json({ error: result.ErrorMessage }); return; }
// Map your custom fields to the API response format const components = (result.Results || []).map(c => ({ namespace: c.CategoryName, // Map your fields name: c.ComponentName, // to the expected version: c.Version, // API format title: c.DisplayName, description: c.Summary, type: c.ComponentType, status: c.PublishStatus }));
res.json({ components, total: components.length }); } catch (error) { res.status(500).json({ error: 'Failed to list components' }); } }
protected async getComponent(req: Request, res: Response): Promise<void> { const { namespace, name } = req.params;
const rv = new RunView(); const result = await rv.RunView({ EntityName: 'Custom Components', ExtraFilter: `CategoryName = '${namespace}' AND ComponentName = '${name}'`, OrderBy: 'Version DESC', MaxRows: 1, ResultType: 'entity_object' });
if (!result.Success || !result.Results?.length) { res.status(404).json({ error: 'Component not found' }); return; }
const component = result.Results[0]; res.json({ id: component.ID, namespace: component.CategoryName, name: component.ComponentName, version: component.Version, specification: JSON.parse(component.ComponentSpec) // Your spec field }); }
// Override search to use your custom table protected async searchComponents(req: Request, res: Response): Promise<void> { const { q, type } = req.query;
let filter = "IsActive = 1 AND IsPublished = 1"; if (q) { filter += ` AND (ComponentName LIKE '%${q}%' OR DisplayName LIKE '%${q}%')`; } if (type) { filter += ` AND ComponentType = '${type}'`; }
// ... rest of implementation }}Custom Routes and Middleware
Section titled “Custom Routes and Middleware”Add additional routes or modify middleware:
class ExtendedRegistryServer extends ComponentRegistryAPIServer { /** * Add custom middleware */ protected setupMiddleware(): void { // Call parent middleware setup super.setupMiddleware();
// Add custom middleware this.app.use('/api/v1/admin', this.adminAuthMiddleware);
// Add request logging this.app.use((req, res, next) => { console.log(`${req.method} ${req.path}`); next(); }); }
/** * Add custom routes */ protected setupRoutes(): void { // Call parent route setup super.setupRoutes();
// Add admin routes this.app.post('/api/v1/admin/components', this.createComponent.bind(this)); this.app.delete('/api/v1/admin/components/:id', this.deleteComponent.bind(this)); }
private async createComponent(req: Request, res: Response) { // Custom component creation logic }
private async deleteComponent(req: Request, res: Response) { // Custom component deletion logic }}Database Provider Customization
Section titled “Database Provider Customization”Override the database setup for different providers:
class PostgresRegistryServer extends ComponentRegistryAPIServer { /** * Use PostgreSQL instead of SQL Server */ protected async setupDatabase(): Promise<void> { // Your PostgreSQL setup logic const pgClient = new PostgreSQLClient(config); await pgClient.connect();
// Setup MemberJunction with PostgreSQL provider await setupPostgreSQLClient(pgClient); }}Advanced Usage
Section titled “Advanced Usage”Programmatic Usage
Section titled “Programmatic Usage”Use the server as a library in your application:
import { ComponentRegistryAPIServer } from '@memberjunction/component-registry-server';
async function startMyApp() { // Create and configure server const registryServer = new ComponentRegistryAPIServer();
// Initialize without starting await registryServer.initialize();
// Access the Express app for integration const app = registryServer.app;
// Add to existing Express app existingApp.use('/registry', app);
// Or start standalone await registryServer.start();}Multiple Registry Support
Section titled “Multiple Registry Support”Run multiple registries on different ports:
class MultiRegistryManager { private registries: Map<string, ComponentRegistryAPIServer> = new Map();
async startRegistry(name: string, port: number, registryId: string) { const server = new ComponentRegistryAPIServer();
// Override configuration componentRegistrySettings.port = port; componentRegistrySettings.registryId = registryId;
await server.initialize(); await server.start();
this.registries.set(name, server); }
async startAll() { await this.startRegistry('public', 3200, 'public-registry-id'); await this.startRegistry('private', 3201, 'private-registry-id'); await this.startRegistry('internal', 3202, 'internal-registry-id'); }}Configuration Reference
Section titled “Configuration Reference”componentRegistrySettings
Section titled “componentRegistrySettings”| Property | Type | Default | Description |
|---|---|---|---|
port | number | 3200 | Port number for the server |
enableRegistry | boolean | false | Whether to enable the registry server |
registryId | string | null | null | Optional GUID of the registry record in MJ: Component Registries |
requireAuth | boolean | false | Whether to require authentication for component endpoints |
corsOrigins | string[] | [’*‘] | Allowed CORS origins |
Database Schema
Section titled “Database Schema”The Component Registry Server uses the following MemberJunction entities:
- MJ: Component Registries: Registry metadata and configuration
- MJ: Components: Component definitions and specifications
- MJ: Component Libraries: External library dependencies
- MJ: Component Dependencies: Component-to-component dependencies
Security Considerations
Section titled “Security Considerations”Authentication
Section titled “Authentication”By default, the server runs without authentication. For production use:
- Set
requireAuth: truein configuration - Extend the server class and override
checkAPIKey() - Implement your authentication strategy (Bearer tokens, API keys, JWT, etc.)
Configure CORS origins appropriately for your environment:
componentRegistrySettings: { corsOrigins: [ 'https://app.example.com', 'https://admin.example.com' ]}SQL Injection
Section titled “SQL Injection”The server uses parameterized queries via MemberJunction’s RunView system. User input is escaped when building filters.
Troubleshooting
Section titled “Troubleshooting”Server Won’t Start
Section titled “Server Won’t Start”- Check database connection settings in
mj.config.cjs - Ensure the database is accessible
- Verify
enableRegistryis set totrue - Check port availability
Components Not Found
Section titled “Components Not Found”- Verify components exist in the database
- Check component
Statusis ‘Published’ - Ensure
SourceRegistryIDis NULL for local components - Review the component filter if using a custom implementation
Authentication Issues
Section titled “Authentication Issues”- Verify
requireAuthsetting matches your expectations - Check your
checkAPIKeyimplementation - Ensure auth headers are being sent correctly
Contributing
Section titled “Contributing”Contributions are welcome! Please follow the MemberJunction contribution guidelines.
License
Section titled “License”ISC License - See LICENSE file for details
Support
Section titled “Support”For issues and questions:
- GitHub Issues: MemberJunction/MJ
- Documentation: docs.memberjunction.org