# Packages and npm

Course: [Node.js](https://www.devtutorials.ai/learn/nodejs) · Section: Node.js Foundations
Source: https://www.devtutorials.ai/learn/nodejs/foundations/packages-and-npm

> What npm and the npm registry are, package.json fields, dependency types, installing and updating packages, semantic versioning and version ranges, package-lock.json and npm ci, npm scripts and node --run, npx and local binaries, how Vite and the Angular CLI use Node.js, npm audit, package entry points with exports and imports, and optional workspaces, publishing and other package managers.

## Packages and npm

**Package** = a folder of code with a `package.json` file, ready to share.

**npm** = the tool that downloads, updates and shares packages.

- Other people have already written code for common jobs.
- You can use their packages instead of writing everything yourself.
- npm comes with Node.js. You installed it in **Introduction to Node.js**.

**Simple lesson:** a package is shared code, and npm is the tool that moves it around.

### A Real-World Example

Think of a school library.

- The library has thousands of books on its shelves.
- A student fills in a borrow slip with the book name and edition.
- The librarian fetches the book from the shelf.
- The register keeps the exact copy number, so the same copy can be found again.

Now the mapping:

- The shelves are the **npm registry**.
- The borrow slip is **package.json**.
- The librarian is the **npm** command.
- The register is **package-lock.json**.

## What You Will Learn

In this lesson, you will learn:

1. What npm and the npm registry are
2. The main fields of `package.json`
3. Dependency types and installing, updating and removing packages
4. Semantic versioning and version ranges
5. `package-lock.json`, `npm install` and `npm ci`
6. npm scripts, `node --run`, `npx` and local tools
7. How Vite and the Angular CLI use Node.js
8. Package safety with `npm audit`
9. Package entry points, and optional extras: workspaces, publishing, pnpm and Yarn

## npm Fundamentals

- **npm registry** = a huge online store of free packages at npmjs.com.
- **npm (the command)** = the program you type in the terminal, such as `npm install`.
- **Node.js** runs your code. **npm** manages the packages your code uses.
- Packages go into a folder called `node_modules` inside your project.

Never edit files inside `node_modules`. The next `npm install` replaces them.

<figure className="ngc-fig node-fig">
<svg viewBox="0 0 560 200" role="img" ariaLabelledby="fig-node-npm" style={{ width: "100%", maxWidth: 560, height: "auto" }}>
<title id="fig-node-npm">Animation: npm reads package.json, downloads a package from the registry into node_modules, and writes package-lock.json</title>
<rect x="20" y="70" width="130" height="60" rx="10"  style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)", strokeWidth: 1.5 }}/>
<text x="85" y="97" textAnchor="middle" style={{ fill: "var(--ngc-orange)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>package.json</text>
<text x="85" y="117" textAnchor="middle" style={{ fill: "var(--ink)", fontFamily: "var(--mono)", fontSize: 11 }}>"dayjs": "^1.11.0"</text>
<rect x="215" y="70" width="130" height="60" rx="10"  style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)", strokeWidth: 1.5 }}/>
<text x="280" y="105" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>npm install</text>
<rect x="410" y="20" width="130" height="50" rx="10"  style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 1.5 }}/>
<text x="475" y="50" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>npm registry</text>
<rect x="410" y="130" width="130" height="50" rx="10"  style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 1.5 }}/>
<text x="475" y="160" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>node_modules</text>
<line x1="150" y1="100" x2="215" y2="100"  style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }}/>
<line x1="345" y1="90" x2="410" y2="50"  style={{ stroke: "var(--ngc-slate)", strokeWidth: 2, strokeDasharray: "4 4" }}/>
<line x1="345" y1="110" x2="410" y2="150"  style={{ stroke: "var(--ngc-slate)", strokeWidth: 2, strokeDasharray: "4 4" }}/>
<circle r="7" cx="150" cy="100" style={{ fill: "var(--ngc-orange)" }}>
<animate attributeName="cx" values="150;215;345;410;345;410" keyTimes="0;0.15;0.3;0.45;0.65;0.8" dur="6s" repeatCount="indefinite" />
<animate attributeName="cy" values="100;100;90;50;110;150" keyTimes="0;0.15;0.3;0.45;0.65;0.8" dur="6s" repeatCount="indefinite" />
</circle>
<text x="280" y="190" textAnchor="middle" style={{ fill: "var(--muted)", fontFamily: "var(--sans)", fontSize: 12 }}>npm also writes package-lock.json with the exact versions</text>
</svg>
<figcaption>package.json says what you want, npm fetches it, and node_modules holds it.</figcaption>
</figure>

**Simple lesson:** the registry stores packages, and npm copies them into your project.

## package.json in Depth

`npm init -y` makes a starter `package.json`.

```text
$ npm init -y
Wrote to /home/asha/school/package.json:

{
  "name": "school",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```

The main fields:

- **`name`** and **`version`** = who the package is and which edition.
- **`description`**, **`author`**, **`license`** = facts for other people.
- **`main`** = the entry file when someone loads your package.
- **`type`** = `"module"` makes `.js` files ES modules.
- **`scripts`** = short names for commands, such as `"start": "node app.js"`.
- **`engines`** = which Node.js versions the project needs, such as `{ "node": ">=22" }`.
- **`private: true`** = blocks `npm publish`, so a school project is never published by mistake.
- **`files`** = which files go into the package when you publish it.

**Simple lesson:** `package.json` is the ID card and the command list of your project.

## Dependencies

**Dependency** = a package your project needs.

- **`dependencies`** = needed when the app runs, such as `express`.
- **`devDependencies`** = needed only while you build and test, such as `typescript`.
- **`peerDependencies`** = a package your package expects the user to install, such as a plugin that needs `react`.
- **`optionalDependencies`** = nice to have. Install does not fail without them.

```bash
npm install dayjs              # add to dependencies
npm install -D typescript      # add to devDependencies
npm install dayjs@1.11.10      # an exact version
npm install -g nodemon         # global: a tool for every project
npm uninstall dayjs            # remove it
```

- **Local install** = goes into this project's `node_modules`. Use this almost always.
- **Global install** (`-g`) = goes into one shared place on your computer. Use it only for command-line tools.

### Checking Your Packages

- `npm ls` = show the installed packages as a tree.
- `npm outdated` = show packages that have newer versions.
- `npm update` = update packages inside the allowed version range.

`package.json`

```json
{
  "name": "school-report",
  "version": "1.0.0",
  "private": true
}
```

```text
$ npm ls
school-report@1.0.0 /home/asha/school
`-- (empty)
```

**Simple lesson:** app needs go in `dependencies`, tool needs go in `devDependencies`.

## Versioning

**Semantic versioning (SemVer)** = a version number in three parts: `MAJOR.MINOR.PATCH`.

- **PATCH** (1.4.**2** → 1.4.**3**) = a bug fix. Nothing breaks.
- **MINOR** (1.**4**.2 → 1.**5**.0) = a new feature. Old code still works.
- **MAJOR** (**1**.4.2 → **2**.0.0) = a breaking change. Old code may stop working.
- **Prerelease** (`2.0.0-beta.1`) = a test version before the real release.

### Version Ranges

<div className="tscroll">

| Range | Means | Allows |
|---|---|---|
| `1.4.2` | Exactly this | only 1.4.2 |
| `^1.4.2` | Same major | 1.4.2 up to below 2.0.0 |
| `~1.4.2` | Same minor | 1.4.2 up to below 1.5.0 |
| `>=1.4.2` | This or newer | anything from 1.4.2 |
| `*` | Any version | everything (risky) |

</div>

`npm install` writes `^` by default. You get fixes and features, but no breaking changes.

Commit `package-lock.json` to Git. It makes every install on every computer the same.

This is a small copy of what npm does. The real rules also handle `0.x` versions and prereleases.

**Simple lesson:** read a version as major.minor.patch, and `^` never jumps to a new major.

<figure className="ngc-fig node-fig">
<svg viewBox="0 0 560 200" role="img" ariaLabelledby="fig-node-semver" style={{ width: "100%", maxWidth: 560, height: "auto" }}>
<title id="fig-node-semver">Which versions ^1.4.2 and ~1.4.2 accept</title>
<text x="20" y="30" style={{ fill: "var(--ink)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>Version 1.4.2 = MAJOR . MINOR . PATCH</text>
<rect x="20" y="50" width="110" height="34" rx="8"  style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)" }}/><text x="75" y="72" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>1.4.2</text>
<rect x="150" y="50" width="110" height="34" rx="8"  style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)" }}/><text x="205" y="72" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>1.4.9</text>
<rect x="280" y="50" width="110" height="34" rx="8"  style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)" }}/><text x="335" y="72" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>1.7.0</text>
<rect x="410" y="50" width="130" height="34" rx="8"  style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)" }}/><text x="475" y="72" textAnchor="middle" style={{ fill: "var(--ngc-red)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>2.0.0</text>
<text x="20" y="118" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>^1.4.2</text><text x="90" y="118" style={{ fill: "var(--ink)", fontFamily: "var(--sans)", fontSize: 13 }}>accepts 1.4.2, 1.4.9 and 1.7.0. Never 2.0.0.</text>
<text x="20" y="146" style={{ fill: "var(--ngc-orange)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>~1.4.2</text><text x="90" y="146" style={{ fill: "var(--ink)", fontFamily: "var(--sans)", fontSize: 13 }}>accepts 1.4.2 and 1.4.9 only.</text>
<text x="20" y="176" style={{ fill: "var(--ink)", fontFamily: "var(--sans)", fontSize: 12 }}>A new MAJOR number means breaking changes, so npm never jumps to it on its own.</text>
<rect x="18" y="98" width="524" height="28" rx="6" opacity="0" style={{ fill: "none", stroke: "var(--ngc-purple)", strokeWidth: 1.5 }}><animate attributeName="opacity" values="0;1;1;0;0" keyTimes="0;0.1;0.45;0.5;1" dur="6s" repeatCount="indefinite" /></rect>
<rect x="18" y="126" width="524" height="28" rx="6" opacity="0" style={{ fill: "none", stroke: "var(--ngc-orange)", strokeWidth: 1.5 }}><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.5;0.6;0.95;1" dur="6s" repeatCount="indefinite" /></rect>
</svg>
<figcaption>^ allows new minor and patch versions. ~ allows only new patch versions.</figcaption>
</figure>

## Lockfiles

**package-lock.json** = the exact version of every installed package, including packages of packages.

- `package.json` says `^1.11.0`. That is a range.
- `package-lock.json` says `1.11.13`. That is the exact copy.
- Commit the lockfile to Git. Then everyone installs the same versions.

**`npm install` vs `npm ci`:**

- `npm install` = install and update the lockfile when needed. Use it while you work.
- `npm ci` = delete `node_modules` and install exactly what the lockfile says. It fails if the lockfile does not match `package.json`. Use it on build servers.

**Simple lesson:** package.json allows a range, the lockfile pins one exact version.

## npm Scripts

**npm script** = a named command in the `"scripts"` part of `package.json`.

- `npm run report` runs the `"report"` script.
- `npm start` and `npm test` are short forms for two common names.
- Words after `--` are passed to the script: `npm run greet -- Asha`.
- npm adds `node_modules/.bin` to the path, so installed tools run by name.
- npm sets variables such as `npm_package_name` for the script.
- `node --run report` runs a script faster, without npm's extra work.

Click **▶ Run**. The lab runs `npm run greet -- Meena`.

- **The banner:** npm prints the package name, version and script name first.
- **The argument:** `Meena` arrived in `process.argv[2]`, because it came after `--`.
- **The variable:** `npm_package_name` was filled in by npm.

### Lifecycle Scripts: pre and post

- A script named `prebuild` runs before `build`. A script named `postbuild` runs after it.
- `npm install` runs a package's `install` and `postinstall` scripts. This is why you should trust a package before installing it.

`package.json`

```json
{
  "name": "school-report",
  "version": "1.0.0",
  "scripts": {
    "prebuild": "echo 1. cleaning old files",
    "build": "echo 2. building the report",
    "postbuild": "echo 3. done"
  }
}
```

```text
$ npm run build

> school-report@1.0.0 prebuild
> echo 1. cleaning old files

1. cleaning old files

> school-report@1.0.0 build
> echo 2. building the report

2. building the report

> school-report@1.0.0 postbuild
> echo 3. done

3. done
```

### Environment Variables in Scripts

- `"start": "NODE_ENV=production node app.js"` sets a variable for one command.
- That form works on Mac and Linux. On Windows Command Prompt it does not.
- For all systems, use `node --env-file=.env app.js`, or the `cross-env` package.

**Simple lesson:** scripts give long commands short names, and `--` passes your own words to them.

## Running Packages: npx and Local Tools

- Installed tools put their commands in `node_modules/.bin`.
- **`npx <tool>`** = run a tool from `node_modules/.bin`. If it is missing, npx offers to download it and run it once.
- Example: `npx tsc --version` runs the TypeScript compiler of this project.
- **Corepack** = a helper that sets up pnpm or Yarn. It came with older Node.js versions. Newer versions need `npm install -g corepack`.

### How Vite and the Angular CLI Use Node.js

The React, Vue and Angular courses start with commands like `npm run dev`. Here is what happens.

- `npm create vite@latest` downloads the Vite starter package and runs it with Node.js.
- `npm run dev` runs the `"dev"` script, which starts Vite, a Node.js program.
- Vite reads your files with `fs`, builds them and serves them on `localhost` with `http`.
- `ng serve` in Angular works the same way: the Angular CLI is a Node.js program.
- In browser code, Vite gives you `import.meta.env`. In Node.js code, you use `process.env`.

**Simple lesson:** frontend tools are Node.js programs, and npm scripts start them.

## Package Safety

A package runs on your computer with your rights. Check it first.

- **`npm audit`** = checks your installed packages against a list of known security problems.
- **`npm audit fix`** = updates packages to fixed versions, inside your version ranges.
- **Before installing,** check the weekly downloads, the last update date and the GitHub page.
- **Watch for look-alike names,** such as `expres` instead of `express`. This trick is called **typosquatting**.
- **Read `postinstall` scripts** of small, unknown packages. They run during install.

```text
$ npm audit
# npm audit report

minimist  <1.2.6
Severity: critical
Prototype Pollution in minimist
fix available via `npm audit fix`

1 critical severity vulnerability
```

This output is an example. Your report depends on your packages.

`npm audit fix --force` may install breaking major versions. Read what it changes, and run your tests after.

**Simple lesson:** treat a new package like a stranger's USB stick. Check it before you plug it in.

## Package Entry Points (Advanced)

**`exports` field** = the list of files other people may import from your package.

- Without `exports`, anyone can import any file inside the package.
- With `exports`, only the listed paths work. Others get `ERR_PACKAGE_PATH_NOT_EXPORTED`.
- **Conditional exports** = give `import` users one file and `require` users another.
- **Subpath imports** (`#`) = short private names inside your own package, set in `"imports"`.

- **`"."` with `import` and `require`:** this is a **dual package**. It serves both module systems.
- **`"./grades"`:** a public subpath.
- **`secret.js`:** the file exists, but `exports` does not list it, so Node.js blocks it.

**Simple lesson:** `exports` is the front gate of your package. Only listed paths get in.

## Workspaces (Optional)

**Workspaces** = several packages in one repository, managed by one `npm install`.

- Add `"workspaces": ["packages/*"]` to the root `package.json`.
- Shared dependencies are installed once, in the root `node_modules`.
- `npm run test --workspaces` runs `test` in every package.
- `npm run build -w packages/reports` runs it in one package.

## Publishing (Optional)

1. Remove `"private": true` and pick a unique `name`.
2. List the files to share in `"files"`.
3. Run `npm pack --dry-run` to see what would be uploaded.
4. Run `npm version patch` to raise the version number.
5. Run `npm publish` (after `npm login`).

`package.json`

```json
{
  "name": "@greenfield/grades",
  "version": "1.0.0",
  "main": "index.js",
  "files": ["index.js"]
}
```

`index.js`

```js
module.exports.grade = (m) => (m >= 90 ? "A" : "B");
```

`notes.txt`

```js
These notes stay private.
```

```text
$ npm pack --dry-run
npm notice Tarball Contents
npm notice 53B index.js
npm notice 104B package.json
npm notice total files: 2
```

- `notes.txt` is not in the list, because `"files"` only allows `index.js`.
- **Scoped package** = a name like `@greenfield/grades`. The scope groups a team's packages.
- `npm deprecate <name>@<version> "message"` warns people away from an old version.

## Other Package Managers (Optional)

- **pnpm** = saves disk space by storing each package version once and linking it. Fast installs.
- **Yarn** = another package manager with its own lockfile, `yarn.lock`.
- Both read the same `package.json`. Use the one your team already uses.
- Do not mix them in one project. Each writes its own lockfile.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Not committing `package-lock.json` | The lockfile is temporary | Commit it | Teammates get different versions |
| Editing files in `node_modules` | It is my code now | Change your own code, or fork the package | The next install wipes your change |
| Putting `typescript` in `dependencies` | All packages are the same | Build tools go in `devDependencies` | Production installs carry extra weight |
| `npm run greet Asha` | npm passes words to the script | Use `npm run greet -- Asha` | npm may treat the word as its own option |
| Installing project tools with `-g` | Global is easier | Install locally and use `npx` | Different projects need different versions |
| Installing a package with a look-alike name | Names are checked by npm | Check the exact name and its downloads | A harmful package may run on install |
| Using `npm install` on a build server | Both commands are the same | Use `npm ci` | Builds may get different versions |

</div>

## TL;DR

1. **npm** = the package tool. **Registry** = the online store of packages.
2. **package.json** = name, version, type, scripts, engines and dependencies.
3. **dependencies** = needed to run. **devDependencies** = needed to build and test.
4. **SemVer** = MAJOR.MINOR.PATCH.
   - `^` same major, `~` same minor
5. **package-lock.json** = exact versions. Commit it. Use `npm ci` on servers.
6. **npm scripts** = named commands. `--` passes your own arguments.
7. **npx** = run a tool from `node_modules/.bin`.
8. **npm audit** = check packages for known security problems.
9. **exports** = the list of paths a package allows.

## Check Yourself

## Exercises

## What to Remember

- npm downloads packages from the registry into `node_modules`.
- `package.json` lists what you need. `package-lock.json` pins exact versions.
- `^` allows new minor and patch versions, never a new major.
- Use `npm ci` on servers, and `npm run name -- words` to pass arguments.
- Check a package before installing it, and run `npm audit`.
- `exports` decides which files of a package others may import.

Next: **Event Loop and Timers**. You will see how Node.js keeps working while slow jobs finish.
