npm and Packages

npm is an app store for code that comes with Node.js; you'll describe a project in package.json, install packages into node_modules, tell dependencies from devDependencies, and run the project's commands as npm scripts.

Journeyman · JavaScript · Node.js · Software Development · Updated

After this lesson you can

  • Install packages with npm and tell dependencies from devDependencies in package.json
  • Explain what node_modules and package-lock.json are for and which of them to commit
  • Add an npm script to package.json and run it with npm run

Key takeaways

  • A package is code someone published for anyone to use. npm installs packages from its registry with npm install, and lists them in package.json.
  • A dependency is a package your app needs when it runs. A devDependency is a tool you only need while you build and test, installed with --save-dev.
  • node_modules holds the installed packages and is never committed; .gitignore keeps it out. package-lock.json records exact versions and is committed.
  • npm install with no package name rebuilds node_modules from package.json and package-lock.json. It's the first command for any project you download.
  • An npm script is a named command in package.json. npm run name runs it, and npm start and npm test work without the word run.

Download almost any JavaScript project, whether it's a starter for a class assignment, an open-source game on GitHub or the code base on your first day at a new job, and its README says the same thing first: run npm install. Professional projects are built mostly from code other people wrote and shared, and npm is how that code gets into your project. After this lesson you can set up a project of your own, add other people's code to it, and know which files matter when you share it.

An app store for code

When you want a flashlight on your phone, you don't build one. You open the app store, search, and install. Programmers work the same way. Need to show a date as Saturday, November 14? Fire confetti when someone wins? Draw a chart? Someone has already written that code, tested it, and published it for anyone to use.

A piece of code published to share is called a package. npm (say each letter: "en-pee-em"; it started as short for Node Package Manager) is two things at once:

  • The registry, at npmjs.com: a free, searchable library of millions of packages.
  • The npm command, which came with Node.js when you installed it. It downloads packages from the registry into your project.

A few packages you might reach for: date-fns for dates, canvas-confetti for celebrations, chart.js for charts, phaser for 2D browser games. The tools in the rest of this module (Vite, ESLint, Prettier and Vitest) are npm packages too.

package.json: the project's ID card

Every npm project has a file named package.json at its top: the project's ID card. It holds the project's name, its commands and the packages it uses. You met it in Modules: import and export, where its "type": "module" line made Node read files as modules.

The running example in this chapter is the Game Night Scoreboard: three friends, a few rounds of party games, and a program that keeps score. In VS Code, make a folder game-night in your course folder, open it, and run this in the terminal (-y is read "dash y": it answers yes to every question npm init would ask):

npm init -y

npm writes package.json and shows it:

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

Change "commonjs" to "module", so your files can use import and export. (If your package.json has no "type" line, an older npm made it: add "type": "module", yourself.)

The scoreboard

Two files make the scoreboard. scores.js holds the players, the rounds and the adding up:

export const players = ["Maya", "Jordan", "Priya"];

export const rounds = [
  { game: "Trivia", points: { Maya: 8, Jordan: 6, Priya: 7 } },
  { game: "Charades", points: { Maya: 5, Jordan: 9, Priya: 6 } },
  { game: "Word Scramble", points: { Maya: 7, Jordan: 4, Priya: 9 } },
];

export function totalFor(player, rounds) {
  return rounds.reduce((sum, round) => sum + (round.points[player] ?? 0), 0);
}

export function standings(players, rounds) {
  return players
    .map((player) => ({ player, total: totalFor(player, rounds) }))
    .sort((a, b) => b.total - a.total);
}

main.js prints the standings, highest first:

import { players, rounds, standings } from "./scores.js";

console.log("Game Night Scoreboard");
for (const { player, total } of standings(players, rounds)) {
  console.log(`${player}: ${total}`);
}

node main.js prints:

Game Night Scoreboard
Priya: 22
Maya: 20
Jordan: 19

Installing a package

The scoreboard should say which night it's for, written the friendly way: Saturday, November 14. JavaScript's own dates can do it, with some work. The date-fns package does it in one line. Install it with npm install and the package's name:

npm install date-fns
added 1 package, and audited 2 packages in 5s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities

(Funding lists packages whose authors accept donations, and vulnerabilities counts known security problems in what you installed: 0 is what you want.) Three things changed in the folder: package.json has a new dependencies section, a node_modules folder appeared with the package's code inside, and a file named package-lock.json appeared. Now main.js can import from the package by its name, with no ./ in front:

