Arjun was a junior developer who had just joined a small startup in Bangalore. On his first day, his lead told him: “You’ll build a Task Dashboard for the team – a small Node.js app that tracks tasks, prints useful logs in the terminal, and later exposes APIs for a React frontend.”
By the end of that week, Arjun would discover npm – and realize it’s basically the nervous system of a modern JavaScript project.
The day Arjun met npm
Arjun started with a plain folder:
mkdir team-task-dashboard cd team-task-dashboard
He opened VS Code, created index.js, and wrote:
console.log("Server is starting...");
He ran:
node index.js
It worked – but it felt boring and fragile. He wanted:
Colored logs (green for success, yellow for warnings, red for errors).
An HTTP server to later expose APIs.
A way to restart automatically when he changed code.
Trying to build all of that from scratch would be like forging every part of a car by hand – mining the metal, melting it, hammering it, assembling it. Instead, his seniors told him: “Use npm. You assemble your app from parts that others already built.”
At this point, Arjun had a question:
“So… what exactly is npm?”
What npm really is (beyond the definition)
His lead explained:
Node.js is the runtime that runs JavaScript outside the browser.
npm is the package manager that gives you access to a giant library of reusable code (the npm registry).
You talk to npm using the
npmcommand in your terminal (the CLI).
In other words:
Authors write packages and publish them to the registry.
-
Developers like Arjun install those packages into their projects using commands like:
-
npm install chalk -
npm install express -
npm install --save-dev nodemon
-
Instead of hand-building everything, Arjun could now:
Compose his app out of existing packages.
Focus his time on business logic and features.
Installing npm (without realizing you did)
Arjun asked, “Where do I get npm?”
His lead smiled: “You already have it. npm comes with Node.js.”
When Arjun had installed Node earlier:
He went to
https://nodejs.org.Downloaded the LTS installer for Windows.
Clicked through the wizard – which quietly installed:
- `node` (the runtime).
- `npm` (the package manager).[](https://nodejs.org/en/learn/getting-started/an-introduction-to-the-npm-package-manager)
To confirm, he ran:
node -v
npm -v
Both showed version numbers. Arjun had everything he needed to start playing with npm.
The first spell: npm init
The startup’s GitHub repo used package.json files everywhere, so Arjun asked, “What is this file?”
His lead answered:
“
package.jsonis like your project’s passport and checklist. It describes what the project is and what it depends on.”
Inside team-task-dashboard, Arjun ran:
npm init -y
This did a few things:
Created a
package.jsonfile with default metadata.Told npm, “This folder is now a proper npm project.”
His package.json looked like this:
{
"name": "team-task-dashboard",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "Arjun",
"license": "ISC"
}
He didn’t fully understand it yet, but he would soon rely on it heavily.
Adding color to life: installing chalk (local package)
The next pain point: logs. Everything was plain white text. In a long terminal output, it was easy to miss errors. His friend suggested: “Use chalk – it colors console logs.”
So Arjun ran:
npm install chalk
This single line:
Downloaded
chalkand many tiny helper packages (likeansi-styles,supports-color,color-convert,color-name,has-flag) into a newnode_modulesfolder.Updated/created a
package-lock.jsonfile with exact versions.
Now his folder had:
package.jsonpackage-lock.jsonnode_modules/index.js
In index.js, Arjun updated his code:
import chalk from "chalk";
console.log(chalk.green("Server is starting..."));
console.log(chalk.yellow("Fetching tasks from database..."));
console.log(chalk.red("Error: Database connection failed!"));
Running node index.js now produced colorful logs. A tiny npm package made his app more readable in seconds.
Local vs global: Arjun’s first confusion
The next requirement was clear:
“When I change the code, I don’t want to stop & restart
node index.jsevery time.”
His mentor recommended nodemon, a tool that restarts the server automatically when files change.
Arjun saw two options in blog posts:
npm install nodemonnpm install -g nodemon
He was confused. His mentor explained:
Local packages
Installed in the project.
Live in
node_modulesinside the project folder.Listed in
dependenciesordevDependenciesinpackage.json.Used by the code via
importorrequire.
Example:
npm install chalk express npm install --save-dev nodemon
Use when:
The app itself needs the package to run.
You want each project to manage its own versions.
Global packages
Installed once, system-wide.
Available as commands from any directory.
Usually CLIs or tools, not libraries imported in code.
Example:
npm install -g nodemon
Use when:
- You want to run a tool like
nodemon,npm-check, orservefrom any project.
Mental model Arjun adopted:
“If I import it in my code ⇒ install locally.”
“If I run it as a global tool in my terminal ⇒ maybe install globally.”
For the project, he chose:
npm install --save-dev nodemon
So that it’s part of the project setup and works for anyone who clones the repo.
Scripts: teaching npm to do the boring work
package.json also had a scripts field, which was empty. His mentor said:
“Think of
scriptsas named shortcuts for terminal commands.”
Arjun edited package.json:
{
"name": "team-task-dashboard",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"chalk": "^5.2.0",
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.0"
}
}
Now he could run:
npm run dev
npm looked at
scripts.devand executednodemon index.js.Every time Arjun saved
index.js,nodemonrestarted the server automatically.
For the production-equivalent run, he used:
npm start
-
startis a special script name that can run withnpm startinstead ofnpm run start.
npm had quietly turned his messy commands into a neat, shareable workflow.
When one package depends on 10 more: the dependency web
One day Arjun opened node_modules and was shocked. He had installed only a few packages, but the folder had hundreds of subfolders.
His mentor laughed:
“Welcome to the dependency universe.”
The reality:
Arjun’s project depends on
chalkandexpress.chalkdepends on packages likeansi-stylesandsupports-color.ansi-stylesdepends oncolor-convert.color-convertdepends oncolor-name.expressdepends on its own long list of packages.
To see the full tree, Arjun ran:
npm list
This printed a hierarchy of dependencies and versions.
He finally understood why people joke that node_modules is “the densest matter in the known universe”. Each tiny improvement in his app might pull in dozens of small, focused packages.
Versions: Arjun breaks production (in theory)
A few weeks later, Arjun’s app was running smoothly. Then he saw a message:
“New version of
chalkavailable – includes cool new features!”
Curious, he ran:
npm view chalk versions
He saw a long list of versions: 1.0.0, 2.0.0, 3.0.0, 4.x.x, 5.x.x, and more.
His mentor warned him:
“Updates are great… but they can also break your app if you’re not careful.”
This is where Semantic Versioning (SemVer) comes in.
Semantic versioning: MAJOR.MINOR.PATCH in real life
Every npm package has a version:
MAJOR.MINOR.PATCH
For example: 4.17.1.
MAJOR– big, breaking changes.MINOR– new features, backward compatible.PATCH– bug fixes, no breaking changes.
Examples:
1.0.0→ first stable release.Next patch:
1.0.1.Next minor:
1.1.0.Next major:
2.0.0.
Importantly, these numbers don’t behave like decimals:
How npm uses these numbers in package.json
When Arjun installed chalk, package.json recorded:
"chalk": "^5.2.0"
The ^ (caret) means:
npm can auto-update to newer MINOR or PATCH versions of
5.x.x(e.g.,5.3.0,5.2.1).npm will not auto-update to
6.0.0.
If it had been:
"chalk": "~5.2.0"
The ~ (tilde) would mean:
If there were no symbol:
"chalk": "5.2.0"
- npm would stick to exactly
5.2.0unless Arjun changed it.
Arjun’s safe workflow became:
-
For normal updates:
-
npm update→ get safe MINOR/PATCH updates according to the^or~rules.
-
-
For a major upgrade (e.g.,
4.x.x→5.x.x):-
npm install chalk@5→ then test the app carefully.
-
He understood that SemVer is not just theory; it’s what keeps his app from randomly breaking when packages evolve.
package-lock.json: Arjun’s time machine
A few days later, a teammate cloned Arjun’s repo and ran:
npm install npm run dev
Everything worked the same way. The reason? package-lock.json.
Here’s the subtle problem it solves:
package.jsonallows version ranges like"chalk": "^5.2.0".Arjun installed when the latest was
5.2.0.His teammate might install when the latest is
5.3.1.If
5.3.1or one of its dependencies behaves slightly differently, the teammate might get bugs that Arjun doesn’t see.
package-lock.json fixes this by:
Recording exact versions of every dependency and sub-dependency (MAJOR, MINOR, PATCH).
-
For example, it might pin:
-
chalkat5.2.0 -
ansi-stylesat6.1.0 -
supports-colorat7.2.0, etc.
-
When npm install sees package-lock.json:
- It uses this file to recreate the exact same dependency tree that Arjun had.
Arjun learned two best practices:
Commit
package.jsonandpackage-lock.jsonto Git.Do not commit
node_modules(too large and regenerable).
With this, “it works on my machine” bugs were much less likely.
The complete story: how npm fits Arjun’s daily life
By the end of the sprint, Arjun’s typical workflow looked like this:
-
Start a project
mkdir team-task-dashboard cd team-task-dashboard npm init -y -
Install runtime dependencies
npm install express chalk -
Install dev dependencies
npm install --save-dev nodemon -
Write scripts in package.json
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
} Build features using packages
- Use `express` to create `/tasks` API endpoints.
- Use `chalk` to highlight important logs.
- Use `nodemon` so changes reflect instantly.
- Share the project
- Commit code + `package.json` + `package-lock.json`.
- Teammates run:
`npm install npm run dev`
and get the exact same environment.
- Update dependencies safely
- Occasionally run `npm outdated` and `npm update`.[](https://dev.to/sudiip__17/-npm-modules-explained-in-nodejs-a-beginner-to-intermediate-guide-e70)
- For major versions, update explicitly with `npm install package@version` and test.
Arjun no longer thought of npm as “just a tool” – it was the story behind how his project came alive: how packages got installed, how they were versioned, how his logs turned green and red, and how his team all ran the same code in the same way.
Top comments (0)