Menu

npm Basics: install, init, update, and Managing Dependencies

How npm actually works — installing packages, init, dev dependencies, updates, and the mental model behind node_modules and the lockfile.

What npm Actually Is

npm is three things bundled under one name. It's a registry — a huge public database of JavaScript packages at npmjs.com. It's a command-line tool that ships with Node.js for installing and managing those packages. And it's a specification (the package.json format) that describes what a project needs.

When you run npm install express, the CLI talks to the registry, downloads express and everything it depends on, drops the files into a folder called node_modules, and records the package and its version in your package.json. That's the whole loop.

You already have it if you have Node.js installed. Check:

node --version
npm --version

If both print a version, you're ready.

Starting a Project: npm init

Every npm project needs a package.json. That's the manifest — it lists the project's name, version, scripts, and dependencies. The fastest way to create one is npm init -y, which accepts all the defaults:

mkdir my-app
cd my-app
npm init -y

That writes something like:

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Leave off the -y and npm walks you through each field interactively. Either way, you end up with a package.json — the file everything else attaches to. We'll cover its fields in detail on the next page.

Installing a Package

Once you have a package.json, install a package with npm install (or npm i for short):

npm install lodash

Three things happen:

  1. npm downloads lodash and its dependencies into node_modules/.
  2. It adds "lodash": "^4.17.21" (or whatever the latest is) to the dependencies section of package.json.
  3. It writes a package-lock.json recording the exact versions of every package in the tree.

Now you can use it:

index.js
Output
Click Run to see the output here.

The require call (or import in an ESM project) finds the package by looking inside node_modules. You don't write a path — Node's module resolver handles it.

dependencies vs devDependencies

Not every package is needed when your app runs in production. Test frameworks, linters, and bundlers only matter during development. Install those with --save-dev (or -D):

npm install --save-dev jest
npm install -D eslint prettier

These land in devDependencies instead of dependencies:

{
  "dependencies": {
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "eslint": "^8.57.0",
    "prettier": "^3.2.5"
  }
}

On a production server, npm install --omit=dev skips the dev section entirely, keeping the install smaller and faster. Getting this split right matters more than it seems — a misplaced webpack in dependencies bloats every production deploy.

Installing Everything at Once

When you clone a repo that already has a package.json, you don't list each package. Just run:

npm install

With no arguments, npm reads package.json (and respects the exact versions in package-lock.json) and installs the whole tree into node_modules. This is the first thing you run on any fresh checkout.

That's also why node_modules belongs in .gitignore. It's reproducible from the lockfile, it's huge, and it changes every time anyone runs npm install. Commit package.json and package-lock.json; let everyone else regenerate node_modules themselves.

Updating Packages

npm outdated shows what's behind:

npm outdated

You'll see a table with Current, Wanted, and Latest columns. Wanted is the newest version allowed by the range in package.json (for ^4.17.21, anything below 5.0.0). Latest is the newest version published, which might be a major release you haven't opted into yet.

To update within the allowed range:

npm update

To jump to the actual latest, including major version bumps, install the package again with @latest:

npm install lodash@latest

Major version bumps can break your code — that's what the version number is signalling. Read the changelog before crossing one.

Uninstalling

Removing a package is symmetric:

npm uninstall lodash

That removes it from node_modules and deletes the entry from package.json. Add -D if it was a dev dependency (npm figures it out either way, but being explicit avoids surprises in scripts).

Global vs Local

Almost every install should be local — pinned to one project inside its node_modules. The exception is command-line tools you want available anywhere:

npm install -g typescript
npm install -g http-server

A global install puts the tool in a system-wide location and its bin entry on your PATH, so you can run tsc or http-server from any directory. But global installs aren't tracked per-project and can drift out of sync across machines.

A better middle ground for one-off commands is npx, which ships with npm:

npx create-react-app my-app
npx prettier --write .

npx runs a package without installing it globally — it fetches it on demand, runs it, and you're done. For tools you use once, that's cleaner than a permanent global install.

A Minimal Cheat Sheet

The commands you'll actually use day to day:

npm init -y                     # create package.json
npm install                     # install everything in package.json
npm install <pkg>               # add a runtime dependency
npm install -D <pkg>            # add a dev dependency
npm install -g <pkg>            # install a CLI tool globally
npm uninstall <pkg>             # remove a dependency
npm outdated                    # see what's out of date
npm update                      # update within allowed ranges
npm install <pkg>@latest        # jump to the newest version
npm run <script>                # run a script from package.json
npx <pkg>                       # run a package without installing it

That's most of npm. The rest — publishing, workspaces, scoped packages — you can pick up when you need them.

What Actually Lives in node_modules

One last mental model. node_modules is a flat-ish folder containing every package your project depends on, plus everything those packages depend on, transitively. Install one package and you might pull in a hundred — that's normal. npm deduplicates where it can, so two packages depending on the same version of lodash share one copy.

The lockfile (package-lock.json) records the exact resolved version of every one of those packages. That's what makes builds reproducible: two developers running npm install from the same lockfile get byte-identical trees, even months apart.

Treat node_modules as generated output. Never edit files inside it — your changes will vanish the next time anyone installs.

Next: package.json

package.json is the file npm keeps reading and rewriting behind the scenes. Knowing its fields — scripts, main, type, version ranges, engines — is what turns npm from a black box into something you control. That's next.

Frequently Asked Questions

What is npm?

npm is the default package manager for Node.js. It ships with Node, hosts a huge public registry of JavaScript packages, and provides a command-line tool for installing, updating, and publishing them. When you run npm install lodash, npm downloads lodash from the registry into node_modules and records it in package.json.

What's the difference between dependencies and devDependencies?

dependencies are packages your app needs to run in production — things like express or react. devDependencies are only needed while developing or building — test runners, bundlers, linters. You install the latter with npm install --save-dev <pkg> (or -D). In production, npm install --omit=dev skips devDependencies.

Should I commit node_modules to git?

No. node_modules can easily be hundreds of megabytes and is fully reproducible from package.json + package-lock.json. Add it to .gitignore and commit the lockfile instead. Anyone cloning your repo runs npm install and gets the exact same dependency tree.

What does npm install global vs local mean?

A local install (npm install <pkg>) puts the package inside your project's node_modules and records it in package.json. A global install (npm install -g <pkg>) installs it system-wide, usually for command-line tools you want available everywhere. Prefer local installs for project dependencies so versions stay pinned per project.

Learn to code with Coddy

GET STARTED