Tutorials Ultimate Web Development Series Chapter 22

What Is npm and npx? Packages, the Registry, package.json, and Running One-Off Tools

WebChapter 22 of the Ultimate Web Development Series26 minMay 28, 2026Beginner

Open any tutorial, any GitHub repo, any Stack Overflow answer about JavaScript and within three lines you hit a command that starts with npm or npx. npm install. npm run dev. npx create-next-app. npx prettier --write .. They're the first thing you type in a new project and the thing you type a hundred times a day after that — and almost nobody sits a beginner down and explains what they actually are.

This chapter does, from zero. By the end you'll know what a package is, what the registry is, what package.json, node_modules, and package-lock.json each do, the difference between dependencies and devDependencies, how the ^ and ~ in a version number change what gets installed, when to install something locally vs globally, what npx is and why it was invented, and exactly when to reach for each. Ch 20 already covered npm run <script> — this chapter is the layer underneath it: the tool itself.

The Problem npm Solves

Before package managers, sharing code in any language meant one of two miserable workflows:

  1. Copy-paste. Find a library on someone's website, download a .zip, drop the files into your project, and pray it doesn't depend on three other libraries you also have to hunt down by hand.
  2. Manual dependency tracking. When that library updated to fix a security bug, you had to notice, re-download, and re-drop the files. Multiply by 40 libraries.

A package manager automates all of it. You declare what you want — "I need React, version 19" — and the tool figures out where to get it, what else it needs, and how to keep it up to date. You never touch a download link again.

npm is that tool for JavaScript. It ships built into Node.js — install Node, and you already have npm (and npx) on your machine. Check it:

node --version   # v22.x.x
npm --version    # 10.x.x
npx --version    # 10.x.x  (same version as npm — npx ships with it)

Two Things Called "npm"

The word "npm" actually refers to two different things, and keeping them straight makes everything click:

"npm" the…Is…You interact with it via…
CLI toolThe npm command on your machineTyping npm install, npm run dev in a terminal
registryA giant online warehouse of packages at registry.npmjs.orgThe CLI downloads from it; you browse it at npmjs.com

The registry is a public database of more than two million packages — React, Express, the TypeScript compiler, ESLint, tiny one-function utilities, everything. Anyone can publish to it for free. When you run npm install react, the CLI asks the registry "give me react," downloads the files, and drops them into your project.

Loading diagram…

Figure 1 — The npm CLI is a middleman between you and the registry. You ask for a package by name; the CLI fetches it (and everything it depends on) from the registry and writes it into your project's node_modules folder.

What Is a "Package"?

A package is just a folder of code that someone bundled up, gave a name and a version number, and published to the registry so others can reuse it. That's it.

Every package has its own package.json at its root declaring its name, version, and what it depends on. When you install one package, npm reads its dependencies, installs those too, reads their dependencies, and so on — the whole dependency tree, automatically. This is the superpower: you ask for one thing and get the entire transitive graph it needs to run.

Packages are also called modules or libraries — in everyday speech they're interchangeable. "Install the date-fns package," "import the date-fns library," "add the date-fns module" all mean the same action.

The Three Files (and One Folder) That Run the Show

Every npm project has the same furniture. Learn these four names and you can read any JavaScript repo.

package.json — the manifest you write

The human-edited control file at the root of your project. It declares your project's name, its scripts (see Ch 20), and — crucially — the packages it depends on:

{
  "name": "my-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "next dev",
    "build": "next build"
  },
  "dependencies": {
    "next": "^15.0.0",
    "react": "^19.0.0"
  },
  "devDependencies": {
    "typescript": "^5.4.0",
    "eslint": "^9.0.0"
  }
}

You rarely hand-edit the dependencies block — npm writes it for you when you run npm install <something>. But you read it constantly: it's the at-a-glance answer to "what does this project use?"

node_modules/ — the folder npm fills

When you run npm install, npm downloads every package in your dependency tree and dumps them into a node_modules/ folder. This folder can get enormous — thousands of nested folders, hundreds of megabytes — because it contains not just your direct dependencies but their dependencies, all the way down.

Two rules every developer internalizes early:

package-lock.json — the exact snapshot npm writes

Here's a subtlety that trips people up. package.json says "react": "^19.0.0" — the ^ means "19.0.0 or any compatible newer version" (more on that below). So if you install today and a teammate installs next month, you might get 19.0.1 and they get 19.0.4. Subtle version drift between machines is exactly how "works on my machine" bugs are born.

The lockfile (package-lock.json) fixes this. It records the exact version of every package — direct and transitive — that was actually installed, down to the cryptographic hash. Commit it to git, and everyone who runs npm install gets a byte-for-byte identical node_modules.

Loading diagram…

Figure 2 — package.json says what range you'll accept; package-lock.json records the exact versions you got. Commit both. The lockfile is what makes installs reproducible across machines and CI.

This is why CI uses npm ci ("clean install") instead of npm install: npm ci installs exactly what the lockfile says, fails if package.json and the lockfile disagree, and never silently updates anything. Reproducible builds, every time.

Installing Packages

The core verb. Three flavors cover almost everything.

Add a runtime dependency

npm install react
# or the shorthand everyone uses:
npm i react

This downloads react, writes it into node_modules/, adds "react": "^19.x.x" to dependencies in package.json, and updates the lockfile — all in one command.

Add a development-only dependency

npm install --save-dev typescript
# shorthand:
npm i -D typescript

The -D (--save-dev) flag puts it in devDependencies instead. The distinction matters:

dependenciesdevDependencies
For things needed…at runtimeonly to build/test/lint
Examplesreact, next, express, zodtypescript, eslint, vitest, prettier
Shipped to production?YesNo (npm ci --omit=dev skips them)

Rule of thumb: if your shipped code imports it, it's a dependency. If only your tooling touches it, it's a devDependency. Getting this wrong isn't catastrophic — it mostly affects how lean your production install is — but it's a signal of a tidy project.

Install everything an existing project declares

You just cloned a repo. There's a package.json but no node_modules/ (because it's gitignored). One command rebuilds it:

npm install     # reads package.json + lockfile, fills node_modules/

This is the first thing you run after cloning any JavaScript project. No arguments — it installs everything the project declares.

Semver — Reading ^19.0.0 and ~5.4.0

Those symbols in front of version numbers aren't decoration. JavaScript uses semantic versioning (semver): every version is MAJOR.MINOR.PATCH.

PartBumped when…Example
MAJORA breaking change — old code may stop working19.0.0 → 20.0.0
MINORA new feature, backward-compatible19.0.0 → 19.1.0
PATCHA bug fix, no new features19.0.0 → 19.0.1

The prefix on a version in package.json says how much drift you'll accept when npm resolves the range:

