Knex.js - node process never exits, how to close it gracefully - but only when all queries are resolved?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When a Node.js script using Knex finishes its queries but the process still does not exit, the usual reason is the open connection pool. Knex keeps database connections alive for reuse, and as long as that pool remains active, the event loop still has work to watch.
Why The Process Stays Alive
Knex is built on top of a pool manager. Even if every query promise has resolved, the pool itself still owns sockets and timers. That means Node sees active resources and does not terminate naturally.
The fix is not process.exit(). The fix is to wait for your query work to finish and then destroy the Knex instance cleanly.
The Basic Pattern
The important line is await knex.destroy(). That closes the pool after the outstanding work is done.
Wait For All Queries Before Destroying
If you fire multiple queries concurrently, make the wait explicit with Promise.all or an equivalent coordination point.
Destroying the pool too early can interrupt in-flight queries, so the shutdown point belongs after the promises you care about have settled.
CLI Script Versus Long-Running Server
In a short-lived migration script, batch job, or one-off CLI program, destroying the Knex instance at the end is correct and necessary.
In a long-running web server, you usually keep Knex alive for the life of the process and destroy it only during shutdown.
That pattern is for application shutdown, not for ending every request.
Avoid process.exit() As Normal Flow Control
process.exit() forces termination immediately and can cut off logs, pending writes, or cleanup steps. If you call it before the pool is destroyed, you are skipping the cleanup rather than solving the underlying issue.
A better pattern is:
- await the real application work
- destroy Knex
- let Node exit naturally, or set
process.exitCodeif needed
Transactions Need The Same Discipline
If you use transactions, make sure the transaction callback has fully completed before shutdown begins.
Only after the transaction promise resolves should the pool be destroyed.
Common Pitfalls
The most common mistake is expecting resolved query promises to close the connection pool automatically. Another is calling knex.destroy() before all concurrent queries or transactions finish. Developers also sometimes add process.exit() because the script hangs, which masks the real problem and can cut off cleanup. In server applications, the opposite mistake appears too: destroying the shared Knex instance after one request and then wondering why later requests fail.
Summary
- A hanging Node process after Knex work usually means the connection pool is still open.
- Use
await knex.destroy()after the queries you care about have resolved. - Coordinate concurrent queries with
Promise.allbefore shutdown. - Use shutdown hooks for long-running servers, not per-request teardown.
- Avoid
process.exit()as a substitute for proper resource cleanup.

