Library / SDK
sindresorhus/nano-spawn avatar
sindresorhus/nano-spawn

nano-spawn is a promise-shaped wrapper around child_process

Tiny process execution for humans — a better child_process

592 stars16 forksJavaScriptMIT

At a glance

What is it?
nano-spawn is a dependency-free JavaScript package that turns Node's child_process into a promise you can await, iterate line by line, or pipe into another command, with no dependencies and a tiny install size. It sits deliberately below Execa, and the README says so, which tells you how to choose between them.
Who is it for?
Adopt nano-spawn if you want the child_process API with promises and line iteration and nothing else, since it has no dependencies and keeps the familiar spawn signature. Skip it if you need the larger surface, because the README itself directs you to Execa for additional features, and that is the better choice for a script that has to manage timeouts, cancellation or complex pipelines.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 35 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The same spawn signature, with a promise attached

nano-spawn is described as tiny process execution for humans and a better child_process, and the design goal is that the transition costs you almost nothing. The default export is spawn(file, arguments, options), and the documentation says explicitly that this has the same syntax as child_process.spawn(). All the child_process options can be passed through, including stdio, shell, timeout, signal, cwd, killSignal, serialization, detached, uid, gid, windowsVerbatimArguments, windowsHide and argv0. What changes is the return value. Instead of a child process object with event listeners, you get a Subprocess, and awaiting it either resolves with a result object or throws a SubprocessError. The package is MIT licensed, has no dependencies, and the README advertises a small package size with a bundle size badge and an install size badge. The npm keywords list is telling about intent, since it includes spawn, exec, child, process, subprocess, execute, fork, execfile, file, shell, bin, binaries, npm, path, local, zx and execa, which places it in the same family as the tools people reach for when they want to run a command from JavaScript without ceremony.

Three ways to consume the output

The output handling is the part that goes beyond a plain promise wrapper, and there are three distinct ways to read a subprocess. The first is awaiting the subprocess, which gives you the result. The second is iterating over stdout as an async iterable, one line at a time, as soon as each line is available, which is the shape you want for a long running command that prints progress. The iteration waits for the subprocess to end even when you break out of the loop or return from the function, and it throws if the subprocess fails, so you do not need to await the subprocess separately. There is a matching subprocess.stderr for the error stream, and the subprocess itself is an async iterator over both streams combined, which is how you get interleaved output in the order the child actually produced it rather than in two separate blocks. The result object also carries the stdout with unnecessary newlines stripped, and the duration of the command in milliseconds. In practice that means a build script can stream its output into a logger as it happens and still end up with a single result object, and the README example is only three lines.

js
for await (const line of spawn('ls', ['--oneline'])) {
	console.log(line);
}

Piping commands in JavaScript rather than in a shell

The pipe method mirrors the shell's | symbol without spawning a shell, and it resolves with the second subprocess's result. The example in the README chains three commands.

js
const result = await spawn('npm', ['run', 'build'])
	.pipe('sort')
	.pipe('head', ['-n', '2']);

That is a real difference from writing the same thing as a string, because each stage is a separate process with its own arguments array, and there is no shell to interpret the string. The intermediate result of a piped stage can be retrieved, which is the other half of the feature and the reason to prefer this over a shell string. The documentation notes that if either process in the chain fails, the whole pipeline fails, and the error surfaces as a SubprocessError. Whether the failure of a downstream stage should stop an upstream producer is a design question the package answers by throwing, and that answer is worth knowing before you rely on the pipeline for a long job.

Local binaries, Windows handling and error messages