RangeMeansWill installWon't install
^19.0.0Caret: minor + patch updates OK19.4.220.0.0
~19.0.0Tilde: patch updates only19.0.919.1.0
19.0.0Exact: this version, nothing else19.0.019.0.1
*Anything (don't do this)anything

The caret (^) is npm's default and the right choice 95% of the time: you automatically get bug fixes and new features, but never an unannounced breaking change. The lockfile still pins the exact version you got, so the range only matters when you deliberately run npm update or add the package fresh.

Local vs Global Installs

By default npm install is local — the package lands in this project's node_modules/ and is invisible to other projects. That's almost always what you want: each project pins its own versions, and nothing leaks between them.

There's also a global install (-g), which puts a package in a system-wide location available from any directory:

npm install -g some-cli      # available everywhere, one shared version

For years people installed CLI tools globally (npm i -g create-react-app) and immediately hit two problems:

  1. Version skew. Your global create-react-app is six months stale; the tutorial assumes the latest. Now you're debugging a version mismatch before you've written a line of code.
  2. Project drift. Two projects need two versions of the same tool. A single global install can't serve both.

This pain is exactly the problem npx was invented to solve. Which brings us to the second half of this chapter.

What Is npx?

npx runs a package's command-line tool without installing it permanently. It ships alongside npm (since npm 5.2, back in 2017), so if you have npm you already have npx.

When you run npx <tool>, npx:

  1. Looks in your project's node_modules/.bin/ — if the tool is already installed locally, it runs that copy.
  2. If it's not there, npx downloads it to a temporary cache, runs it, and cleans up — no permanent install, no package.json change, no global pollution.
npx create-next-app@latest my-app

That single line fetches the latest create-next-app on the fly, runs it to scaffold a new project, and leaves nothing behind globally. The next time you scaffold an app, npx fetches the latest again — you can never be on a stale version of a scaffolding tool, because you never "installed" one.

Loading diagram…

Figure 3 — npx's decision: use the project-local tool if it's installed, otherwise fetch-run-discard a temporary copy. Either way you didn't have to install anything globally.

npm vs npx — The One-Sentence Distinction

This is the line worth memorizing:

npm installs packages. npx runs them.

npmnpx
JobInstall packages into a projectExecute a package's command
Leaves files behind?Yes — writes to node_modules + package.jsonNo — temporary, unless already installed locally
Typical usenpm install reactnpx create-next-app
For libraries your code imports?Yes — this is what npm is forNo — npx is for command-line tools, not imported code

A clean way to feel the difference: you npm install react because your code does import React from "react" — it's a library your app depends on at runtime. You npx create-next-app because it's a one-time command-line tool you run once to generate a project and never need again.

Real Applications — What People Actually Use Each For

The point of all this. Here's where each tool earns its keep day to day.

Applications of npm

Applications of npx

The Cheat Sheet

Pin this. It's 90% of what you'll ever type.

# --- npm: installing ---
npm install               # install everything in package.json (after cloning)
npm install react         # add a runtime dependency
npm i react               # same, shorthand
npm i -D typescript       # add a dev-only dependency
npm uninstall lodash      # remove a package
npm update                # update within the ranges in package.json
npm outdated              # show which packages are behind
npm audit                 # report known security vulnerabilities
npm ci                    # clean, exact install from the lockfile (used in CI)
 
# --- npm: running your project's scripts (Ch 20) ---
npm run dev               # start the dev server
npm run build             # production build
npm test                  # run tests (the one script you can drop "run" for)
 
# --- npx: running tools ---
npx create-next-app@latest my-app   # scaffold a new project
npx prettier --write .              # run a tool once, no install
npx tsc@5.4.0 --version             # run a specific version
npx eslint .                        # run the project's local copy by hand

A Note on the Alternatives — yarn, pnpm, bun

npm is the default, but you'll meet three rivals. They all read the same package.json and talk to the same registry — they differ mostly in speed and disk usage:

ToolInstall commandRun-a-tool equivalentPitch
npmnpm installnpxThe default. Ships with Node. Nothing to set up.
pnpmpnpm installpnpm dlxFast, disk-efficient (shares one copy of each package across all projects). Popular in monorepos.
yarnyarnyarn dlxThe original "faster npm." Still common in older codebases.
bunbun installbunxNewest; a whole JS runtime with a very fast built-in installer.

Mental Model — Three Sentences

  1. npm is two things: a CLI on your machine and a registry of 2M+ packages in the cloud; npm install is the CLI fetching packages from the registry into your project's node_modules.
  2. package.json declares the ranges you'll accept, package-lock.json pins the exact versions you got, and node_modules holds the files — you commit the first two, gitignore the third, and rebuild it any time with npm install.
  3. npm installs, npx runs — reach for npm install when code needs to live in your project, and npx when you just need to run a command once (scaffold an app, format a file, try a version).

Try It Yourself (10 Minutes)

  1. In an empty folder, run npm init -y. Open the generated package.json — that's the manifest, born empty.
  2. Run npm install date-fns. Watch node_modules/ appear, a package-lock.json get written, and a dependencies entry show up in package.json. Open all three and connect what you see.
  3. Run npm i -D prettier. Note it lands in devDependencies, not dependencies.
  4. Now run the tool without a global install: npx prettier --version. Since you just installed it locally, npx runs that copy.
  5. Delete node_modules/ entirely (rm -rf node_modules). Run npm install again — it rebuilds the whole folder from the lockfile in seconds. That's why nobody commits node_modules.
  6. For the full effect, scaffold a real project the way every framework tutorial starts: npx create-vite@latest my-first-app. Watch npx fetch a tool it never permanently installs, generate a project, then cd my-first-app && npm install && npm run dev.

After step 6 you've used every concept in this chapter in the exact sequence you'll repeat for every project from now on.

Where This Lands in the Series

Ch 20 explained npm run <script> — the alias layer on top. This chapter went under it: what npm and npx are, where packages come from, and how the manifest, lockfile, and node_modules fit together. That closes the operational-workflow track (Chs 15–22): CI, lint, tests, PRs, review, file types, CI cost, and now the package tooling that underpins all of it.

Next, a short glossary chapter — Ch 23: Decoding the Jargon — clears up five words you've been bumping into all series (SQL, schema, D1, "the working tree is dirty," and "source of truth"). Then Part 3 opens the modern-frontend track, beginning exactly where this chapter ends: with npx create-next-app. You now know what that command actually does; the remaining question is why you'd run it at all — what a framework like React or Next.js solves that vanilla JavaScript can't, and why almost every team in 2026 builds against one.

Ch 21: Do You Actually Need GitHub Actions?Ch 23: Decoding the Jargon — SQL, Schema, D1, Working Tree & Source of Truth
Production WebProduction Web Apps SeriesProduction patterns for web apps: caching, rate limiting, webhooks, queues, cron jobs, and idempotency.Astro + Next.jsAstro & Next.js SeriesStatic and hybrid web app patterns with Astro, Next.js, MDX, dynamic routes, and Cloudflare deploys.CloudflareCloudflare Feature FocusFocused Cloudflare tutorials for Workers, R2, Stream, Durable Objects, and edge deployment.

Ship your apps faster

When you're ready to publish your Swift app to the App Store, Simple App Shipper handles metadata, screenshots, TestFlight, and submissions — all in one place.

Try Simple App Shipper
5 free articles remainingSubscribe for unlimited access