npm vs npx: When to Install and When to Run
npm installs and manages packages. npx runs package binaries, fetching one when necessary. Confusing persistence with execution creates global clutter; treating either command as a trust boundary can execute code you did not inspect. Advanced TypeScript patterns show where installed compiler and type-checking dependencies fit in a real project.
For JavaScript developers using Node.js projects and CI, this guide compares the two commands through lodash and a scaffold CLI. You will be able to choose install vs run, pin the intended version, and verify what changed on disk. For repeatable automation after the local command works, see CI/CD with GitHub Actions.
npm: install and manage packages
npm (Node Package Manager) is the default package manager for Node.js. It installs packages locally (project node_modules) or globally (-g), records them in package.json / lockfiles, and runs project scripts via npm run.
When to use: anything that should stay in the project — libraries, frameworks, and scripts your teammates need after npm install. When not to: one-shot CLIs you will never import — prefer npx so globals and deps stay clean.
Quick reference
- Installs to
node_modules(or a global prefix with-g). - Tracks versions in
package.jsonand the lockfile. - Common:
npm install,uninstall,update,npm run <script>. - Example:
npm install lodash— lodash becomes a project dependency.
Remember this
A package your teammates need after npm install belongs in npm; a CLI you'll never import just leaves globals and deps cluttered if it goes through npm instead of npx.
npx: run without a permanent install
npx (Node Package eXecute) runs a package binary. If it is not already available, npx downloads it temporarily, executes it, and does not leave it as a normal project dependency. Perfect for generators and try-once CLIs.
When to use: scaffolding (create-next-app, create-react-app), one-off demos (cowsay), or pinning a CLI version (npx package@version). When not to: libraries your app imports at runtime — those belong in npm install.
Quick reference
- Does not require a permanent global install.
- Does not add the tool to your app’s
node_modulesas a dependency by default. - Common:
npx <pkg>,npx create-react-app my-app,npx pkg@version. - Example:
npx cowsay Hello— download, run, done.
Remember this
npx fetches a binary, runs it, and leaves nothing behind in package.json — the right shape for a scaffold or a try-once CLI, the wrong one for a library your code needs to import.
Side-by-side: install vs execute
npm’s job is manage. npx’s job is run. npm says yes to installing and saving under node_modules. npx says no to permanent install for that one-shot — temporary fetch if needed, then execute.
Same ecosystem, different intent: after npm install lodash your code can import it forever; npx lodash is the wrong mental model for a library (lodash is not a typical CLI). Prefer npx for tools with a bin entry you want to invoke once.
Quick reference
- npm → install/manage; npx → execute.
- npm saves to node_modules; npx does not keep one-shot tools as deps.
- Both come together (npm 5.2+); no extra setup for npx.
- Global
-gtools still exist — prefer npx when you can.
Remember this
npx lodash is the wrong mental model for a library — npm persists what your code imports, npx runs a one-shot bin entry and forgets it existed.
Trust boundary: package name, version, and install scripts
Both commands can execute third-party code. The failure chain is concrete: a typo or unpinned @latest resolves an unintended package or major version; npm downloads it; allowed lifecycle scripts such as postinstall may run during installation; then npx launches the package binary with your user or CI permissions. Symptoms range from a changed lockfile to leaked environment credentials or a scaffold that rewrites files.
Prevent this by checking the exact package and publisher, pinning a reviewed version, preferring a locked dev dependency for repeatable team tools, and running unfamiliar generators in a clean directory or container. --ignore-scripts can suppress lifecycle scripts during install, but it does not make the binary safe—the command you requested still executes. Detect damage with git diff, lockfile review, and CI egress/secret controls; recover by rotating exposed credentials and rebuilding from a clean checkout.
Quick reference
- Package spelling and publisher identity are security decisions.
- Pin an exact reviewed version when reproducibility matters.
- Postinstall is one risk; the requested binary itself is executable code.
- Use a locked dev dependency for CI and frequently repeated tools.
- Rotate credentials if an untrusted package ran with secret access.
Remember this
A typo or a floating @latest resolves to the wrong package before a single postinstall script runs with your permissions — pin an exact reviewed version and diff the lockfile after every generator run.
Decision rule and practice
Rule of thumb: npm when you want to install and manage packages; npx when you want to run a package or tool quickly without installing it. Pro tip: scaffolding and experimental CLIs are npx’s sweet spot — your disk and package.json stay cleaner.
Your homework: in an empty folder, run npm init -y, npm install lodash, then npx create-next-app@latest demo-app --yes (or equivalent). Confirm lodash is in package.json and the create tool is not.
Quick reference
- Dependencies and scripts → npm.
- Generators and one-off CLIs → npx.
- CI:
npm cifor installs;npxfor pinned tools without globals. - Prefer lockfiles; avoid random global installs on shared machines.
Remember this
"Keep it" means npm; "just run it once" means npx — and after either, package.json should show only the dependency you actually meant to keep.
Key takeaway
npm manages what the project owns; npx executes a package binary. Persistence differs, but both paths require trust in the resolved package and version.
Practice (15 min): in a new Git directory, run npm init -y, npm install lodash, and one exactly pinned scaffold or read-only CLI. Pass the exercise by showing that lodash appears in package.json and the lockfile, the one-shot CLI does not become a dependency, npm view <package> repository version matches what you intended, and git diff contains no unexplained file or script changes.
Related Articles
Explore this topic