Three smaller features are called out in the README as the ones that matter in practice. The first is preferLocal, an option that allows executing binaries installed locally with npm or yarn, and its default is false, so by default the command is looked up the way the operating system looks it up. Turning it on is how you run the project's own local toolchain, and it is the option that changes which binary actually runs. The second is improved Windows support, which is a general statement rather than a mechanism, so the details live in the code and in the tests. The third is proper handling of subprocess failures with better error messages, which matters because the failure mode of a process wrapper is usually the failure mode of the thing it runs, and a wrapper that reports a bare exit code sends you back to the shell to find out what happened. The package also lets you pass strings as stdin input, and if the file is node, the current Node.js version and flags are inherited, so a subprocess that runs node gets the same runtime as the parent.

Environment variables and stdio without the ceremony

The options are shaped so that the common cases read cleanly. options.env takes an object and overrides specific environment variables, with the rest inherited from the current process, which is the behaviour you want in almost every case, since a subprocess that loses PATH or loses the environment of a CI runner fails in ways that are hard to read. options.stdin, options.stdout and options.stderr accept a string, a number, a Stream or an object of the form {string: '...'}, and the README lists the common values: pipe as the default, which returns the output through result.stdout, result.stderr and result.output, inherit to use the current process's input and output, which is what you want in a terminal, ignore to discard, a Stream to redirect to or from, and the {string: '...'} form to pass a string as input. A stream can be used once its open event has been emitted, which is a detail worth knowing because fs.createReadStream and fs.createWriteStream are the obvious choice here.

Where nano-spawn stops and Execa starts

The README is explicit about the boundary, and it is the most useful sentence in it for a reader deciding between packages: for additional features, check out Execa. That is the whole recommendation. nano-spawn is deliberately small, and the features it drops are the features Execa covers, which means the two are not competitors so much as two points on the same curve. The trade-off is size against capability, and the size is genuinely small, with no dependencies at all and a promise-based interface, iteration over output lines, piping, local binary execution, improved Windows support, proper failure handling, interleaved output, newline stripping, string stdin, inherited Node flags, simpler environment and stdio options, and the command duration. What it does not attempt is the wider process management surface. A team that needs fine-grained timeout and signal handling, or a richer result object, or advanced piping, is being pointed at the bigger package. The package metadata shows the toolchain this is maintained with, an ESM-only module with sideEffects false, an engines requirement of node >=20.17, and a test script that runs xo for linting, c8 with ava for coverage, and tsd for the type tests.

Editorial conclusion

Adopt nano-spawn if you want the child_process API with promises and line iteration and nothing else, since it has no dependencies and keeps the familiar spawn signature. Skip it if you need the larger surface, because the README itself directs you to Execa for additional features, and that is the better choice for a script that has to manage timeouts, cancellation or complex pipelines. Before you install, check that your Node version is 20.17 or newer, since that is the engines requirement, and decide whether you need the local binary resolution, because preferLocal is off by default and that is the option that changes how a command is found on your machine.

Frequently asked questions

How do I install and use nano-spawn?

Install it with npm install nano-spawn, then import the default export and await it. The API mirrors child_process.spawn(), so the result object and the options are familiar, but awaiting the subprocess resolves with a result or throws a SubprocessError.

How do I stream output lines from a nano-spawn subprocess?

Iterate the subprocess directly with for await, which yields each stdout line as soon as it is available. The iteration waits for the subprocess to end even if you break out, and it throws if the subprocess fails, so you do not need to await the subprocess separately. The subprocess is also an async iterator over both stdout and stderr for interleaved output.

What is the preferLocal option for in nano-spawn?

It allows executing binaries installed locally with npm or yarn rather than whatever the system finds, and it defaults to false. The package requires Node.js 20.17 or newer and has no runtime dependencies.

When should I use Execa instead of nano-spawn?

The README says to check out Execa for additional features, which makes nano-spawn the deliberately smaller option with a promise interface, line iteration, piping, local binary execution, improved Windows support and proper failure handling. The two are not alternatives in the sense of one being wrong, they are points on the same size against capability curve.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. sindresorhus/nano-spawn on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sindresorhus-nano-spawn.svg)](https://hysenlabs.com/projects/sindresorhus-nano-spawn)

Was this useful?