or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication.mdcaching.mdextensions.mdindex.mdplugin-system.mdrequest-processing.mdresponse-management.mdrouting.mdserver-management.mdvalidation.md
tile.json

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';
}