CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-hapi--hapi

HTTP Server framework for Node.js with built-in authentication, validation, caching, logging, and plugin architecture

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

Modular plugin architecture for extending server functionality with dependency management and isolation in @hapi/hapi.

Capabilities

Plugin Registration

Register plugins to extend server functionality with proper dependency management.

/**
 * Register one or more plugins
 * @param plugins - Plugin object(s) or module references
 * @param options - Registration options
 * @returns Promise that resolves when all plugins are registered
 */
register(plugins: PluginObject | PluginObject[] | string | string[], options?: PluginRegisterOptions): Promise<void>;

interface PluginObject {
    /** Plugin name */
    name: string;
    /** Plugin version */
    version?: string;
    /** Plugin registration function */
    register: PluginRegister;
    /** Plugin requirements */
    requirements?: PluginRequirements;
    /** Plugin dependencies */
    dependencies?: string | string[];
    /** Register plugin only once */
    once?: boolean;
    /** Plugin-specific options */
    options?: object;
}

type PluginRegister = (server: Server, options: object) => Promise<void> | void;

interface PluginRequirements {
    /** Required hapi version */
    hapi?: string;
    /** Required Node.js version */
    node?: string;
}

interface PluginRegisterOptions {
    /** Plugin options */
    options?: object;
    /** Registration routes prefix */
    routes?: {
        prefix?: string;
        vhost?: string | string[];
    };
    /** Once registration flag */
    once?: boolean;
}

Usage Examples:

// Register a plugin object
const myPlugin = {
    name: 'my-plugin',
    version: '1.0.0',
    register: async (server, options) => {
        server.route({
            method: 'GET',
            path: '/plugin-route',
            handler: () => ({ plugin: 'my-plugin', options })
        });
    }
};

await server.register(myPlugin, {
    options: { setting: 'value' }
});

// Register multiple plugins
await server.register([
    myPlugin,
    {
        plugin: require('@hapi/inert'),
        options: { etagMethod: 'simple' }
    },
    {
        plugin: require('@hapi/vision'),
        options: {
            engines: { hbs: require('handlebars') },
            path: './templates'
        }
    }
]);

// Register plugin with route prefix
await server.register(myPlugin, {
    routes: {
        prefix: '/api/v1'
    }
});

Plugin Dependencies

Manage plugin dependencies and loading order.

/**
 * Declare plugin dependencies
 * @param dependencies - Plugin name(s) this plugin depends on
 * @param after - Optional callback to run after dependencies are loaded
 */
dependency(dependencies: string | string[], after?: Function): void;

Usage Examples:

const myPlugin = {
    name: 'my-plugin',
    dependencies: ['@hapi/inert', '@hapi/vision'],
    register: async (server, options) => {
        // Declare additional dependencies programmatically
        server.dependency(['@hapi/good', '@hapi/bell'], () => {
            console.log('All logging and authentication plugins loaded');
        });
        
        // Plugin implementation that uses dependencies
        server.route({
            method: 'GET',
            path: '/template',
            handler: {
                view: { template: 'index' } // Uses @hapi/vision
            }
        });
    }
};

Plugin API Exposure

Expose plugin APIs for use by other plugins or the main application.

/**
 * Expose plugin API
 * @param key - API key name or object with multiple APIs
 * @param value - API value (if key is string)
 * @param options - Exposure options
 */
expose(key: string | object, value?: any, options?: ExposeOptions): void;

interface ExposeOptions {
    /** Expose at server level instead of plugin level */
    scope?: 'server' | 'plugin';
}

Usage Examples:

const databasePlugin = {
    name: 'database',
    register: async (server, options) => {
        const db = await connectToDatabase(options.connectionString);
        
        // Expose database API
        server.expose('db', db);
        server.expose('query', (sql, params) => db.query(sql, params));
        
        // Expose multiple APIs at once
        server.expose({
            users: db.collection('users'),
            posts: db.collection('posts'),
            comments: db.collection('comments')
        });
        
        // Server-level exposure (accessible via server.plugins.database)
        server.expose('connection', db.connection, { scope: 'server' });
    }
};

// Using exposed APIs in another plugin or handler
const userPlugin = {
    name: 'users',
    dependencies: ['database'],
    register: (server, options) => {
        server.route({
            method: 'GET',
            path: '/users',
            handler: async (request, h) => {
                // Access exposed database API
                const db = server.plugins.database.db;
                const users = await server.plugins.database.query('SELECT * FROM users');
                return users;
            }
        });
    }
};

Plugin Context and Realms

Understand plugin isolation and context through realms.

interface Realm {
    /** Parent realm */
    parent?: Realm;
    /** Plugin name */
    plugin: string;
    /** Plugin options */
    pluginOptions: object;
    /** Registered plugins in this realm */
    plugins: object;
    /** Realm settings */
    settings: {
        /** Context binding */
        bind?: object;
        /** File settings */
        files: {
            /** Relative path base */
            relativeTo?: string;
        };
    };
}

// Access current realm
server.realm: Realm;

Usage Examples:

const pluginWithRealm = {
    name: 'realm-demo',
    register: (server, options) => {
        console.log('Plugin realm:', server.realm.plugin);
        console.log('Plugin options:', server.realm.pluginOptions);
        
        // Set realm-specific settings
        server.path(__dirname); // Set relative path for this realm
        
        server.bind({ pluginName: 'realm-demo' }); // Bind context
        
        server.route({
            method: 'GET',
            path: '/realm-info',
            handler: function(request, h) {
                // Access bound context
                return {
                    plugin: this.pluginName,
                    realm: request.route.realm.plugin
                };
            }
        });
    }
};

Plugin Event System

Register and listen to custom events within plugins.

/**
 * Register custom event
 * @param event - Event configuration or event name
 */
event(event: EventObject | string): void;

interface EventObject {
    /** Event name */
    name: string;
    /** Event channels */
    channels?: string[];
    /** Event clone function */
    clone?: Function;
    /** Event spread function */
    spread?: boolean;
    /** Event tags */
    tags?: string[];
}

Usage Examples:

const eventPlugin = {
    name: 'events',
    register: (server, options) => {
        // Register custom events
        server.event('user:created');
        server.event('user:updated');
        server.event({
            name: 'order:processed',
            channels: ['email', 'sms'],
            tags: ['order', 'notification']
        });
        
        // Listen to events
        server.events.on('user:created', (user) => {
            console.log('New user created:', user.id);
        });
        
        server.route({
            method: 'POST',
            path: '/users',
            handler: async (request, h) => {
                const user = await createUser(request.payload);
                
                // Emit custom event
                server.events.emit('user:created', user);
                
                return h.response(user).code(201);
            }
        });
    }
};

Plugin Lifecycle Hooks

Handle plugin lifecycle events and cleanup.

Usage Examples:

const lifecyclePlugin = {
    name: 'lifecycle',
    register: async (server, options) => {
        const resources = await initializeResources();
        
        // Store resources for cleanup
        server.expose('resources', resources);
        
        // Handle server stop for cleanup
        server.ext('onPreStop', async () => {
            console.log('Cleaning up plugin resources...');
            await resources.cleanup();
        });
        
        // Plugin-specific initialization
        server.log(['plugin', 'lifecycle'], 'Plugin initialized with resources');
    }
};

Plugin Registration Patterns

Common patterns for plugin registration and organization.

Usage Examples:

// Plugin collection pattern
const registerPlugins = async (server) => {
    // Core plugins
    await server.register([
        { plugin: require('@hapi/inert') },
        { plugin: require('@hapi/vision') },
        { plugin: require('@hapi/bell') }
    ]);
    
    // Application plugins
    await server.register([
        { plugin: require('./plugins/database') },
        { plugin: require('./plugins/auth') },
        { plugin: require('./plugins/api') }
    ], {
        routes: { prefix: '/api' }
    });
    
    // Environment-specific plugins
    if (process.env.NODE_ENV === 'development') {
        await server.register([
            { plugin: require('@hapi/good') },
            { plugin: require('./plugins/dev-tools') }
        ]);
    }
};

// Plugin factory pattern
const createDatabasePlugin = (connectionString) => ({
    name: 'database',
    register: async (server, options) => {
        const db = await connect(connectionString);
        server.expose('db', db);
    }
});

// Conditional plugin registration
const registerConditionalPlugins = async (server, config) => {
    const plugins = [];
    
    if (config.enableAuth) {
        plugins.push({ plugin: require('./plugins/auth') });
    }
    
    if (config.enableCaching) {
        plugins.push({ 
            plugin: require('@hapi/catbox-redis'),
            options: config.redis 
        });
    }
    
    if (plugins.length > 0) {
        await server.register(plugins);
    }
};

Plugin Testing

Patterns for testing plugins in isolation.

Usage Examples:

// Test plugin in isolation
const testPlugin = async () => {
    const server = Hapi.server();
    
    await server.register(myPlugin);
    
    const response = await server.inject({
        method: 'GET',
        url: '/plugin-route'
    });
    
    console.log(response.statusCode); // 200
    console.log(response.result); // Plugin response
};

// Test plugin dependencies
const testWithDependencies = async () => {
    const server = Hapi.server();
    
    // Register dependencies first
    await server.register([
        { plugin: require('@hapi/inert') },
        { plugin: myPlugin }
    ]);
    
    const exposed = server.plugins.myPlugin;
    console.log('Exposed API:', exposed);
};

Types

interface Server {
    /** Plugin registrations tracking */
    registrations: { [pluginName: string]: PluginRegistration };
    /** Exposed plugin APIs */
    plugins: { [pluginName: string]: object };
    /** Current plugin realm */
    realm: Realm;
}

interface PluginRegistration {
    /** Plugin version */
    version: string;
    /** Plugin name */
    name: string;
    /** Registration options */
    options: object;
    /** Plugin attributes */
    attributes: object;
}

interface ExposeOptions {
    /** Exposure scope */
    scope?: 'server' | 'plugin';
}

docs

authentication.md

caching.md

extensions.md

index.md

plugin-system.md

request-processing.md

response-management.md

routing.md

server-management.md

validation.md

tile.json