package.json and npm basics
Start a project with npm init, add a dependency, understand package.json and package-lock.json and semver ranges, and run scripts with npm run and node --run.
What you will learn
- Create a project with package.json and set its module type
- Install a dependency and a development dependency
- Read a semver range such as ^2.1.3
- Run a project script with npm run and node --run
On this page
A real project is more than a few files. It has a name, a list of other people’s code it depends on, and commands to run it. Node.js projects keep all of that in a file called package.json. npm, the package manager that is installed with Node.js, reads and writes that file.
The commands in this lesson were run with Node.js 26.11.1 and npm 11.20.0. They work in the same way on the Node.js 22 and 24 lines, but the exact files and messages npm prints can differ between npm versions.
Create a project
Make a folder, move into it and run npm init. The -y flag accepts the defaults for every question.
mkdir greetercd greeternpm init -ynpm writes a package.json that looks like this (the name comes from the folder):
{ "name": "greeter", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "type": "commonjs"}Two fields matter right away. "name" and "version" identify the project. "type" decides how Node.js reads .js files: "commonjs" is the older module system, and "module" makes .js files ES modules, as the last lesson described. This course uses ES modules, so change it. Edit the file, or run npm pkg set type=module.
Here is the package.json for a small project we will use. It is "private" so it cannot be published by accident, has "type": "module" and defines two scripts:
{ "name": "greeter", "version": "1.0.0", "private": true, "type": "module", "scripts": { "start": "node src/hello.mjs", "dev": "node --watch src/hello.mjs" }}Its single source file is:
const name = process.argv[2] ?? "world";console.log(`Hello, ${name}!`);Hello, world!process.argv is an array of the words typed on the command line after node. Item 0 is the path of Node itself and item 1 is the path of your script, so process.argv[2] is the first word you added yourself. When you add none, it is undefined, and ?? supplies "world". Run node hello.mjs Ada and it prints Hello, Ada!.
Scripts
The "scripts" field gives names to commands. Run one with npm run NAME. The name start is special, so npm start works too.
npm run start> greeter@1.0.0 start> node src/hello.mjs
Hello, world!npm prints the command it runs, then the program’s output. Node.js can also run a script by itself, without npm:
node --run startHello, world!node --run was added in Node.js 22.0.0. Here it prints no extra lines. It runs the script’s command and nothing else, so it is a good fit for simple scripts like these. To pass arguments to the script, put them after --: node --run start -- Ada prints Hello, Ada!.
The dev script uses node --watch, which restarts the program every time a file it uses changes. Stop it with Ctrl+C.
Add a dependency
A dependency is a package that your project uses. Packages come from the npm registry. To add one, run npm install NAME. Here is a small package called ms, which converts time strings to milliseconds:
npm install msadded 1 package, and audited 2 packages in 461ms
found 0 vulnerabilitiesThree things changed:
package.jsonhas a new"dependencies"section:"ms": "^2.1.3".node_modules/is a new folder holding the package’s files. Do not edit it, and do not commit it to version control.package-lock.jsonis a new file that records the exact version of every package that was installed.
Now use the package. This file is index.js and the project has "type": "module":
import ms from "ms";
console.log(ms("2 days"));console.log(ms(60000));console.log(ms(3600000, { long: true }));1728000001m1 hourimport ms from "ms" has no ./, so Node looks in node_modules for a package named ms. The first call converts text to milliseconds, and the others convert back.
Dependencies and development dependencies
Some packages are needed only while you work on the project, such as a code formatter or a test tool. Install those with --save-dev (or -D):
npm install --save-dev prettierThey go in a separate "devDependencies" section:
"dependencies": { "ms": "^2.1.3"},"devDependencies": { "prettier": "^3.9.10"}dependencies are needed when the program runs. devDependencies are needed only for development. A server can install only the first group with npm ci --omit=dev.
Semver ranges
Package versions follow semantic versioning: three numbers, MAJOR.MINOR.PATCH. A patch release fixes bugs, a minor release adds features without breaking existing use, and a major release may break things. The text before the version in package.json is a range that tells npm which versions are acceptable:
| Written | Allows |
|---|---|
2.1.3 |
exactly 2.1.3 |
^2.1.3 |
2.1.3 up to, but not including, 3.0.0 (what npm install writes by default) |
~2.1.3 |
2.1.3 up to, but not including, 2.2.0 |
For versions that start with 0, the caret is stricter: ^0.2.3 allows only 0.2.x from 2.3, because anything below 1.0.0 may change at any time.
The lock file and npm ci
A range means two people can install different versions on different days. The lock file, package-lock.json, prevents that. It records the exact version of every package, including the packages your packages need. Commit it to version control together with package.json.
npm installinstalls what the lock file says, and updates the lock file when you add or change a dependency.npm ci(“clean install”) deletesnode_modulesand installs exactly what the lock file lists. It fails ifpackage.jsonand the lock file disagree. Use it on build servers and in tests, where you want the same result every time.
npm ciadded 2 packages, and audited 3 packages in 361msTo see what is installed, run npm ls. To remove a package, run npm uninstall NAME.
Common pitfalls
- Committing
node_modules. It is large and anyone can recreate it frompackage.jsonand the lock file. Keep it out of version control. - Forgetting the lock file. Commit
package-lock.json. - Editing
package.jsonby hand and forgettingnpm install. The lock file andnode_modulesfall out of date. - Leaving
"type": "commonjs"and usingimport. The"type": "commonjs"thatnpm init -ywrites means exactly that:importin a.jsfile is a syntax error. Set"type": "module", or use.mjs. (With no"type"at all, Node 22.7 and later guesses by syntax detection, as the ES modules lesson explained, but it warns on each run. Set the field.) - Installing a package you do not recognise. Check its name and its publisher first. Installing runs other people’s code on your machine.
Exercises
Make a new folder and turn it into an ES-module project. Install ms, create index.js that prints ms("1 hour"), and add a script called start that runs it. Run it with both npm start and node --run start. Expected result: both commands print 3600000.
Show solution
mkdir timercd timernpm init -ynpm pkg set type=module scripts.start="node index.js"npm install msindex.js:
import ms from "ms";
console.log(ms("1 hour"));Both npm start and node --run start print 3600000. Check package.json: "type" is "module", "ms" appears under dependencies and scripts.start holds the command.
A project lists "left-pad": "~1.3.0". Would npm accept version 1.3.7? 1.4.0? 2.0.0?
Show solution
1.3.7 is accepted: it is the same minor version, and only the patch differs. 1.4.0 is not accepted, because ~ allows patch changes only. 2.0.0 is not accepted either. With ^1.3.0 instead, both 1.3.7 and 1.4.0 would be accepted, and 2.0.0 would not.
Summary
package.jsondescribes a project: its name, version, module type, scripts and dependencies.npm init -ycreates one.- Set
"type": "module"so.jsfiles are ES modules. npm install NAMEadds a dependency, and--save-devadds a development one. Packages live innode_modules.^and~allow newer compatible versions.package-lock.jsonpins exact versions, andnpm ciinstalls exactly those.npm run NAMEandnode --run NAMErun a script frompackage.json.
Next: loading JSON and code while a program runs.