npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@memberjunction/component-registry-server

v5.7.0

Published

MemberJunction: Component Registry Server API Implementation

Readme

@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

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

  • 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

npm install @memberjunction/component-registry-server

Quick Start

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

# Using npm scripts
npm start

# Or directly via node
node dist/index.js

The server will start on the configured port (default: 3200) and be available at:

http://localhost:3200/api/v1

API Endpoints

Registry Information

GET /api/v1/registry

{
  "name": "Local Component Registry",
  "description": "MemberJunction Component Registry",
  "version": "v1",
  "requiresAuth": false
}

Health Check

GET /api/v1/health

{
  "status": "healthy",
  "timestamp": "2024-01-01T00:00:00.000Z",
  "version": "v1",
  "componentCount": 42
}

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

GET /api/v1/components/search?q=revenue&type=Dashboard

{
  "results": [...],
  "total": 5,
  "query": "revenue"
}

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

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

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 server
const server = new MyAuthenticatedRegistryServer();
await server.initialize();
await server.start();

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

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

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

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

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

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

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

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

Authentication

By default, the server runs without authentication. For production use:

  1. Set requireAuth: true in configuration
  2. Extend the server class and override checkAPIKey()
  3. Implement your authentication strategy (Bearer tokens, API keys, JWT, etc.)

CORS

Configure CORS origins appropriately for your environment:

componentRegistrySettings: {
  corsOrigins: [
    'https://app.example.com',
    'https://admin.example.com'
  ]
}

SQL Injection

The server uses parameterized queries via MemberJunction's RunView system. User input is escaped when building filters.

Troubleshooting

Server Won't Start

  1. Check database connection settings in mj.config.cjs
  2. Ensure the database is accessible
  3. Verify enableRegistry is set to true
  4. Check port availability

Components Not Found

  1. Verify components exist in the database
  2. Check component Status is 'Published'
  3. Ensure SourceRegistryID is NULL for local components
  4. Review the component filter if using a custom implementation

Authentication Issues

  1. Verify requireAuth setting matches your expectations
  2. Check your checkAPIKey implementation
  3. Ensure auth headers are being sent correctly

Contributing

Contributions are welcome! Please follow the MemberJunction contribution guidelines.

License

ISC License - See LICENSE file for details

Support

For issues and questions: