npm & package.json
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:
mkdir shop-api
cd shop-api
npm init -yWrote 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:
npm pkg set type=moduleInstalling packages
Install Express, the web framework you will use from the Express 5 Basics lesson onwards:
npm install expressadded 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 withnpm install.package.json: now has adependenciessection 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):
npm install --save-dev eslintYour package.json now looks like this:
{
"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:
node_modules/
.envVersion 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.
| Range | Allows | Example matches |
|---|---|---|
^5.3.0 (npm's default) | Same major version | 5.3.1, 5.4.0, not 6.0.0 |
~5.3.0 | Same minor version | 5.3.1, 5.3.9, not 5.4.0 |
5.3.0 | Exactly this version | Only 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:
{
"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:
npm run dev # development server with watch mode
npm start # same as npm run start
npm test # same as npm run testnpm prints the command before running it. A script called hello that runs node hello.js shows:
> 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
| Command | What it does |
|---|---|
npm install | Install everything listed in package.json (after cloning a project) |
npm ci | Clean install of the exact versions in package-lock.json; use it on servers and in CI |
npm install --omit=dev | Install without dev dependencies (production servers) |
npm uninstall <pkg> | Remove a package and its entry in package.json |
npm outdated | List packages with newer versions |
npm update | Update packages within their allowed ranges |
npm ls | Show the installed packages |
npm audit | Check 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:
{
"engines": {
"node": ">=24"
}
}Common mistakes
- Committing
node_modules. It is huge and machine-specific. Add it to.gitignore. - Deleting
package-lock.jsonto "fix" problems. You lose the record of tested versions. Deletenode_modulesand runnpm installinstead. - Running
npm installin the wrong folder. npm looks forpackage.jsonin the current folder; in a parent folder it creates files where you did not expect. - Leaving
"type": "commonjs"in place and then usingimport. Set"type": "module"for this course. - Putting build or test tools in
dependencies. Use--save-devso production installs stay small.
Key points
npm init -ycreatespackage.json; set"type": "module"to use ES modules.npm install <pkg>adds a dependency;--save-devadds a development-only one.node_modulesholds installed code and is never committed;package-lock.jsonrecords exact versions and is committed.- Versions follow semver (
MAJOR.MINOR.PATCH);^allows minor and patch updates. scriptsgive a project named commands:npm run dev,npm start,npm test, ornode --run <name>.- Use
npm cion 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
mkdir library-app
cd library-app
npm init -y
npm pkg set type=module
npm install express{
"type": "module",
"scripts": {
"start": "node app.js",
"dev": "node --watch app.js",
"test": "node --test"
},
"dependencies": {
"express": "^5.3.0"
}
}node_modules/
.envnpm 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?
The lock file pins exact versions so every install gets the same packages.
2. package.json lists "express": "^5.3.0". Which version may npm update install?
The caret allows newer minor and patch versions within the same major version, 5.
3. How do you install a testing tool that is only needed during development?
--save-dev records it under devDependencies, which production installs can skip.
4. What is the difference between npm run dev and node --run dev?
Node.js's built-in runner skips npm's lifecycle scripts such as predev and postdev.
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
