Tutorials

A Beginner's Guide to NPM

Key Takeaways

  • npm helps you install, manage, and share JavaScript packages, including their dependencies.
  • Use package.json to describe your project and package-lock.json to record its resolved dependency tree.
  • Use npm install when changing dependencies and npm ci for clean, lockfile-based installs in automation.
  • Installing shared code creates supply-chain risk. Review packages, control installation scripts, and treat npm audit as one check—not a safety certificate.

If you've used npm init or npm install while learning Node.js, you've already met npm's everyday workflow. npm is a JavaScript package manager: it helps you install reusable code, resolve that code's dependencies, and record what your project needs. Its ecosystem also lets developers publish packages for others to use.

The benefit is straightforward: you don't have to build everything yourself. The trade-off is that somebody else's code becomes part of your application or development environment.

What Is npm?

npm was created by Isaac Z. Schlueter in 2009. Although people often expand its name to “Node Package Manager,” the project's own branding FAQ says npm isn't an acronym for that phrase.

Separate these related things:

  • The npm CLI: the command-line interface you run in a terminal, using commands such as npm install.
  • The npm registry: the service that stores published packages and their metadata. The CLI uses the public npm registry by default, but you can configure another compatible registry.
  • The npm website: the interface for discovering packages and managing accounts.
  • The package named npm: the package containing the CLI itself. It's not a dependency you need to add to every application.

Why Use a Package Manager?

Suppose you're building an HTTP service with Express. Instead of downloading Express manually and finding every package it needs, you install it with npm. npm resolves the dependency tree—the packages your project needs, plus the packages those packages need—and installs the code locally. Those indirect dependencies are called transitive dependencies.

Your project
  package.json       -- requested dependencies --> npm CLI
  package-lock.json  -- resolved dependency tree -> npm CLI
                                                    |
                                              fetch packages
                                                    |
                                              npm registry
                                                    |
                                                    v
                                              node_modules/

Express is one example of reusable code; Socket.IO provides bidirectional, event-based communication between clients and servers. Neither a familiar package name nor a large download count removes the need to assess what you're installing.

How to Use npm: A Small Express Project

Start With a Supported Node.js LTS Release

For most beginners, choose an actively supported Node.js LTS release rather than whichever release has the highest number. LTS means long-term support. Check the official release status and follow npm's installation guidance. Node.js installers normally include npm, although npm can also be updated separately.

Verify that both commands are available:

node --version
npm --version

Initialize the Project and Install Packages

Run these commands in your terminal:

mkdir npm-beginner-demo
cd npm-beginner-demo
npm init -y
npm install express
npm install -D --save-exact prettier
npm pkg set "scripts.start=node server.mjs" "scripts.format=prettier --write server.mjs"

npm init asks questions and creates package.json; -y skips the questionnaire. The final command adds two entries to that file's scripts object.

The installs record Express under dependencies and Prettier under devDependencies. Use dependencies for packages your application needs at runtime; use development dependencies for tools such as formatters, linters, and test runners. -D selects devDependencies, and --save-exact records an exact version rather than a version range.

Create and Run the Express Server

Create a file named server.mjs in the project directory. The .mjs extension explicitly identifies it as an ECMAScript module, so you can use import syntax.

import express from 'express';

const app = express();

app.get('/', (_req, res) => {
  res.send('Hello from npm!');
});

app.listen(3000, '127.0.0.1', (error) => {
  if (error) {
    console.error('Could not start the server:', error.message);
    process.exitCode = 1;
    return;
  }

  console.log('Open http://127.0.0.1:3000');
});

This creates a local server with one GET route. The startup callback handles errors such as an occupied port. It's a learning example, not a production-ready service.

Format the file, then start the server:

npm run format
npm start

npm run format executes your format script; npm start executes your start script. Running npm run without a script name lists available scripts. npm scripts can find locally installed command-line tools, so Prettier doesn't need a global installation.

For the next step, see our Node.js and Express tutorial.

package.json, Lockfiles, and Dependency Versions

After installing packages, you'll work with three important project artifacts:

  • package.json: the project manifest. It contains metadata, scripts, and dependency requirements.
  • package-lock.json: npm's generated record of the resolved dependency tree, including exact versions and package integrity information.
  • node_modules: the directory containing locally installed packages.

Commit package.json and package-lock.json to version control. Normally, exclude node_modules and recreate it through installation. Review lockfile changes alongside dependency changes; don't delete the lockfile as a routine troubleshooting step, because you'll discard the recorded resolution.

Understand Version Ranges

A dependency requirement doesn't always identify one version. For hypothetical stable releases:

  • 1.2.3 requests that exact version.
  • ^1.2.3 allows versions at least 1.2.3 but below 2.0.0.
  • ~1.2.3 allows versions at least 1.2.3 but below 1.3.0.

Caret ranges are narrower for zero-major releases: ^0.2.3, for example, stays below 0.3.0. These are range-selection rules, not guarantees that an update won't break your application. See npm's version-range reference for the precise rules, including prereleases.

