npm, package.json & Dependencies
Advanced · 18 min read · ▶ live playground · ✦ checkpoint
Sooner or later, your project needs code you should not write yourself: date helpers, test tools, a dev server, maybe a tiny ID generator. npm is Node's package manager: the tool that creates project metadata, downloads shared packages, and runs project commands.
Start with package.json
A package is a reusable project that can be installed by another project. Your app can be a package too, even if you never publish it. The file that makes that true is package.json, a manifest, meaning a project file that names the project and records its commands and dependencies.
In a clean practice folder, the first move often looks like this:
mkdir receipt-board
cd receipt-board
npm init -yThe -y flag accepts npm's defaults. Without it, npm init asks questions one by one. Either path creates a file shaped like this:
{
"name": "receipt-board",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}JSON is strict: double-quoted keys, double-quoted strings, no comments, no trailing commas. You already saw that in the JSON lesson; here it becomes project paperwork.
For modern ESM code, you often add "type": "module" so Node treats .js files as ES modules:
{
"name": "receipt-board",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node src/main.js"
}
}A script is a named terminal command stored in package.json. Scripts give every teammate the same verbs:
npm run start
# npm also lets these common names skip "run":
npm start
npm testThat little scripts object becomes the front desk for your project. Instead of remembering a long command, you remember the project verb.
Installing dependencies
A dependency is a package your project needs. If your receipt app formats dates, you might install date-fns:
npm install date-fns@4.4.0
# added 1 package, and audited 2 packagesHere the command asks for 4.4.0 directly so the transcript stays stable; in real projects, you usually install the current package version recommended by the package's docs.
npm changes three things:
- it adds the package to
package.json - it writes exact install details to
package-lock.json - it downloads package files into
node_modules
After the install, the manifest might include:
{
"name": "receipt-board",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node src/main.js"
},
"dependencies": {
"date-fns": "^4.4.0"
}
}dependencies are packages your app needs to run. devDependencies are packages you need while building, testing, or formatting the project. The command for dev-only packages is npm install --save-dev package-name, often shortened to npm install -D package-name.
How versions stay predictable
Semantic versioning is the major.minor.patch version pattern: 4.4.0 means major 4, minor 4, patch 0. The usual promise is: patches fix bugs, minors add compatible features, majors may break old code.
The symbol before the version is the range:
{
"dependencies": {
"exact-tool": "2.4.1",
"patches-ok": "~2.4.1",
"compatible-updates-ok": "^2.4.1"
}
}Read those like this:
"2.4.1"means exactly2.4.1"~2.4.1"means patch updates in the2.4.xline are allowed"^2.4.1"means compatible updates before3.0.0are allowed
That is package.json: the range of versions your project accepts. package-lock.json is the lockfile, the generated receipt that records the exact versions npm actually installed, including packages your packages depend on.
{
"name": "receipt-board",
"lockfileVersion": 3,
"packages": {
"": {
"dependencies": {
"date-fns": "^4.4.0"
}
},
"node_modules/date-fns": {
"version": "4.4.0",
"integrity": "sha512-example"
}
}
}Commit the lockfile for apps. It lets npm ci install the exact locked tree on another machine:
npm ci
# removes node_modules and installs exactly what package-lock.json recordsWhat node_modules really is
node_modules is the generated folder where npm places downloaded package files. It can get huge because packages can have their own dependencies, called transitive dependencies, meaning packages installed because another dependency needs them.
Think of package.json as your shopping list, package-lock.json as the receipt, and node_modules as the full pantry. You keep the list and the receipt. You do not hand-edit the pantry shelf by shelf.
That is why projects usually commit package.json and package-lock.json, but ignore node_modules. Anyone can rebuild node_modules from the two files with npm install or npm ci.
Healthy suspicion, not fear
Supply-chain hygiene means the small habits that keep third-party code changes visible and intentional. Start with these:
- Check the package name before installing; typo packages are a real failure mode.
- Install only what you need; one small helper can bring many transitive dependencies.
- Commit the lockfile so installs are repeatable.
- Run
npm auditto check known vulnerability reports, then read the proposed fix before applying it. - Use pinning, exact versions like
"4.4.0"with no^, when surprise updates are more dangerous than missing automatic compatible updates.
Most npm work is boring in the best way. You install a package, read its docs, import it, and move on. The professional move is not paranoia. It is leaving a clean paper trail for what entered your project and why.
Now you have files, packages, scripts, and repeatable installs. Next comes the tool that turns those pieces into a fast browser workflow: Vite and the build step.
Checkpoint
Answer all three to mark this lesson complete