Graceful shutdown signal handler utility for Node.js
- Features
- Requirements
- Installation
- Usage
- API
- TypeScript
- Errors / Troubleshooting
- Development
- Security
- License
- Register handlers for process signals (e.g.,
SIGTERM,SIGINT,SIGHUP) - Register async shutdown hooks to run on signals, explicit shutdown, or
beforeExit - Customizable logger and process object
- Idempotent registration with repeat-safe listener cleanup
- AbortSignal support for lifecycle-managed applications
- Concurrent shutdown protection and per-hook error isolation
- Configurable exit code and optional non-exiting mode
- Simple ESM API
- TypeScript type definitions included
- Well-tested with Jest
- Node.js 26 or newer
- A process-like application lifecycle that can receive shutdown signals
npm install @eliware/signalsimport log from '@eliware/log';
import registerSignals from '@eliware/signals'; // Default export
// or: import { registerSignals } from '@eliware/signals';
const { shutdown, getShuttingDown } = registerSignals({ log });import log from '@eliware/log';
import registerSignals from '@eliware/signals';You can call registerSignals multiple times to add async shutdown hooks. All hooks will be run (in order of registration) when a signal is received or Node emits beforeExit. Repeated registrations must use the same lifecycle options (log, signals, exitCode, exit, and signal); conflicting options throw TypeError.
// Simulate a resource that needs cleanup (e.g., database connection)
const fakeDb = {
close: async () => {
return new Promise(resolve => setTimeout(() => {
log.info('Fake DB connection closed');
resolve();
}, 100));
}
};
// Register signal handlers
registerSignals({ log });
// Add shutdown hook for closing the fake DB connection
registerSignals({
log,
shutdownHook: async (signal) => {
await fakeDb.close();
log.info(`Cleanup complete on ${signal}`);
}
});Registers shutdown handlers for the specified signals and allows registering async shutdown hooks.
processObj(default:process): Process-like object to attach handlers to; must provideon, with optionaloffandexit.log(default:@eliware/log): Logger for output. Must havedebug,warn, anderrormethods; invalid loggers throwTypeError. Custom loggers are responsible for their own error serialization/redaction.signals(default:[ 'SIGTERM', 'SIGINT', 'SIGHUP' ]): Array of signals to listen for.shutdownHook(optional): A sync or async function to run during shutdown. Multiple registrations add hooks in order.exitCode(default:0): Exit code used after signal-driven shutdown.exit(default:true): Set tofalsefor embedded applications and tests that should not callprocess.exit.signal(optional): AnAbortSignalthat removes all registered listeners when aborted.
An object with:
shutdown(signal: string): Promise<void>— Manually trigger shutdown logic.getShuttingDown(): boolean— Returns whether shutdown is in progress.removeHandlers(): void— Detaches registered listeners; safe to call repeatedly.removed: boolean— Indicates whether cleanup has completed.
**Shutdown hooks run on signals, explicit
shutdown(), orbeforeExit. They are intentionally not run from Node’sexitevent because asynchronous cleanup cannot complete reliably there.
Type definitions are included:
import registerSignals, { RegisterSignalsOptions } from '@eliware/signals';
// Optionally provide options
const options: RegisterSignalsOptions = {
processObj: process, // optional, defaults to process
log: myLogger, // optional; must provide debug, warn, and error
signals: ['SIGTERM', 'SIGINT', 'SIGHUP'], // optional, defaults as shown
shutdownHook: async (signal) => { /* ... */ } // optional
};
const { shutdown, getShuttingDown, removeHandlers, removed } = registerSignals(options);
// Types:
// interface RegisterSignalsOptions {
// processObj?: ProcessLike;
// log?: SignalsLogger;
// signals?: NodeSignal[];
// shutdownHook?: (signal: string) => void | Promise<void>;
// exitCode?: number;
// exit?: boolean;
// signal?: AbortSignal;
// }
//
// function registerSignals(options?: RegisterSignalsOptions): SignalsRegistration;Shutdown hooks run in registration order, and a failing hook is logged without preventing later hooks from running. Use exit: false for embedded applications and tests. Prefer explicit shutdown() or beforeExit for asynchronous cleanup. Always call removeHandlers() when a registration is no longer needed.
npm test
npm run test:gaps
npm run lint
npm run typecheck
npm run packExamples are safe to inspect and should be run only in a controlled process when testing signal behavior.
Set LOG_LEVEL=debug to see diagnostic messages from the default logger. Do not log secrets or sensitive shutdown context. Keep cleanup hooks bounded and avoid relying on asynchronous work after the process has entered the exit event.
For help, questions, or to chat with the author and community, visit:


