npm & package.json
Reviewed & published by Brayan K
npm is the default package manager for Node.js, and package.json is the manifest file at the root of every project that records its name, version, scripts, and the third-party dependencies npm installs.
Part of the free Node.js course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll create a package.json with a single command, install both runtime and development packages, understand semver ranges, and define npm scripts that turn long commands into short, memorable names.
What You'll Learn in This Lesson
- What npm is and what package.json records
- How to scaffold a manifest with npm init -y
- Installing packages with npm install
- dependencies vs devDependencies
- node_modules, package-lock.json, and semver (^ and ~)
- Defining and running npm scripts (start, dev, test)
1️⃣ Creating a Manifest with npm init
Every Node.js project starts with a package.json file. You could write it by hand, but npm will scaffold one for you. Running npm init asks a series of questions; adding the -y ("yes") flag accepts all the defaults instantly.
# Make a new folder for your project and move into it.
mkdir my-app
cd my-app
# Create a package.json with sensible defaults — no questions asked.
# The -y flag answers "yes" to every prompt for you.
npm init -yThat file is the heart of your project. Here is an annotated example showing the fields you'll meet most often. The name, version, and main fields describe the package itself; scripts and the two dependency objects do the day-to-day work.
{
"name": "my-app", // the project's name (lowercase, no spaces)
"version": "1.0.0", // semantic version: MAJOR.MINOR.PATCH
"description": "A demo app", // a short human-readable summary
"main": "index.js", // the entry file when someone requires your package
"scripts": { // named shortcuts you run with "npm run <name>"
"start": "node index.js"
},
"keywords": ["demo"], // words that help people find your package
"author": "Your Name", // who made it
"license": "MIT", // how others may use it
"dependencies": {}, // packages your app needs to RUN
"devDependencies": {} // packages you only need while DEVELOPING
}2️⃣ Installing Packages
The whole point of npm is reusing code other people wrote. The npm install command (short: npm i) downloads a package and records it in your package.json. Where it gets recorded depends on the flag you use.
When npm writes a dependency, it usually adds a version range rather than a single exact version, so you can pick up small bug fixes automatically. Those ranges use semantic versioning (MAJOR.MINOR.PATCH) with two key symbols:
- • ^ (caret) — allow minor and patch updates, but not a new major version. ^4.18.2 means "anything from 4.18.2 up to (but not including) 5.0.0".
- • ~ (tilde) — allow only patch updates. ~4.18.2 means "anything from 4.18.2 up to (but not including) 4.19.0".
- • No symbol — pin the exact version. 4.18.2 means only 4.18.2, nothing else.
{
"dependencies": {
// ^ (caret) allows MINOR and PATCH updates: 4.18.2 -> up to <5.0.0
"express": "^4.18.2",
// ~ (tilde) allows only PATCH updates: 2.29.4 -> up to <2.30.0
"dayjs": "~2.29.4",
// No symbol = exact version. Only 1.4.0, nothing else.
"left-pad": "1.4.0"
}
}3️⃣ npm Scripts
Typing long commands gets old fast. The "scripts" object lets you give a command a short name, then run it with npm run <name>. This is how almost every Node project standardises how it's started, developed, and tested.
{
"name": "my-app",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js", // runs with: npm start
"dev": "nodemon index.js", // runs with: npm run dev
"test": "jest" // runs with: npm test
}
}A few script names are special: start and test can be run without the word run. Every other script needs npm run in front of it.
# "start" and "test" are special — you can drop the word "run":
npm start # same as: npm run start
npm test # same as: npm run test
# Every OTHER script needs "run":
npm run dev
# List all available scripts in this project:
npm runYour turn. Fill in the three blanks marked ___ to build a working manifest, then save it as package.json and run npm start.
{
// 🎯 YOUR TURN — fill in each ___ then save as package.json.
"name": "___", // 👉 your project's name, lowercase (e.g. "todo-cli")
"version": "1.0.0",
"main": "___", // 👉 the file Node runs first (e.g. "index.js")
"scripts": {
"start": "node ___" // 👉 the SAME entry file from "main" above
},
"license": "MIT"
}
// ✅ Expected: running "npm start" launches your entry file.
// > my-app@1.0.0 start
// > node index.jsMini-Challenge: Bootstrap a Project
No blanks this time — just a brief and an outline. Run the steps yourself in an empty folder and confirm the result against the expected file tree. This sequence is exactly how a real Node project begins.
# 🎯 MINI-CHALLENGE: bootstrap a real project from scratch
# Run these in order in an empty folder. No blanks — type them yourself.
#
# 1. Create the manifest with defaults
# 2. Install express as a runtime dependency
# 3. Install nodemon as a dev-only dependency
# 4. Add a "dev" script that runs: nodemon index.js
# 5. Confirm node_modules and package-lock.json now exist
#
# ✅ Expected: package.json lists express under "dependencies",
# nodemon under "devDependencies", and "npm run dev" works.
# your commands hereCommon Errors (and the fix)
- "ENOENT: no such file or directory, open '.../package.json'" — You ran an npm command in the wrong folder. npm looks for package.json in the current directory. cd into your project root (or run npm init -y first to create one).
- Committing node_modules to Git — Don't. It's huge and fully rebuildable. Add node_modules to your .gitignore and commit package-lock.json instead so installs stay reproducible.
- "Cannot find module '...'" after cloning — The repo doesn't ship node_modules. Run npm install to download everything listed in package.json before starting the app.
- Missing or wrong "main" field — If main points at a file that doesn't exist, require("your-pkg") fails. Set main to your real entry file (commonly index.js).
- "EACCES: permission denied" on a global install — Don't fix it with sudo. Use a version manager like nvm so npm owns its own folders, or set a user-level global prefix.
- Version conflicts / "ERESOLVE unable to resolve dependency tree" — Two packages demand incompatible versions of a third. Read the message, align the versions, or as a last resort retry with npm install --legacy-peer-deps.
Pro Tips
- 💡 Use npm ci in CI/CD: it installs straight from package-lock.json for fast, exact, reproducible builds.
- 💡 Check what's outdated with npm outdated, and audit for known vulnerabilities with npm audit.
- 💡 Run a package without installing it using npx — for example npx create-react-app my-app.
📋 Quick Reference — npm Commands
| Task | Command |
|---|---|
| Create a manifest | npm init -y |
| Install a dependency | npm install pkg |
| Install a dev dependency | npm i -D pkg |
| Install everything | npm install |
| Run a script | npm run name |
| Uninstall a package | npm uninstall pkg |
| Install globally | npm i -g pkg |
Tip: the "scripts" object lives inside package.json as { "start": "node index.js" }.
🎉 Lesson Complete!
- ✅ npm is Node's package manager; package.json is the project manifest
- ✅ npm init -y scaffolds a manifest with defaults
- ✅ npm install pkg adds runtime deps; npm i -D pkg adds dev deps
- ✅ Never commit node_modules; do commit package-lock.json
- ✅ Semver: ^ allows minor+patch, ~ allows patch only
- ✅ "scripts" turn long commands into npm run name
Practice quiz
Which command creates a package.json with default values, no questions asked?
- npm init -y
- npm start
- npm new
- npm create
Answer: npm init -y. npm init -y answers yes to every prompt and writes a default package.json.
In semantic versioning, what does the format MAJOR.MINOR.PATCH mean for 1.4.2?
- year.month.day
- size.speed.fixes
- major=1, minor=4, patch=2
- patch.minor.major
Answer: major=1, minor=4, patch=2. 1.4.2 reads as MAJOR 1, MINOR 4, PATCH 2.
Which file lists the EXACT versions installed and SHOULD be committed?
- node_modules
- package-lock.json
- README.md
- .npmignore
Answer: package-lock.json. package-lock.json pins exact versions and belongs in version control.
Which folder holds the actual package code and should NEVER be committed?
- dist
- src
- lib
- node_modules
Answer: node_modules. node_modules holds installed code and is excluded from git.
What does the caret in "express": "^4.18.2" allow?
- Only the exact 4.18.2
- Minor and patch updates, up to <5.0.0
- Any version including 5.x
- Only major updates
Answer: Minor and patch updates, up to <5.0.0. Caret (^) permits minor and patch updates but not the next major.
What does the tilde in "dayjs": "~2.29.4" allow?
- Major updates only
- Any version at all
- Patch updates only, up to <2.30.0
- Nothing — it is exact
Answer: Patch updates only, up to <2.30.0. Tilde (~) permits only patch updates within the same minor version.
Which flag installs a package into devDependencies?
- --prod
- --global
- --exact
- --save-dev
Answer: --save-dev. npm install --save-dev (or -D) records the package under devDependencies.
Which command runs the script named "start" in package.json?
- npm start
- npm run-start
- npm exec start
- node start
Answer: npm start. start is special — npm start works without the word run.
How do you run a custom script named "dev"?
- npm dev
- npm run dev
- node dev
- npm exec dev
Answer: npm run dev. Every script other than start/test/etc. needs npm run <name>.
What does plain npm install (no package name) do in a cloned project?
- Deletes node_modules
- Publishes the package
- Updates npm itself
- Installs everything listed in package.json
Answer: Installs everything listed in package.json. Bare npm install installs all dependencies from package.json.
Continue this course
- Previous: Modules (CommonJS & ESM)
- Next: The Event Loop — Understand how Node runs non-blocking, asynchronous code on one thread
- Quick reference: Node.js cheat sheet › Modules & npm
Frequently asked questions
What is the difference between dependencies and devDependencies?
dependencies are packages your app needs to run in production — like express for a web server. devDependencies are tools you only need while building and testing, such as nodemon, jest, or eslint. Install the first kind with npm install <pkg> and the second with npm install --save-dev <pkg>. When you deploy with npm install --production, npm skips devDependencies, giving you a smaller, faster install.
Should I commit node_modules to Git?
No. The node_modules folder can hold tens of thousands of files and is fully rebuildable from package.json and package-lock.json. Add node_modules to your .gitignore and commit package-lock.json instead — it records the exact versions so anyone who clones your repo gets an identical install by running npm install.
What do the ^ and ~ symbols mean in package.json versions?
They control which updates npm is allowed to install automatically. The caret ^ permits new minor and patch releases (^4.18.2 accepts anything below 5.0.0), while the tilde ~ permits only patch releases (~4.18.2 accepts anything below 4.19.0). A bare version like 4.18.2 pins that exact release. This is semantic versioning, written MAJOR.MINOR.PATCH.
What is package-lock.json and do I need it?
package-lock.json records the exact version of every package (and sub-dependency) that was installed, so installs are reproducible across machines and over time. You should commit it. While package.json may say ^4.18.2 (a range), the lock file nails down precisely 4.18.2, ensuring your teammates and your production server get byte-for-byte the same dependency tree.