New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

hookupjs

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hookupjs

Parallel bootstrapping for modular Node.js apps with customizable loaders and zero dependencies.

latest
Source
npmnpm
Version
1.4.1
Version published
Maintainers
1
Created
Source

hookupjs

In larger projects, it's common to have multiple components that take varying amounts of time to start. It makes sense to start these components in parallel to ensure a fast and efficient application startup.

This project aims to make this as simple and fast as possible, while also offering some handy features that can be enabled—such as importing files without extensions or entire directories (via index.js lookup).

These features, along with importing other file types, are enabled using the new module.registerHook functionality in Node.js.

Zero dependencies and highly configurable.

import boot from 'hookupjs';

boot('./boot');

Table of Contents

Installation
Usage
App
Examples

Installation

npm i hookupjs

Usage

function boot(specifier: string, options?: BootOptions): Promise<App>

function boot(specifier: string, parentURL?: string, options?: BootOptions): Promise<App>

function boot(sequences: Sequences, options?: BootOptions): Promise<App>

specifier and parentURL

OptionTypeDescription
specifierstringPath to the boot module that exports the Sequences. The path is resolved relative to parentURL if specified or the current file if not.
parentURL?stringBase url used to import the specifier. Must be a file URL string. Optional due to auto-detection. Startup is faster if this is specified. Example: import.meta.url.
boot('./boot/sequences.js', import.meta.url);

Sequences

OptionTypeDescription
[key]FunctionExecuted in parallel with other boot sequences. The return value is registered under the name (key) in the resulting App object returned by boot. App is also the first parameter passed to the function.
// boot/sequences.js
export { default as api } from './api.js';
export { default as db } from './db.js';
export { default as session } from './session.js';

// Exports something like this:
// { api: [AsyncFunction], db: [AsyncFunction], session: [AsyncFunction] }
// boot/db.js
import DatabaseClient from 'database';

export default async function db() {
	const client = new DatabaseClient();
	await client.connect();

	return client.getDb();
}
// boot/api.js
import http from 'node:http';

export default async function api() {
	const server = http.createServer((req, res) => {
		// Handle HTTP API calls
	});

	server.listen(3000);

	return server;
}
// boot/session.js
export default async function session(app) {
	const db = await app.db;

	const session = await db.getSession();
	return session;
}

BootOptions

OptionTypeDescription
baseAppAn optional base object to which each boot sequence will be attached.
globalbooleanIf true, the App will be attached to the global scope (global.app).
registerLoadersLoaderOptionsCustom loaders that enables different import types.
resolveResolveOptionsOptions to customize how modules/resources are resolved.
importsimportsCustom import map to resolve modules/resources.
beforeLoading(app) => voidHook called before loading the sequences.
imported(sequences) => sequences | voidHook called after the sequences got imported. Return value overrides the sequences if something other then undefined is returned
wrapper(run) => app | voidIf you need to wrap the sequences run logic you can use this wrapper hook. The return value of this wrapper function is also the return value of boot
initialized(app) => voidHook called after the sequences have initialized.
booted(app) => voidHook called after the app is fully booted.
onError(key, error) => voidHook called if any error occurs during boot.

LoaderOptions

// Import type json works by default
import pkg from './package.json' with { type: 'json' };

// Enable other types for import
import emailTemplate from './resources/email-template.html' with { type: 'text' };
import logo from './resources/logo.png' with { type: 'buffer' };
OptionTypeDescription
texttrue FunctionEnables import with type text. Use a function for custom implementation.
buffertrue FunctionEnables import with type buffer. Use a function for custom implementation (source is not available).
[key]FunctionUse custom functions to enable import with custom types.

Function definition: (source: Buffer, url: string) => string

// ...
const registerLoaders = {
	text: true,
	yaml: (source) => `export default ${yaml.parse(source.toString())}`,
	json5: (source) => `export default ${JSON5.parse(source.toString())}`,
	properties: (source) => `export default ${properties.parse(source.toString())}`,
	toml: (source) => `export default ${toml.parse(source.toString())}`,
	xml: (source) => `export default ${xml2js(source.toString())}`,
	// ...
};
// ...
Register a Custom Typescript Loader

By default Node.js can execute TypeScript files that contains only erasable TypeScript syntax. But if you want to use a third-party package to compile / transpile / transform Typescript you can use the typescript key to add custom logic. Check out the example 5 in the examples folder. The example shows how to use SWC to transform Typescript to Javascript while importing it.

ResolveOptions

// Import directories (auto-resolve index file)
import { SpecialService } from './services';

// Import modules without extensions (auto-resolve extensions)
import model from './model';
import controller from './controller';

// Enable search string forwarding (logger module can read the query params via import.meta.url)
import childLogger from 'logger?child=db';
OptionTypeDescription
directoriestrueEnables importing directories (specifically, imports the index file from the directory).
extensionstrue string[]Enables importing without extensions. Provide an array of strings defining which extensions to auto-complete.
handleSearchtrueEnables forwarding of the search string (or query string). Can be accessed via import.meta.url in the imported module.
// ...
const resolve = {
	directories: true,
	extensions: ['.js', '.mjs', '.cjs'],
	handleSearch: true,
}
// ...

imports

Define a map that applies to import specifiers.

imports = {
	'logger': 'some-logger-package', // Direct replacement
	'logger': './src/logger', // Relative paths getting resolved relative to `parentURL` if specified or the current file if not
	'logger': import.meta.resolve('./src/logger'), // Absolute paths needs to be file urls (file://)
	'~/utils/*': './src/utils/*', // A wildcard (*) can be used to match any number of characters and forward them to the replacement
	'#/*': './src/*', // If a wildcard (*) is used, both sides needs the wildcard. Only one wildcard is allowed
	'#/boot': './src/start.js', // '#/boot' will never getting matched. The wildcard in '#/*' above consumes 'boot'. Needs to be defined before any wildcard
	'@services/*': './src/services/*.service.js', // Resolves '@services/email' to 'file://path/to/src/services/email.service.js'
}

App

The returned promises by the sequence functions are registered under there respective names into the App. Use await on these promises where you need the actual value. There are two ways to access the App everywhere in the project.

import app from 'hookupjs/app';
// or
global.app // if global is set to true in the options

// This function getting called by an incoming http request
export async function uploadImage(userId, imageBuffer) {
	// Image upload logic here
	// ...

	const db = await app.db;
	db.save('image', { uuid, userId, imageUrl });

	const sockets = await app.sockets;
	sockets.sendMessage(userId, 'image uploaded');
}

// or e. g. services/db.js
export async function query(query, params) {
	const db = await app.db;
	const result = await db.query(query, params);
	logger.verbose(query, params, result);
	return result;
}

// or e. g. services/queue.js
export async function handleMessage(uuid, payload) {
	// handle logic here
	// ...

	const queue = await app.queue;
	queue.ack(uuid, result);
}

Examples

Check the examples folder for more ideas.

// main.js
import { EventEmitter } from 'node:events';
import logger from 'logger';
import boot from 'hookupjs';

const app = await boot('./boot', {
	base: new EventEmitter(),
	global: true, // global.app = base
	registerLoaders: {
		text: true,
	},
	resolve: {
		directories: true,
	},
	initialized: app => app.emit('app.initialized'),
	booted: (app) => {
		app.emit('app.boot.complete');
	},
	onError: (key, error) => {
		logger.fatal(`Error during boot sequence "${key}"!`, error);
	},
});

// global.app === app && app instanceof EventEmitter
app.emit('app.boot.complete.too');
// boot.js
export async function queue(app) {
	// global.app === app
	const db = await app.db;
	const queueRaw = db.getAll('queue');
	const queue = await QueueService.init(queueRaw);

	return queue;
}

export async function db() {
	const connection = await DatabaseClient.connect();
	const db = connection.getDb();

	return db;
}

Keywords

boot

FAQs

Package last updated on 19 Jan 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts