Skip to content

Why lastcall? ​

The problem ​

Production Node.js apps receive SIGTERM when:

  • Kubernetes deletes a pod
  • Docker stops a container
  • PM2 reloads a worker
  • You press Ctrl+C locally

Without graceful shutdown:

  • In-flight HTTP requests are dropped
  • Database connections leak
  • Queue consumers stop mid-job
  • Exit codes are wrong for orchestrators

Why not custom signal handlers? ​

Every team writes the same buggy code:

ts
process.on('SIGTERM', async () => {
  server.close();
  await db.end();
  process.exit(0);
});

Problems:

  • Not idempotent (double SIGTERM breaks things)
  • No ordering between cleanups
  • No global timeout (hangs forever)
  • Hard to test
  • No metrics

Why lastcall? ​

FeatureCustom codelastcall
Idempotent shutdownManualBuilt-in
Handler orderingManualPriority + deps
Phases (drain/cleanup)ManualBuilt-in
HTTP drainManualwithHttpServer()
Per-handler timeoutRarelytimeoutMs
Global timeoutRarelyshutdownTimeoutMs
Test utilitiesNoneautoExit: false, simulateSignal()
Zero dependencies✓✓

The name ​

lastcall = "last call" at the bar. Finish your drinks (in-flight requests), then we close.

Short, memorable, and npm-available.

Alternatives considered ​

  • lifeline — taken on npm (unshiftio)
  • graceful-shutdown-* — crowded namespace
  • prestop — too Kubernetes-specific

Ecosystem comparison ​

LibraryPhasesDepsHTTP drainZero depsTest helpers
lastcall✓✓✓✓simulateSignal, autoExit: false
close-with-grace——partial✓delay option
@godaddy/terminus——✓✗health hooks

lastcall targets teams that want phased shutdown, dependency ordering, and first-class testability without pulling in framework-specific glue.