import { format } from "date-fns";
import { players, rounds, standings } from "./scores.js";

const gameNight = new Date(2026, 10, 14);

console.log(`Game Night Scoreboard: ${format(gameNight, "EEEE, MMMM d")}`);
for (const { player, total } of standings(players, rounds)) {
  console.log(`${player}: ${total}`);
}
Game Night Scoreboard: Saturday, November 14
Priya: 22
Maya: 20
Jordan: 19

The 10 is November because JavaScript counts months from 0. "EEEE, MMMM d" is date-fns's pattern for a weekday, a month and a day, written out. A path that starts with ./ is a file of yours; a bare name like "date-fns" is a package in node_modules.

Dependencies and devDependencies

A bakery buys two kinds of things: flour and butter, which go into the bread you take home, and mixers and ovens, which never leave the kitchen. A project's packages split the same way.

  • A dependency is a package your program needs when it runs, like date-fns. npm install name puts it under "dependencies".
  • A devDependency (a development dependency) is a tool you use only while you build and check the code, such as a code formatter or a test runner. npm install --save-dev name puts it under "devDependencies". (--save-dev is read "dash dash save dev"; -D is its short form.)

Here's Prettier, the code formatter you'll set up in Clean Code with ESLint and Prettier, installed as a devDependency. The @3.9.10 after its name (read "at 3.9.10") asks for that version:

npm install --save-dev prettier@3.9.10
  "dependencies": {
    "date-fns": "^4.4.0"
  },
  "devDependencies": {
    "prettier": "^3.9.10"
  }

Version numbers follow a pattern called semantic versioning (semver for short): major.minor.patch. A patch (4.4.1) fixes bugs, a minor version (4.5.0) adds features without breaking anything, and a major version (5.0.0) may change how things work, so code written for 4 might need changes. The ^ in front, read "caret", means "this version or any newer one with the same major number": ^4.4.0 accepts 4.4.1 and 4.9.0, but not 5.0.0. To pin an exact version with no caret, add --save-exact.

node_modules and package-lock.json

Open node_modules and you'll find a folder for each package: date-fns alone is over a thousand files. A real project's node_modules often holds tens of thousands of files and hundreds of megabytes.

Most developers keep their projects in Git, a tool that saves a history of a project's versions so it can be shared, on GitHub for example. Saving a version is called a commit. You don't need Git for this course, but you'll find its files in every project you download, so it helps to know the rule: you never write in node_modules, and you never commit it. Anyone can rebuild it in seconds from package.json. A file named .gitignore lists what Git should leave alone, and every JavaScript project's has this line:

node_modules/

package-lock.json is different. It records the exact version of every package that was installed (date-fns 4.4.0, not ^4.4.0), including the packages your packages need. Commit it, so everyone who works on the project gets the same versions you tested with. Never edit it by hand: npm keeps it up to date.

This is what makes npm install the first command for a project you download. With no package name, it reads package.json and package-lock.json and rebuilds node_modules. Delete node_modules, run it, and everything comes back:

npm install
added 2 packages, and audited 3 packages in 5s

2 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

On Windows, if the terminal says npm.ps1 cannot be loaded because running scripts is disabled on this system, PowerShell is blocking npm's start-up script. Run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned once, answer Y, and open a new terminal.

npm scripts

Every project has commands you run again and again: start the program, run the tests, build the site. Instead of remembering each one, a project keeps them in package.json, like speed-dial buttons on a phone. Each one is an npm script: a name, and the command it runs. Replace the "scripts" section that npm init wrote with these two:

  "scripts": {
    "start": "node main.js",
    "test": "node --test"
  },

npm run and a script's name runs it. start and test are used so often that npm start and npm test work without the word run. npm prints the script's name and its command, then the command's output:

npm start
> game-night@1.0.0 start
> node main.js

Game Night Scoreboard: Saturday, November 14
Priya: 22
Maya: 20
Jordan: 19

Any other name needs run: a script named lint runs with npm run lint, never npm lint. npm run on its own lists the project's scripts. The script names are the same in almost every project, so on a team, or in a project you download, npm start, npm test and npm run build are often all you need to know to get going.

Try it. Add a script named scores that runs node main.js, and run it. Then change Jordan's Charades score to 12 in scores.js and run it again: who's winning now?

Check yourself

2 questions to try on the lesson page.

So what

Every JavaScript project you'll download, join or start begins with package.json and npm install. Now you can read one, add packages to it, and run its scripts like a professional.

Key terms

Cheat sheets

Related