Abhinav C

NodeMinus: put the safety check where the delete happens

node_modules is the most disposable large thing on a developer’s disk. It can be rebuilt perfectly from package.json with one command, yet dozens of copies pile up across old branches, experiments and cloned repos, quietly taking tens of gigabytes.

NodeMinus is a menu-bar app for macOS, Windows and Linux that finds every node_modules folder under a directory, sorts them by how much space they take, and deletes the ones you choose. It’s built with Tauri: a web UI on top of a Rust backend.

Deleting folders is easy. Deleting only the right folders, every time, is the actual product.

Where the guardrail lives

Any app that deletes files has a confirmation dialog. NodeMinus has one too: before anything is deleted, it shows the exact paths and how much space they’ll free. That’s good for the user, but it isn’t a safety guarantee. A UI check can be skipped by a bug in the UI, a stale list, or a mistake in how paths are passed to the backend.

So the real check lives in the Rust function that performs the delete:

/// Only directories literally named `node_modules` may ever be deleted,
/// no matter what the frontend sends.
fn is_node_modules_dir(path: &Path) -> bool {
    path.file_name().is_some_and(|name| name == "node_modules")
}

Every path is checked right before deletion. Anything that isn’t a directory named node_modules is refused and reported, whatever the UI asked for. And there’s a test that tries to delete a folder called important-data and asserts that it survives.

The general principle: enforce safety at the point of the destructive action, not at the point of the decision. The same idea shows up in infrastructure as IAM policies that deny deletes, S3 Object Lock, CloudFormation stack policies and Kubernetes admission controllers. The interface can help people make good choices. The boundary has to make bad ones impossible.

Fast enough to feel live

Scanning an entire home directory can mean walking millions of entries. NodeMinus uses a parallel directory walker and skips what can’t contain useful results: .git folders, system directories, and anything inside a node_modules folder it has already found, since nested copies are deleted with their parent anyway.

Results stream into the list as they’re found, sorted by size, instead of appearing all at once when the scan finishes. The biggest wins usually show up within seconds, and the user can start deciding while the scan continues. Deletes report progress per folder, so a bulk purge of 40 GB doesn’t look frozen.

Shipping it like it matters

Each release builds installers for Apple Silicon and Intel Macs, Windows and Linux, and publishes a SHA256SUMS.txt file next to them, so anyone can verify a download before running it. The README is upfront that macOS builds aren’t notarized yet, and explains how to open the app anyway, rather than leaving people to discover a scary warning on their own.

It’s a small utility. But small tools that delete things deserve the same care as large systems that delete things, because the user’s files are just as gone either way.

Filed under: things I built. Downloads on GitHub.

More writing