npm and package.json
Everything in this course so far has been written by you. That stops being sensible around the point you need date handling or a chart. npm is how you use somebody else's code — and how you take on somebody else's problems, which is the half that gets skipped.
Starting a project
npm init -y
{
"name": "tiffin-tracker",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "ISC",
"type": "commonjs"
}
package.json is the project's description: what it is, what it needs, and how
to run it. Change "type" to "module" so import works in Node, as the
last lesson said.
| Field | Is |
|---|---|
name, version |
Identity. version matters only if you publish |
type |
"module" for ES modules, "commonjs" for require |
main |
The entry point when someone imports your package |
scripts |
Named commands |
dependencies |
Needed to run |
devDependencies |
Needed only to build or test |
Installing
npm install dayjs
"dependencies": {
"dayjs": "^1.11.23"
}
Three things happened: the package was downloaded into node_modules, recorded
in package.json, and pinned exactly in package-lock.json.
For tools that never ship to users — formatters, test runners, bundlers:
npm install --save-dev prettier
The split matters because a deployment installs only dependencies with
npm ci --omit=dev. Put a build tool in the wrong list and you ship it to
production; put a runtime library in devDependencies and production breaks.
Version ranges
That ^ is not decoration.
Versions are MAJOR.MINOR.PATCH — semver:
| Change | Means |
|---|---|
PATCH 1.11.23 → 1.11.24 |
A bug fix. Nothing else should change |
MINOR 1.11.0 → 1.12.0 |
New features, still backwards compatible |
MAJOR 1.0.0 → 2.0.0 |
Something that worked will stop working |
| Range | Allows |
|---|---|
^1.11.23 |
Any 1.x.x from 1.11.23. The default |
~1.11.23 |
Any 1.11.x from 1.11.23 |
1.11.23 |
Exactly that |
* |
Anything. Never do this |
^ trusts the author to follow semver. Most do; some do not, and a "minor"
release occasionally breaks things. That is what the lockfile is for.
package-lock.json
package.json says "any 1.x". package-lock.json says "exactly 1.11.23,
and here are the exact versions of all seventy other packages that came with
it".
Commit it. Without it, two developers running npm install a week apart get
different code, and "works on my machine" becomes literally true.
| Command | Does |
|---|---|
npm install |
Installs, and may update the lockfile |
npm ci |
Installs exactly the lockfile. Deletes node_modules first |
Use npm ci in CI and for deployments. It is faster and it cannot drift.
Never commit node_modules. Add it to .gitignore; it is rebuildable and
enormous.
Scripts
"scripts": {
"dev": "vite",
"build": "vite build",
"format": "prettier --write .",
"test": "node --test"
}
npm run dev
npm test
npm run <name> for any script; test and start may drop the run.
Scripts can use installed tools by name — prettier resolves to
node_modules/.bin/prettier, so you do not need it installed globally and
everyone on the project gets the same version.
Scripts are the project's documented commands. A new person should be able to
read scripts and know how to run everything, which is worth more than a README
section that goes stale.
npx runs a package without installing it:
npx serve
Exactly what the last lesson used to serve a module page.
The cost of a dependency
The honest part, and the reason this lesson is not just a command list.
Installing two packages in an empty project — dayjs and express — gave me:
total installed packages: 70
direct dependencies: 2
node_modules: 15M
Two things I asked for, seventy things I got. That is normal, and each one is code you did not read, running with the same permissions as yours.
So, before adding a dependency:
Could you write it in twenty lines? A debounce, a slugify, a date
formatter you use once — Intl from module 6 does most date and money formatting
with nothing installed.
How many dependencies does it bring? Check on npm before installing.
Is it maintained? Last publish date, open issues, whether one person maintains it in their spare time.
What does it cost the user? A 200KB library on a mid-range Android on mobile
data is a real delay. bundlephobia.com gives you the number.
And then, when you do need one — dates beyond Intl, charts, a framework —
use it. Writing your own date library is a worse idea than any of this. The
point is that it is a decision with a cost, not a free convenience.
Security
npm audit
Lists known vulnerabilities in your tree. npm audit fix updates what it safely
can.
Two cautions, because this tool is widely over-trusted:
Not every report matters. A vulnerability in a build tool that never runs on a user's machine is not the same as one in your server. Read what it actually says.
npm audit fix --force installs major versions, which is a breaking change
dressed up as a security fix. Never run it on a Friday.
The real risk is quieter: a package you trust is taken over and a malicious
version published. Install scripts run automatically. That is why the lockfile
matters, why npm ci is safer than npm install, and why adding a dependency
for a twenty-line function is not free.
Where the browser fits
node_modules is for Node. A browser cannot import 'dayjs' — it has no idea
what that means:
import dayjs from 'dayjs';
TypeError: Failed to resolve module specifier 'dayjs'
Browsers resolve paths, not package names. Bridging that is what a bundler — Vite, esbuild, webpack — does: it reads your imports, finds the packages, and produces files a browser can load.
Bundlers are beyond this course. You now know exactly what problem they solve, which makes learning one an afternoon rather than a mystery.
Check your work
npm init -y creates package.json. Set "type": "module" for import.
dependencies are needed to run; devDependencies only to build or test, and
a deployment installs only the first.
^1.11.23 allows any 1.x from that version; ~ allows only patches; a bare
version is exact. ^ is the default and trusts the author to follow semver.
package.json states a range; package-lock.json pins exact versions.
Commit the lockfile. npm ci installs it exactly; npm install may change it.
Never commit node_modules.
Scripts can call installed tools by name, and are the project's documented
commands. npx runs a package without installing it.
Two direct dependencies installed seventy packages and 15MB. Every one is unread code with your permissions.
npm audit fix --force installs major versions and can break your project.
A browser cannot resolve import 'dayjs' — it resolves paths, not package
names. That is what a bundler is for.
Practice
npm init -yin an empty folder, read every field, and switch it to"type": "module".- Install a package and find the three places it now appears.
- Install something with
--save-devand confirm which list it lands in. - Count the cost. In an empty project, install
expressand count the entries inpackage-lock.jsonand the size ofnode_modules. - Delete
node_modulesand restore it withnpm ci. Time both that andnpm install. - Change a version to
~and work out which upgrades that now allows. - Add a
formatscript using a dev dependency and run it withnpm run. - Use
npx serveto serve the module page from the last lesson. - Run
npm auditand read one report properly — what is vulnerable, and would it ever run on a user's machine? - Try
import 'dayjs'in a browser withtype="module"and read the resolution error. That error is the entire reason bundlers exist. - Harder. Pick a small package you might reach for — a debounce, a slugify, a currency formatter. Read its source on GitHub, count its dependencies, and then write your own version. Compare them, and decide honestly which you would ship. For at least one of the three, the answer should be your own.
Next: debugging — breakpoints instead of console.log, and reading what the
browser is already telling you.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship