Difficulty: Beginner
npm (Node Package Manager) is the default package manager for Node.js and the world's largest software registry with over two million packages. It consists of three components: the npm CLI tool (installed with Node.js), the online registry (npmjs.com) where packages are published, and the website for browsing and searching packages. Understanding npm is essential because virtually every Node.js project depends on third-party packages.
Every Node.js project starts with a package.json file, which is the project's manifest. You create one by running 'npm init' (interactive) or 'npm init -y' (accepts all defaults). package.json contains metadata like the project name, version, description, entry point, and most importantly, the dependencies your project needs. It also defines scripts that automate common tasks like starting the server, running tests, or building the project.
npm install (or npm i) is the command you use most frequently. Running 'npm install express' adds Express to your dependencies and downloads it into the node_modules directory. The --save-dev (or -D) flag installs a package as a devDependency, meaning it is only needed during development, not in production. Typical devDependencies include testing frameworks (jest, mocha), linters (eslint), formatters (prettier), and build tools (typescript, webpack). Running 'npm install' with no arguments installs all dependencies listed in package.json.
The package-lock.json file is automatically generated and tracks the exact version of every installed package and its transitive dependencies. This ensures that everyone on your team (and your CI/CD pipeline) installs the exact same dependency tree, preventing the 'works on my machine' problem. You should always commit package-lock.json to version control. Never edit it manually. If you need a clean install that strictly follows the lock file, use 'npm ci' instead of 'npm install' - it is faster, stricter, and is the command you should use in CI/CD pipelines.
npm scripts are custom commands defined in the 'scripts' section of package.json. You run them with 'npm run <scriptName>'. Special script names like 'start', 'test', 'preinstall', and 'postinstall' can be run without the 'run' keyword (just 'npm start' or 'npm test'). Scripts are powerful because they let you standardize common operations across the team. Pre and post hooks (prefixed with 'pre' and 'post') run automatically before and after a script - for example, 'pretest' runs before 'test'.
// Terminal commands (not JavaScript):
// Create package.json with defaults
// npm init -y
// Install production dependencies
// npm install express cors dotenv
// Install dev dependencies
// npm install -D typescript @types/node jest
// Install a specific version
// npm install express@4.18.2
// Install globally (available system-wide)
// npm install -g nodemon
// View installed packages
// npm list --depth=0
// Simulating what a package.json looks like after installs:
const packageJson = {
name: "my-api",
version: "1.0.0",
scripts: {
start: "node src/index.js",
dev: "node --watch src/index.js",
test: "jest"
},
dependencies: {
cors: "^2.8.5",
dotenv: "^16.3.1",
express: "^4.18.2"
},
devDependencies: {
"@types/node": "^20.11.0",
jest: "^29.7.0",
typescript: "^5.3.3"
}
};
console.log(JSON.stringify(packageJson, null, 2));
dependencies are required in production. devDependencies are only needed during development. The ^ prefix means compatible updates are allowed (e.g., ^4.18.2 allows 4.19.0 but not 5.0.0).
// package.json scripts section:
const scripts = {
// Special scripts (run without 'run' keyword)
"start": "node dist/index.js",
"test": "jest --coverage",
// Custom scripts (need 'npm run <name>')
"dev": "node --watch src/index.js",
"build": "tsc",
"lint": "eslint src/",
"format": "prettier --write src/",
// Lifecycle hooks
"pretest": "npm run lint",
"postbuild": "echo Build complete!",
// Chaining commands
"ci": "npm run lint && npm test && npm run build"
};
console.log('Run with: npm start, npm test, npm run dev');
console.log('pretest runs automatically before test');
console.log('postbuild runs automatically after build');
console.log('ci runs lint, test, and build in sequence');
npm scripts standardize project commands. 'start' and 'test' are special and can be run as 'npm start' and 'npm test'. All others need 'npm run'. The && operator chains commands so the next one only runs if the previous succeeded.
// npx runs a package without installing it globally
// Create a new project:
// npx create-react-app my-app
// npx create-next-app@latest my-app
// Run a one-off tool:
// npx eslint --init
// npx tsc --init
// npx json-server db.json
// Run a specific version:
// npx node@18 --version
// npx typescript@5.0 tsc --version
// npx first checks local node_modules/.bin,
// then global, then downloads temporarily
console.log('npx checks: local -> global -> download');
console.log('Great for project scaffolding and one-off tools');
npx is bundled with npm 5.2+ and solves the problem of installing tools globally just to use them once. It checks the local node_modules first, making it ideal for running project-local binaries like eslint or tsc.
npm init, npm install, npm scripts, devDependencies, Lock Files, npx