In the last few days I built a project from scratch.
Not a simple one. A project that has everything a real production system has worker threads, cluster, streams, database connections, scheduling mechanisms. All of it. I wanted to understand how production-level systems actually work, not just read about them.
And in those days I learned a lot. But I also crashed my app. A lot.
Every time the app crashed.
one doubt kept coming back to me that what happens to my database connections when the app crashes? What happens to the requests that were still being processed? What happens to the internet connections that were open?
This doubt pushed me to find an answer. And I found an approach called graceful shutdown.
I read about it. And I found some important things I didn't know yesterday. So let me share.
What is Graceful Shutdown
In general graceful shutdown means closing your application without losing data, dropping active requests, or leaving open connections.
Instead of killing the application immediately, we follow a process:
- Stop the app from accepting new requests
- Allow current requests to finish
- Close all database and cache connections
- Exit the process cleanly
That's it. That's the whole idea.
Before Building This Two Signals You Need to Know
To implement graceful shutdown, first you need to understand two signals.
SIGINT — Signal Interrupt
This is the signal triggered when you press CTRL + C. Manual shutdown. You're telling the process yourself stop now.
SIGTERM — Signal Terminate
This is the signal sent by Kubernetes, Docker, or the Linux operating system. It's basically a polite way of asking a process to stop. The system is not killing it forcefully. it's saying, hey, wrap up and exit cleanly.
These signals are important to listen to. If you don't handle them, your app just dies the moment they're triggered. Connections stay open. Requests get dropped. Data can get lost.
How to Build This
First, you need a separate server file. In this file you create a server using app.listen() and you hold the reference of that server in a variable. That reference is important you need it to control the shutdown.
Then you set a process.on() listener for both signals. Whenever either signal is received, you trigger a shutdown function.
Here's the code:
javascriptconst server = app.listen(3000)
app.get("/", (req, res) => {
setTimeout(() => { res.send("Response after 5 seconds Complete") }, 5000);
})
async function shutdownServer(signal) {
console.log(`Received ${signal}. Shutting down gracefully...`);
server.close(() => {
console.log("Server closed. Exiting process.");
process.exit(0);
});
setTimeout(() => {
console.error("Could not close connections in time. Forcefully shutting down.");
process.exit(1);
}, 10000);
}
process.on("SIGINT", () => shutdownServer("SIGINT"));
process.on("SIGTERM", () => shutdownServer("SIGTERM"));
One Thing Most People Get Wrong About server.close()
A lot of people think server.close() stops the server immediately. That's not true.
What this method actually does it stops accepting new requests first.
Then it waits for all the pending requests to finish processing. Then it closes all the database and cache connections. And then it exits.
That's the whole point. You're not killing everything at once. You're letting the app finish what it started.
But there is a catch. Sometimes connections don't close properly. Maybe a database connection is hanging, maybe something is stuck. If you only rely on server.close(), your app could wait forever.
That's why there's a backup a setTimeout. If the connections are not closing within a certain time, it forces the process to exit. It's a safety net. If everything closes properly, process.exit(0) runs first and the timeout never fires. If something is stuck, the timeout catches it and forces a clean exit with process.exit(1).
Both cases handled.
If I got something wrong or anything can be improved — please drop it in the comments. I'm still learning and I want to get this right.



Top comments (0)