npm install vs npm ci

CommandTypical useLockfile behavior
npm installLocal development and dependency changesUses locked versions when they satisfy package.json; resolves changes and updates the lockfile when needed.
npm ciContinuous integration, deployments, and clean local reinstallsRequires an existing lockfile, rejects dependency mismatches, and doesn't rewrite package.json or package-lock.json.

Continuous integration, or CI, runs automated checks on your changes. npm ci removes an existing node_modules directory before installing the project; it cannot add an individual dependency. Keep dependency-tree configuration consistent between the machine generating the lockfile and CI.

For more detail, read how npm install works. For maintenance, npm outdated reports available updates, while npm update updates dependencies within applicable version constraints. Review and test changes rather than treating an update as automatically safe. Our npm update guide explains that workflow.

npm Security: What You Are Trusting

Availability Problems Aren't the Same as Malicious Packages

On March 22, 2016, the maintainer of left-pad unpublished it amid a package-name dispute, breaking dependent builds. This was a dependency-availability incident, not a malicious-package compromise. npm subsequently changed its unpublishing safeguards; its current unpublish policy restricts when packages can be removed.

The broader lesson still matters: your build depends on more than your own repository. A lockfile records what to fetch, but doesn't guarantee that every required artifact will remain available.

Recognize Supply-Chain Threats

A software supply-chain attack targets code or infrastructure you depend on. npm's threat overview describes several relevant attack paths:

  • Typosquatting: a malicious package uses a name resembling a legitimate package.
  • Dependency confusion: a public package impersonates an internal package name and is selected through incorrect registry configuration.
  • Account takeover: an attacker compromises a publisher's credentials and gains control of their packages.
  • Malicious releases: a previously useful package gains harmful behavior.
  • Transitive exposure: the problematic package arrives through another dependency, rather than one you chose directly.

Control Installation Scripts

Lifecycle scripts are commands attached to events such as installation. Packages can declare hooks including preinstall, install, and postinstall, so installing code can also involve executing code.

When a project supports installation without lifecycle scripts, you can explicitly disable them:

npm ci --ignore-scripts

This can leave packages that require build or setup scripts unusable. It also isn't a sandbox: explicitly running npm run still executes the requested script, and importing a malicious package can execute its code later.

Use npm audit Without Overestimating It

npm audit submits dependency information to your configured default registry and requests a report of known vulnerabilities. It can help identify vulnerable direct and transitive dependencies; it doesn't establish that every package is trustworthy.

npm audit
npm audit fix --dry-run --json

The second command previews proposed fixes without applying them. Review the advisory, affected versions, dependency path, and whether your application uses the vulnerable functionality. Use npm explain with the reported package name to find why it's installed.

After review, npm audit fix can apply available remediations. Don't reflexively add --force: it permits updates outside your declared dependency ranges, including potentially breaking major-version changes. Inspect the diff and run your tests.

Practical npm Security Checklist

  • Install deliberately. Verify the exact package name and repository. Check maintainers, release history, installation scripts, and whether the dependency earns its place.
  • Review updates. Inspect manifest and lockfile changes, especially unexpected packages or maintainer changes. Keep dependencies and your Node.js runtime supported.
  • Understand provenance. Where available, npm provenance connects a published package to its source and build process. It doesn't certify that the code is harmless.
  • Separate internal packages. Use organization-scoped names and explicitly map them to the intended registry to reduce dependency-confusion risk.
  • Protect publishing access. Enable strong two-factor authentication, protect credentials, and avoid exposing secrets to untrusted installation jobs. For applications you never intend to publish, setting "private": true in package.json makes npm refuse publication.

npm makes shared code practical; it doesn't transfer responsibility for that code away from you. For an Express application, dependency hygiene is only one layer—also review our Express security checklist.

FAQ

What is npm and what is it used for?

npm is a package manager commonly used with JavaScript and Node.js. You use its CLI to install dependencies, manage package versions, and run project scripts; its registry lets developers distribute packages.

Is npm included with Node.js?

Node.js installers normally include npm. Install an actively supported Node.js LTS release, then check node --version and npm --version; npm can also be updated separately.

What do package.json and package-lock.json do?

package.json describes your project, scripts, and dependency requirements. package-lock.json records the resolved dependency tree, including exact package versions; commit both files so teammates and automation can reproduce that resolution.

What is the difference between npm install and npm ci?

npm install installs dependencies and can update the lockfile when dependency requirements change. npm ci requires a matching lockfile, removes an existing node_modules directory, and installs without rewriting the manifest or lockfile.

Does npm audit guarantee that a package is safe?

No. npm audit reports known dependency vulnerabilities using advisory information supplied by your configured registry; it cannot guarantee the absence of malicious code, unknown vulnerabilities, or compromised publishers.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. Author of Mastering the Tech Interview. More about me