This article is published in English.
What npm install Really Does: Registry, package.json and Lockfiles
A practical walkthrough of npm: the registry and CLI, how npm install resolves packages, what package.json and package-lock.json record, and how to publish.
Almost every JavaScript project, whether it runs on Node.js, React or Next.js, starts with the same command, and many developers run it daily without a clear picture of what it does. Knowing where packages come from, how npm decides what to install, and what package.json, package-lock.json and node_modules are each for makes broken installs easier to debug. This guide covers each piece, from the first install to publishing your own package.
The commands everyone types
The most familiar form installs everything a project already declares:
npm install
Just as common is adding a library by name, such as the Express web framework:
npm install express
or the Axios HTTP client:
npm install axios
Behind these one-liners sit a few questions. Where is the code downloaded from? How does npm know what a project depends on? What is package.json for? And why does node_modules grow so large?
What npm is
npm is the default package manager for Node.js and ships with it. It gives you access to a huge catalog of reusable packages published by other developers, so you can pull proven code into your project instead of writing it yourself.
Think of it as a public library for code. Suppose a Node.js backend needs an HTTP server. Rather than building routing and middleware handling from scratch, you add Express:
npm install express
Calling other APIs? Add an HTTP client:
npm install axios
Validating incoming data against a schema is handled by Zod:
npm install zod
And bcrypt takes care of hashing passwords:
npm install bcrypt
That is the ecosystem's biggest strength: when a well-tested solution exists, there is rarely a reason to write your own. The flip side is that every installed package is code you now trust, so prefer maintained, widely used libraries and keep the list lean. See how npm supply chain attacks work for why this matters.
An ecosystem, not only a command
Newcomers often see npm as just a word typed into the terminal. The name actually covers several cooperating parts:
- The npm registry: the public service where packages are published and downloaded from.
- The npm CLI: the command-line tool that talks to the registry and manages dependencies.
- Packages: the reusable units of code themselves.
package.json: the manifest describing your project and what it depends on.
Public packages are free: you can install anything public without an account. That low barrier helped open-source JavaScript spread worldwide. An account is only needed to publish.
What happens when you run npm install
Take a project that adds Express:
npm install express
At a high level:
- npm looks up the package in the registry and picks a version matching the requested range (the latest if none is given).
- It resolves the package's own dependencies, and theirs, into a full tree.
- It downloads whatever is not already cached and places it in
node_modules. - It records the dependency in
package.jsonand the exact resolved tree inpackage-lock.json.
That is how one short command brings in a whole tree of functionality.
package.json: the project's manifest
One file sits at the heart of every Node.js project:
package.json
It acts as your project's identity card and configuration in one, holding the name, version, description, scripts, runtime and development dependencies, and author details. A minimal example declares an entry file in main, one script and a single dependency:
{
"name": "my-project",
"version": "1.0.0",
"description": "My Node.js application",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^5.1.0"
}
}
name
name identifies the project or package. If you publish it, this is what people type to install it, so it must be unique in the registry.
"name": "my-project"
version
version holds the package's current version.
"version": "1.0.0"
It matters most when publishing. npm uses semantic versioning (MAJOR.MINOR.PATCH): breaking changes bump the major number, compatible features the minor, fixes the patch.
description
description is a one-line summary of what the project does, shown in registry search results.
"description": "My Node.js application"
scripts
scripts defines named shortcuts for frequent commands. Here start launches the entry file:
"scripts": {
"start": "node index.js"
}
Without it you would type the full command:
node index.js
With it, this does the same:
npm start
The value is consistency: every contributor and every CI job runs the same named command. Custom names such as build run with npm run build.
Dependencies: what your project relies on
Imagine a food delivery platform. Its backend might use:
- Express for the APIs
- Zod for validation
- bcrypt for password hashing
- a JSON Web Token library for authentication
- Mongoose for MongoDB
Each is a dependency. When you install one:
npm install express
npm adds an entry to package.json:
"dependencies": {
"express": "^5.1.0"
}
The caret in ^5.1.0 is a range: any later 5.x release is accepted, 6.0.0 is not. The entry tells anyone reading the project that it needs Express to run. Tools needed only during development, such as test runners, go into devDependencies via npm install --save-dev.
Why one command scales to a whole team
Say you join a team and clone its repository. Nobody expects you to fetch fifty libraries by hand. You run:
npm install
npm reads the declared dependencies and installs them all. Rebuilding a working environment from a description is why package managers are indispensable.
node_modules: where installed code lives
After an install, a new folder appears:
node_modules/
It contains the packages you requested plus everything they depend on, which is why it gets large. A typical layout:
my-project/
│
├── node_modules/
├── package.json
├── package-lock.json
└── index.js
Do not commit node_modules to Git. It is big, may contain platform-specific builds, and can always be regenerated. Put it in .gitignore and commit these instead:
package.json
package-lock.json
Anyone else can then run:
npm install
and get the same dependencies back.
package-lock.json: the exact record
A second file sits next to the manifest:
package-lock.json
Why both? The short version:
package.jsonstates what the project needs, usually as ranges.package-lock.jsonrecords exactly what was installed: every package version in the whole tree, plus integrity hashes.
Since ranges can match newer releases over time, the lockfile keeps laptops, teammates and CI on identical versions. Commit it, and prefer npm ci in CI, which installs strictly from the lockfile.
npm commands worth knowing
Add a package:
npm install express
Install everything the project declares:
npm install
Remove a package from node_modules and package.json:
npm uninstall express
Update packages to the newest versions the ranges allow (it will not cross a major version):
npm update
Print the npm version:
npm -v
Print the Node.js version:
node -v
Show the npm account you are logged in as:
npm whoami
From consumer to publisher
You can also contribute packages. Publish a useful library and others install it like any other:
npm install your-package
That loop keeps open source alive: you build on others' work, create your own, and share it back.
Publishing step by step
You need an npm account. Sign in from the terminal:
npm login
Confirm which account is active:
npm whoami
Move into the project:
cd myproject
Make sure it has a valid manifest with a unique name and a version:
package.json
Then publish:
npm publish
The package is now available to everyone. Two tips: run npm pack --dry-run first to see which files will ship, and bump version for every release, because a published version cannot be reused. Check the current npm docs for account security requirements such as two-factor authentication.
How the pieces fit together
The whole flow looks like this:
Developer
↓
npm install
↓
npm Registry
↓
Package + Dependencies
↓
node_modules
↓
package.json
↓
Your Application
You run the command, the CLI fetches packages from the registry, the code lands in node_modules, the manifest and lockfile record it, and your application imports it. That chain is what lets developers reuse, manage, share and publish code efficiently.
Key takeaways
npm can look like a pile of terminal commands, but once you know what happens behind this one:
npm install
the system becomes easy to reason about. JavaScript supplies the language, Node.js the runtime, and npm the ecosystem for sharing code. In practice:
package.jsondeclares intent;package-lock.jsonrecords fact. Commit both.- Never commit
node_modules. - A caret range allows minor and patch updates, and
npm updatestays inside it. - Use
npm cifor exact installs in automation. - Add dependencies deliberately; each one is code you trust.
So the next time you type:
npm install
remember that you are connecting your project to an ecosystem built by developers all over the world.