npm & package.json

Node.js · Lesson 6 of 30 8 min read Updated

You rarely build everything yourself. Need a web framework? A database driver? A date library? Someone has already published a well-tested package for it. npm (Node Package Manager) is the tool that downloads these packages and keeps track of them. It is installed together with Node.js, and the npm registry at npmjs.com hosts millions of free packages.

In this lesson you will start a project with npm init, read every important part of package.json, install Express, understand version ranges and the lock file, and run tasks with npm run.

Starting a project

Every Node.js project has a package.json file in its main folder. It records the project's name, its packages and its commands. Create one with npm init. The -y flag accepts all the default answers:

Terminal
mkdir shop-api
cd shop-api
npm init -y
Output
Wrote to C:\node-course\shop-api\package.json:

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

npm 11, which comes with Node.js 26, writes "type": "commonjs". This course uses ES modules, so change it to "module". You can edit the file in your editor or let npm do it:

Terminal
npm pkg set type=module

Installing packages

Install Express, the web framework you will use from the Express 5 Basics lesson onwards:

Terminal
npm install express
Output
added 65 packages, and audited 66 packages in 13s

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

found 0 vulnerabilities

The time depends on your internet speed. You asked for one package, but npm added 65. Express itself depends on other packages, which depend on others. These are called transitive dependencies, and npm installs them all. Three things changed in your folder:

  • node_modules/: a folder holding the code of all 65 packages. Never edit it, and never commit it to Git; anyone can recreate it with npm install.
  • package.json: now has a dependencies section listing "express": "^5.3.0".
  • package-lock.json: records the exact version of every package installed, including transitive ones. Commit this file. It makes sure your teammates and your server get exactly the same versions you tested.

Some packages are only needed while developing, such as linters and test tools. Install them as dev dependencies with --save-dev (or -D):

Terminal
npm install --save-dev eslint

Your package.json now looks like this:

JSON
{
  "name": "shop-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "module",
  "dependencies": {
    "express": "^5.3.0"
  },
  "devDependencies": {
    "eslint": "^10.13.0"
  }
}

Create a .gitignore file in the project folder with these lines, so Git skips the installed packages and your secrets:

Text
node_modules/
.env

Version numbers and ranges

Packages use semantic versioning (semver): MAJOR.MINOR.PATCH, such as 5.3.0.

  • PATCH goes up for bug fixes (5.3.0 to 5.3.1).
  • MINOR goes up for new features that do not break old code (5.3.0 to 5.4.0).
  • MAJOR goes up for breaking changes (4.x to 5.0.0). Upgrading may need code changes, as you will see with Express 4 and 5.

The symbol in front of the version in package.json is a range: it says which future versions are acceptable when someone runs npm install without a lock file, or runs npm update.

RangeAllowsExample matches
^5.3.0 (npm's default)Same major version5.3.1, 5.4.0, not 6.0.0
~5.3.0Same minor version5.3.1, 5.3.9, not 5.4.0
5.3.0Exactly this versionOnly 5.3.0

Scripts: your project's commands

The scripts section names commands for your project, so nobody has to remember long ones. Update it:

JSON
{
  "scripts": {
    "start": "node server.js",
    "dev": "node --watch --env-file-if-exists=.env server.js",
    "test": "node --test"
  }
}

Run any script with npm run <name>. The names start and test have shortcuts:

Terminal
npm run dev      # development server with watch mode
npm start        # same as npm run start
npm test         # same as npm run test

npm prints the command before running it. A script called hello that runs node hello.js shows:

Output
> shop-api@1.0.0 hello
> node hello.js

Hello from hello

Scripts can call programs installed in node_modules by name, for example "lint": "eslint .", because npm adds node_modules/.bin to the PATH while the script runs.

Node.js also has its own faster runner: node --run dev runs the same script without starting npm. Unlike npm run, it does not run pre and post scripts (such as predev), so use it for simple scripts.

Other everyday commands

CommandWhat it does
npm installInstall everything listed in package.json (after cloning a project)
npm ciClean install of the exact versions in package-lock.json; use it on servers and in CI
npm install --omit=devInstall without dev dependencies (production servers)
npm uninstall <pkg>Remove a package and its entry in package.json
npm outdatedList packages with newer versions
npm updateUpdate packages within their allowed ranges
npm lsShow the installed packages
npm auditCheck installed packages for known security problems
npx <tool>Run a package's command without installing it globally

You can also record the Node.js versions your project supports, so tools can warn people who use an old one:

JSON
{
  "engines": {
    "node": ">=24"
  }
}

Common mistakes

  • Committing node_modules. It is huge and machine-specific. Add it to .gitignore.
  • Deleting package-lock.json to "fix" problems. You lose the record of tested versions. Delete node_modules and run npm install instead.
  • Running npm install in the wrong folder. npm looks for package.json in the current folder; in a parent folder it creates files where you did not expect.
  • Leaving "type": "commonjs" in place and then using import. Set "type": "module" for this course.
  • Putting build or test tools in dependencies. Use --save-dev so production installs stay small.

Key points

  • npm init -y creates package.json; set "type": "module" to use ES modules.
  • npm install <pkg> adds a dependency; --save-dev adds a development-only one.
  • node_modules holds installed code and is never committed; package-lock.json records exact versions and is committed.
  • Versions follow semver (MAJOR.MINOR.PATCH); ^ allows minor and patch updates.
  • scripts give a project named commands: npm run dev, npm start, npm test, or node --run <name>.
  • Use npm ci on servers and check packages carefully before installing them.

Exercise

Create a project folder called library-app. Initialise it with npm, switch it to ES modules, install Express as a normal dependency, and add three scripts: start (runs node app.js), dev (runs app.js in watch mode) and test (runs node --test). Write the final scripts and type part of your package.json, and the two lines of your .gitignore.

Show solution
Terminal
mkdir library-app
cd library-app
npm init -y
npm pkg set type=module
npm install express
JSON
{
  "type": "module",
  "scripts": {
    "start": "node app.js",
    "dev": "node --watch app.js",
    "test": "node --test"
  },
  "dependencies": {
    "express": "^5.3.0"
  }
}
Text
node_modules/
.env

npm pkg set edits package.json from the terminal; you can also edit the file by hand. Run the development server with npm run dev.

Quick Quiz

1. Which file records the exact version of every installed package and should be committed to Git?

2. package.json lists "express": "^5.3.0". Which version may npm update install?

3. How do you install a testing tool that is only needed during development?

4. What is the difference between npm run dev and node --run dev?

Learn Node.js with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call