One of the biggest reasons for Node's success is npm, the Node Package Manager. It's installed automatically with Node and gives you access to the npm registry, the largest collection of open-source packages in the world: web frameworks, database drivers, date libraries, testing tools and much more. Instead of writing everything from scratch, you install a well-tested package with a single command.
Every Node project is described by a package.json file. It records the project's name and version, the packages it depends on, and scripts for common tasks such as starting the server or running tests. Anyone who clones your project runs npm install and gets exactly the packages it needs.
In this lesson you'll create a package.json, install and remove packages, understand the difference between dependencies and development dependencies, read version ranges like ^4.21.0, see why the lock file matters, write npm scripts, and run tools with npx. You'll also learn how to judge whether a package is safe to depend on.
Creating a project and installing packages
mkdir shop-api && cd shop-api
npm init -y # create package.json with defaults
npm install express # add a dependency
npm install --save-dev vitest # add a development-only dependency
npm uninstall express # remove it again- Packages are downloaded into the
node_modulesfolder, which can contain thousands of files. Never edit it, and add it to.gitignoreso it isn't committed. dependenciesare needed to run the app in production;devDependenciesare only needed while developing, such as test runners, linters and build tools.npm installwith no package name installs everything listed inpackage.json.
package.json and scripts
{
"name": "shop-api",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node src/server.js",
"dev": "node --watch src/server.js",
"test": "vitest run"
},
"dependencies": { "express": "^5.1.0" },
"devDependencies": { "vitest": "^3.2.0" }
}- Run scripts with
npm run dev.npm startandnpm testwork withoutrun. - Scripts can use the tools in
node_modules/.bindirectly, sovitestworks even though it isn't installed globally. "type": "module"makes.jsfiles ES modules, as covered in the modules lesson.
Versions and the lock file
npm packages follow semantic versioning: MAJOR.MINOR.PATCH, such as 5.1.0.
- PATCH releases fix bugs. MINOR releases add features without breaking anything. MAJOR releases may contain breaking changes.
^5.1.0allows any5.x.xversion from 5.1.0 up, but not 6.0.0.~5.1.0allows only patch updates:5.1.x.
package-lock.json records the exact version of every installed package, including the packages your packages depend on. Commit it. On servers and in CI, use npm ci, which installs exactly what the lock file says, giving everyone identical installs.
npx and useful commands
npxruns a package's command without installing it permanently:npx create-vite@latest my-app.npm outdatedlists packages with newer versions;npm updateupdates within the allowed ranges.npm auditchecks your packages against a database of known security problems.npm list --depth=0shows what's installed at the top level.- Global installs (
npm install -g) are for command-line tools you use everywhere; project libraries should always be local.
Passing arguments to scripts and pre and post hooks
Scripts often need an extra flag now and then. Anything you type after a double dash is passed straight through to the command the script runs:
npm test -- --watch # runs "vitest run --watch"
npm run dev -- --port 4000Without the --, npm would try to read --watch as one of its own options.
npm also looks for scripts named after another script with pre or post in front. If package.json has pretest and posttest, then npm test runs pretest, then test, then posttest, stopping if any of them fails. Teams use this for small chores, such as clearing an output folder before a build, but too many hidden hooks make a project hard to follow, so keep them rare.
Typing npm run with no script name lists every script in the project, which is a quick way to learn how an unfamiliar repository is meant to be used.
Node.js Basics Syntax
npm commands
npm init -y
npm install express
npm install -D vitest # same as --save-dev
npm uninstall lodash
npm install # everything in package.json
npm ci # exact versions from the lock file
npm run dev
npm test
npx eslint .
npm outdated
npm auditNode.js Basics Summary
- npm installs packages from the npm registry into node_modules.
- package.json lists dependencies, devDependencies and scripts.
- Run scripts with npm run name; npm start and npm test are shortcuts.
- Semantic versions are MAJOR.MINOR.PATCH; ^ allows minor and patch updates.
- Commit package-lock.json and use npm ci for exact, repeatable installs.
Node.js Basics Examples
Each example shows the code, the output it produces and what each part does.
Reading package.json from code
import { readFile, writeFile } from 'node:fs/promises';
const pkg = {
name: 'shop-api',
version: '1.4.2',
scripts: { start: 'node server.js', test: 'vitest run' },
dependencies: { express: '^5.1.0', zod: '^3.24.0' },
devDependencies: { vitest: '^3.2.0' },
};
await writeFile('package-demo.json', JSON.stringify(pkg, null, 2));
const loaded = JSON.parse(await readFile('package-demo.json', 'utf8'));
console.log(`${loaded.name} v${loaded.version}`);
console.log('Scripts:', Object.keys(loaded.scripts).join(', '));
console.log('Runtime packages:', Object.keys(loaded.dependencies).length);
console.log('Dev packages:', Object.keys(loaded.devDependencies ?? {}).join(', '));shop-api v1.4.2
Scripts: start, test
Runtime packages: 2
Dev packages: vitestLine-by-line explanation
- Lines 3 to 9A typical package.json written out for the demo.
- Line 10JSON.stringify with 2 adds indentation, as npm does.
- Line 12package.json is plain JSON, so JSON.parse reads it.
- Line 16?? {} handles a project with no devDependencies.
How version ranges are matched
function parse(v) {
return v.split('.').map(Number);
}
function satisfies(version, range) {
const [maj, min, pat] = parse(version);
const [rMaj, rMin, rPat] = parse(range.slice(1));
const atLeast = maj > rMaj || (maj === rMaj && (min > rMin || (min === rMin && pat >= rPat)));
if (range.startsWith('^')) return atLeast && maj === rMaj;
if (range.startsWith('~')) return atLeast && maj === rMaj && min === rMin;
return version === range;
}
for (const v of ['5.1.0', '5.3.7', '5.1.9', '6.0.0', '5.0.9']) {
console.log(`${v.padEnd(6)} ^5.1.0: ${satisfies(v, '^5.1.0')} ~5.1.0: ${satisfies(v, '~5.1.0')}`);
}5.1.0 ^5.1.0: true ~5.1.0: true
5.3.7 ^5.1.0: true ~5.1.0: false
5.1.9 ^5.1.0: true ~5.1.0: true
6.0.0 ^5.1.0: false ~5.1.0: false
5.0.9 ^5.1.0: false ~5.1.0: falseLine-by-line explanation
- Lines 1 to 3A version string becomes three numbers.
- Line 8The version must be at least the one in the range.
- Line 9^ keeps the same major version, allowing new features and fixes.
- Line 10~ keeps the same major and minor version, allowing only fixes.
- Lines 14 to 166.0.0 is a major release, so neither range accepts it. Real projects use the semver package rather than this simplified version.
A project's everyday commands
npm init -y
npm install express
npm install -D vitest
npm pkg set scripts.dev="node --watch server.js"
npm run devWrote to /projects/shop-api/package.json
added 66 packages, and audited 67 packages in 3s
added 39 packages, and audited 106 packages in 4s
> shop-api@1.0.0 dev
> node --watch server.js
Server listening on http://localhost:3000Line-by-line explanation
- Line 1-y accepts the default answers, creating package.json straight away.
- Line 2express and everything it depends on is installed into node_modules.
- Line 3-D records vitest under devDependencies.
- Line 4npm pkg set edits package.json from the command line.
- Line 5npm run prints the script it's running, then runs it. Package counts vary with versions.
Common Node.js Basics Mistakes
Committing node_modules
git add . # thousands of files from node_modules end up in the repositoryecho "node_modules/" >> .gitignore
git add .Installing a project library globally
npm install -g express # the project's package.json doesn't record itnpm install expressUsing npm install on the server
npm install # may resolve slightly different versionsnpm ci # exactly what package-lock.json saysNode.js Basics in Real Projects
npm in professional teams
Teams commit package.json and package-lock.json, run npm ci in continuous integration, and keep scripts like lint, test and build so every developer runs the same commands. Tools such as Dependabot and Renovate open pull requests when packages have updates, and npm audit runs in CI to catch known vulnerabilities. Many companies also use pnpm or Yarn, which work with the same package.json format.
Node.js Basics FAQ
What is the difference between npm install and npm ci?
npm install reads package.json, may update package-lock.json when ranges allow newer versions, and leaves existing node_modules in place. npm ci deletes node_modules, installs exactly what the lock file records, never changes the lock file, and fails if the lock file and package.json disagree. Use install while developing and ci on servers and in CI.
How do I install a specific version of an npm package?
Add the version after an @: npm install express@4.21.2 for an exact version, or npm install express@4 for the newest 4.x release. npm records a range such as ^4.21.2 in package.json by default; add --save-exact (or -E) if you want the exact number written instead.
Should I delete package-lock.json to fix installation problems?
Usually not. Deleting it throws away the exact versions your project was tested with, and the next install may pull in different versions of hundreds of packages at once. Try deleting just node_modules and running npm ci first; if a single package needs updating, run npm install that-package@version so only its entry changes.
What is the difference between npm and npx?
npm manages packages: it installs, removes and updates them and runs the scripts in package.json. npx runs a package's command-line tool, using the copy in your project's node_modules/.bin if there is one and downloading it temporarily if not. For example, npx eslint . runs the project's ESLint without a global install.
Why does npm audit report vulnerabilities in packages I never installed?
Most of the packages in node_modules are dependencies of your dependencies, and npm audit checks all of them. A report doesn't always mean your app is exploitable, because the vulnerable code may never run in the way you use it. Run npm audit fix to apply compatible updates, and look at whether a newer version of the top-level package removes the problem before forcing major upgrades.
Node.js Basics Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the command that runs the "dev" script.
npm dev
Which file lists a project's dependencies and scripts?
Which versions does "^2.4.1" allow?
Where should a test runner such as Vitest be installed?
Why use npm ci instead of npm install in continuous integration?
Node.js Basics Quiz
5 questions to check what you learned.
What does npx do?
- Deletes packages
- Runs a package's command without a permanent install
- Publishes a package
- Updates npm
In version 3.8.2, what does the 8 represent?
- Major
- Patch
- Minor
- Build
Should package-lock.json be committed to Git?
- No
- Only for libraries
- Only on Windows
- Yes
Which command checks installed packages for known security problems?
- npm check
- npm secure
- npm verify
- npm audit
Which scripts can be run without the word run?
- Any script
- start and test
- dev and build
- None