Fix Nodemon Not Restarting with Turso/libSQL

Your Turso database client is keeping your old server alive. Nodemon can't restart it. Here's the surgical fix that actually works.

Why Nodemon Zombies Haunt Your Turso Setup (And How to Kill Them) — theAIcatchup

Key Takeaways

  • Turso's persistent socket connection prevents Node.js from exiting when nodemon tries to restart—the old process becomes a 'zombie' holding your port
  • Capture SIGUSR2 (nodemon restart signal) with process event handlers that explicitly close the database connection before shutdown
  • Graceful shutdown isn't optional developer niceties—it's essential infrastructure for both local development and production reliability

Zombie processes eating your port.

That’s what happens when you pair Turso with nodemon and forget one critical detail. You write new code, save the file, and… nothing. The old route doesn’t update. The console screams EADDRINUSE: address already in use :::3000. Welcome to a very specific, very annoying development trap that catches most developers using libSQL or Turso at least once.

The problem is architectural, not accidental. And once you understand why it happens, the fix is embarrassingly simple.

Why Your Database Client Won’t Let Go

Here’s the thing: @libsql/client opens a persistent socket connection to your Turso database. That connection stays open. It’s a good design choice—it makes queries fast. But Node.js has a rule: if any background process is still running, the entire application refuses to exit.

To make queries fast, the @libsql/client holds a persistent, open socket connection to your Turso database. Because Node.js is designed to never exit as long as there is an active background process (like a database connection), whenever nodemon tries to restart your app, the old process refuses to die.

So when nodemon sends a restart signal, your Express server shuts down fine. But that database socket? Still there. Still listening. The old Node process becomes a ghost—a zombie—hanging onto Port 3000, preventing your fresh code from binding to it.

You can write all the new routes you want. They’ll never load because the dead process won’t budge.

Is This a Turso Problem or a Nodemon Problem?

Neither, really. It’s a signal handling problem. Nodemon sends SIGUSR2 to restart your app. Your app listens for it… but it only half-shuts down. The database connection never gets told to close.

This isn’t unique to Turso, by the way. Any persistent database client—Redis, PostgreSQL with pg-promise, MongoDB drivers—can trap you the same way if you don’t handle graceful shutdown properly. Turso just makes the pattern more visible because the socket stays aggressively open.

The architectural lesson here: persistent background connections need explicit cleanup logic. You can’t assume they’ll just vanish when your server stops listening.

The Fix: Signal Handlers That Actually Work

You need to intercept the shutdown signal and close your database connection before the process exits. Here’s the pattern:

First, grab a reference to your running server instead of just calling app.listen() and walking away.

const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

Then, at the bottom of your entry file, add a shutdown handler:

import { tursoClient } from "./config/turso.js";

const shutdown = async () => {
  console.log("Shutting down gracefully...");
  tursoClient.close();
  server.close(() => {
    process.exit(0);
  });
};

process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
process.once("SIGUSR2", async () => {
  await shutdown();
});

This catches three termination scenarios: Ctrl+C in your terminal (SIGINT), Docker/system shutdown (SIGTERM), and nodemon restarts (SIGUSR2). The database closes first. The server closes second. The process exits third. Nodemon sees the exit and spawns a fresh instance.

Clean. Predictable. No zombies.

Why This Matters Beyond Nodemon

There’s a deeper lesson embedded here. Most developers treat graceful shutdown as optional—something you add when you feel like it. But in a world where databases are separate services, where containerization is standard, where you might swap your database vendor mid-project, signal handling is infrastructure.

If you deploy this app to Docker and kill the container, the same problem occurs. If you run it on a PaaS that sends SIGTERM before tearing down your dyno, same thing. The zombie process now costs you production outages instead of dev-time frustration.

Building the shutdown logic early isn’t a “nice to have.” It’s part of writing resilient Node.js applications.

The Nuclear Option (When Zombies Already Exist)

If you’ve been running your app without this fix, you probably have orphaned processes still gripping your port. Kill them before running nodemon again.

On Windows (PowerShell):

npx kill-port 3000

On Mac/Linux:

killall node

Then start fresh with npm run dev. Every save triggers a clean shutdown and restart now.

What This Reveals About Development Workflow

The fact that this trips up so many developers says something about how we onboard with new tools. When you scaffold a new Turso project, the docs show you how to query the database. They rarely show you the wiring required to make it play nicely with your development environment.

It’s not a documentation failure, exactly. It’s more that developers tend to rush past the “production-ready code” checklist when building locally. Graceful shutdown feels ceremonial until it isn’t.

The developers who get burned once and fix it? They rarely get burned again. The architecture sticks with them. And they write better deployment logic as a result.


🧬 Related Insights

Frequently Asked Questions

Will killing the process harm my database? No. Turso is fully managed. Closing your client connection just tells the database, “I’m done.” It doesn’t corrupt anything. If you had in-flight transactions (which you shouldn’t in a dev environment), they’d rollback naturally.

Do I need to add this to every Express app? Only if you’re using a persistent database client. Simple apps with no background connections don’t need it. But if you’re using any real database—Turso, PostgreSQL, MongoDB, Redis—adding signal handlers is best practice.

Why doesn’t nodemon handle this automatically? Nodemon doesn’t know which processes are “yours” to close. It can’t introspect your database client. The shutdown logic has to live in your application code, not in the tooling layer.

James Kowalski
Written by

Investigative tech reporter focused on AI ethics, regulation, and societal impact.

Frequently asked questions

Will killing the process harm my database?
No. Turso is fully managed. Closing your client connection just tells the database, "I'm done." It doesn't corrupt anything. If you had in-flight transactions (which you shouldn't in a dev environment), they'd rollback naturally.
Do I need to add this to every Express app?
Only if you're using a persistent database client. Simple apps with no background connections don't need it. But if you're using any real database—Turso, PostgreSQL, MongoDB, Redis—adding signal handlers is best practice.
Why doesn't nodemon handle this automatically?
Nodemon doesn't know which processes are "yours" to close. It can't introspect your database client. The shutdown logic has to live in your application code, not in the tooling layer.

Worth sharing?

Get the best AI stories of the week in your inbox — no noise, no spam.

Originally reported by Dev.to

Stay in the loop

The week's most important stories from theAIcatchup, delivered once a week.