Package managers are tools that help you install, update, and manage external code libraries (packages) in your projects. Instead of manually downloading and copying code files, package managers automate this process and handle dependencies.
npm (Node Package Manager) is JavaScript's most popular package manager. It gives you access to over 2 million packages: pre-built solutions for common problems like formatting dates, making HTTP requests, or adding animations.
Yarn is an alternative package manager created by Facebook (now Meta). It offers faster installations and better security features. While npm and Yarn work differently under the hood, they're largely interchangeable for basic usage.
In this curriculum, we'll focus on npm since it comes bundled with Node.js and is the standard tool. Once you understand npm, learning Yarn is straightforwardβthe concepts are the same.
# npm commands
npm install
npm install package-name
# Equivalent Yarn commands
yarn
yarn add package-name
<aside> π‘
Using packages means you don't have to reinvent the wheel. Why spend hours building a feature when someone has already created, tested, and maintained a solution?
</aside>
https://www.youtube.com/watch?v=P3aKRdUyr0s&t=14s
While npm is an incredibly useful tool, it's important to understand that the open-source ecosystem can be targeted by attackers.
<aside> β οΈ
Recent attacks have compromised popular packages by targeting maintainers through phishing. Always verify package authenticity and keep dependencies updated.
</aside>
Best practices for staying safe:
package-lock.json to lock specific versionsnpm auditThe JavaScript community responds quickly to security incidents, but vigilance is your first line of defense.
npm comes bundled with Node.js. To check if you have them installed:
node --version # v18.0.0 or similar
npm --version # 9.0.0 or similar
If not installed, download Node.js from nodejs.org (choose LTS version).
Every Node.js project has a package.json file, a manifest that describes your project and lists its dependencies. Itβs like a contract that determines which dependencies need to be used for development.
You can create one with npm init -y. This generates a package.json file:
{
"name": "my-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \\"Error: no test specified\\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {}
}
Key sections:
npm run<aside> β¨οΈ
Create a new folder, navigate to it in your terminal, and run npm init -y. Open the generated package.json and examine its structure.
</aside>
When you run npm install, npm automatically generates a package-lock.json file. This file locks the exact versions of all packages and their dependencies.
{
"name": "my-project",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "<https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz>",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
}
}
}
// In package.json, you might see:
"dependencies": {
"chalk": "^4.1.2" // The ^ means "4.1.2 or any compatible newer version"
}
// package-lock.json ensures everyone gets exactly 4.1.2
Key benefits:
<aside> π‘
Always commit package-lock.json to Git. When teammates run npm install, they'll get the exact same dependency tree as you.
</aside>
package.json defines what you want. **It lists your direct dependencies:
"dependencies": {
"chalk": "^4.1.2" // "4.1.2 or any compatible version"
}
package-lock.json locks what you actually have. It locks the entire dependency tree (including chalk's dependencies, and their dependencies...):
"chalk": {
"version": "4.1.2", // Exactly 4.1.2
"resolved": "<https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz>"
}
<aside> π‘
package-lock.json locks thousands of nested dependencies. Your package.json might list 10 packages, but those 10 packages depend on hundreds more. The lock file ensures everyone's entire dependency tree matches exactly.
</aside>
Imagine you and a teammate both run npm install a few days apart. Without package-lock.json, you might get different versions of nested dependencies if updates were published in between.
# Developer A installs chalk
npm install chalk # Gets 4.1.2, creates/updates package-lock.json
# Developer A commits both files to Git
git add package.json package-lock.json
git commit -m "Add chalk dependency"
# Developer B pulls the code
git pull
npm install # Gets exact same version (4.1.2) from lock file
# Later: Developer A decides to update
npm update chalk # Gets 4.1.5, updates lock file
git commit -m "Update chalk to 4.1.5"
# Developer B pulls again
git pull
npm install # Now gets 4.1.5
<aside> π‘
Updates are coordinated through Git. One developer updates, commits the lock file, and everyone else gets the same versions when they pull. No one gets surprise updates.
</aside>
Without package-lock.json, running npm install at different times could give different versions. With it, everyone gets identical dependencies until someone explicitly updates and commits the change.
npm install package-name
This does three things:
node_modules folderdependencies in package.jsonpackage-lock.json (locks exact versions)Example:
npm install chalk
Your package.json now includes:
{
"dependencies": {
"chalk": "^5.6.2" // version might be different
}
}
When you install packages, npm downloads them into a node_modules folder in your project:
my-project/
βββ node_modules/ # All installed packages
β βββ chalk/
β βββ ansi-styles/ # chalk's dependency
β βββ supports-color/ # chalk's dependency
β βββ ...hundreds more
βββ package.json
βββ package-lock.json
βββ index.js
This folder can become massive (easily hundreds of megabytes or even gigabytes).

<aside> β οΈ
Never commit node_modules to Git (it's huge and unnecessary). Always add it to .gitignore. Others can recreate it by running npm install.
</aside>
npm install package1 package2 package3
npm install [email protected]