One Name, Three Things
Documentation uses the word "npm" for three different things and rarely says which one it means.
The command
This is a program on your computer, installed alongside Node. When you type npm install, this is what runs.
The registry
This is a website at npmjs.com holding several million published packages. The command talks to it over the internet. It is a public library, and anyone can add to it.
The package format
This refers to an agreement about what a downloadable folder of JavaScript looks like: it has a package.json at its root, and that file names it and lists what it needs.
Why the confusion sticks
npm ships inside the Node installer, so nobody installs it on purpose and nobody gets a moment where they learn what it is. It is already there the first time anyone needs it.
npm install http-server
│ │ └── the package: a folder of code, named in the registry
│ └────────── the command you typed
└────────────── the CLI program, installed alongside Node The three, and one command using all of them
Here is HAP on the same three meanings, with a real package page and a real node_modules folder on screen rather than descriptions of them. It ends where this page picks up next: what that folder holds, and why it never goes into a repository. This video runs for nearly four minutes.
What A Package Is
A package is a folder of files with a package.json at the top. That is the entire definition. The package.json gives it a name, a version number, and a list of the other packages it needs.
The last part is where the size comes from. A package you install depends on other packages, which depend on other packages. npm follows that chain to the bottom and downloads every link in it. A single tool install routinely downloads two hundred folders, which is normal behavior.
🟠 What node_modules actually is
This is a plain folder full of plain folders, each one downloaded from the registry and each one holding readable JavaScript. None of it is compiled or encrypted, so opening it and reading a package's source is a completely reasonable thing to do.
It is also enormous, entirely reconstructable from package.json, and therefore never committed to git. You can delete this folder and run npm install again to solve most package issues.
The Two Files That Matter
package.json configures your project
A person writes this file. It is short, readable, and edited by hand. The file records your intent, such as wanting version 14-point-something of a tool. It is always committed to git.
package-lock.json is generated for security
npm writes this file. It is long, unreadable, and never edited by hand. The file records the exact version of every single package installed, including the ones you never asked for. It is also committed to git.
The lock file is why a project installs the same way on someone else's laptop. package.json says "version 14-point-anything is acceptable" and that could mean 14.1.1 today and 14.3.0 next month. The lock file pins the answer, so a teammate cloning the project gets your versions.
"node_modules/http-server": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
"integrity": "sha512-...",
"dev": true
} The integrity field is a checksum of the downloaded file. If the contents ever stop matching that hash, npm refuses the install rather than running code that changed underneath you. That is the security feature ensuring you only run the exact code you intended to download rather than a malicious version injected by a hacker.
dependencies And devDependencies
Packages land in one of two lists, and which list is a statement about when the code is needed.
{
"dependencies": {
"some-library": "^2.1.0"
},
"devDependencies": {
"http-server": "^14.1.1"
}
} dependencies
This is code that must be present for the finished site to work. It ships, and a visitor's browser or a running server ends up needing it.
devDependencies
These are tools used while building the project. They include servers, linters, formatters, and generators, none of which need to be in a browser.
How they get there
The command npm install package-name writes to dependencies, while npm install --save-dev thing writes to devDependencies.
For a static site
Almost everything is a devDependency for a static site because the output is finished HTML and CSS. The tools do their work before deployment and are not needed afterward.
The short version
You can determine where a package belongs by asking if a visitor's browser needs the code. If not, it belongs in devDependencies.
Installing Is Running Somebody Else's Code
A note from Grace Hopper
The registry is public. Anyone may publish to it, and a published package may run a script of its own at the moment it is installed. A single install command can therefore execute code written by a person you have never evaluated.
That is a reason to be deliberate about packages rather than a reason to avoid them. You must confirm the name character by character before installing because typo-squatting is a known attack. Where the platform already does the job, you can use the platform instead. Installing fewer things is also a good practice.
What Happens When You npm Install
An install command in a project folder produces three changes. Knowing which is which makes the next page's steps predictable.
node_modules/appears or grows. This folder contains downloaded code and is ignored by git.package.jsongains a line naming what you asked for. This is your file, and you commit it.package-lock.jsonis created or updated with exact versions. This file is generated, but you commit it anyway.
The next page runs those commands and reads the results line by line.