HTTP Server framework for Node.js with built-in authentication, validation, caching, logging, and plugin architecture
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Modular plugin architecture for extending server functionality with dependency management and isolation in @hapi/hapi.
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'
}
});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
}
});
}
};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;
}
});
}
};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
};
}
});
}
};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);
}
});
}
};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');
}
};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);
}
};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);
};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';
}