Home / Articles / What npm install Really Does: Registry, package.json and Lockfiles

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.

1605 words

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:

  1. npm looks up the package in the registry and picks a version matching the requested range (the latest if none is given).
  2. It resolves the package's own dependencies, and theirs, into a full tree.
  3. It downloads whatever is not already cached and places it in node_modules.
  4. It records the dependency in package.json and the exact resolved tree in package-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.json states what the project needs, usually as ranges.
  • package-lock.json records 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.json declares intent; package-lock.json records fact. Commit both.
  • Never commit node_modules.
  • A caret range allows minor and patch updates, and npm update stays inside it.
  • Use npm ci for 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.