Every Node.js project ends up with a handful of commands you type over and over: start the server, run tests, build for production. npm scripts let you store those commands in one place so anyone joining your project can hit the ground running with a simple npm start instead of hunting through a README for the right incantation.
Defining and running scripts
Scripts live in the "scripts" field of your package.json. The key is the name you'll call, and the value is the shell command that runs.
{
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js",
"test": "node --test",
"build": "echo 'Building...'"
}
}To run them:
npm start # No 'run' needed for start, test, stop, restart
npm test # Same shortcut
npm run dev # Custom scripts need 'run'
npm run buildstart, test, stop, and restart, that get special treatment and skip the run keyword. Every other name requires npm run.Building a real script setup
A realistic project has more than a start command. Here is what a typical Node.js + TypeScript project looks like:
{
"scripts": {
"start": "node dist/index.js",
"dev": "nodemon src/index.js",
"build": "npm run clean && npm run compile",
"clean": "rm -rf dist",
"compile": "tsc",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write src/",
"db:migrate": "prisma migrate dev",
"db:seed": "node scripts/seed.js"
}
}Notice the naming patterns: test:watch and test:coverage group related scripts under a shared prefix separated by a colon. npm does not treat these specially, the colon is just a convention, but it keeps things readable.
Pre and post hooks
npm automatically looks for scripts named pre[name] and post[name] and runs them before and after the target script. You do not need to call them yourself.
{
"scripts": {
"prestart": "npm run build",
"start": "node dist/index.js",
"poststart": "echo 'Server is up!'"
}
}Running npm start now executes three steps in order:
# 1. prestart runs first
# 2. start runs
# 3. poststart runs lastSome hooks that apply at the package level (not just your custom scripts) are worth knowing:
| Hook | When it runs |
|---|---|
preinstall / postinstall | Before / after npm install |
prepare | After install and before publish |
prepublishOnly | Right before npm publish |
version | After a version bump |
postversion | After the version commit is made |
Passing arguments and environment variables
Sometimes you want to tweak a script without making a whole new entry. You can forward extra flags using --:
npm run test -- --verbose
npm run test -- --testPathPattern user.test.js
npm start -- --port 8080For environment variables, the syntax differs between Unix and Windows. On macOS/Linux you can write:
{
"scripts": {
"dev": "NODE_ENV=development node src/index.js"
}
}On Windows this breaks. The fix is cross-env, which normalises the syntax across platforms:
npm install --save-dev cross-env{
"scripts": {
"dev": "cross-env NODE_ENV=development node src/index.js",
"prod": "cross-env NODE_ENV=production node dist/index.js"
}
}cross-env in open-source packages or team projects where developers might be on Windows. It is a tiny dependency that prevents a frustrating class of "it works on my machine" bugs.Running scripts in parallel
By default, when you chain two scripts with &&, the second one only starts after the first finishes. That is fine for builds, but terrible for development where you want an APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. server and a frontend watcher running at the same time.
npm-run-all gives you explicit control:
npm install --save-dev npm-run-all{
"scripts": {
"start:api": "node api.js",
"start:web": "node web.js",
"dev": "npm-run-all --parallel start:api start:web",
"build": "npm-run-all --sequential clean compile"
}
}Quick reference
| Pattern | Example | What it does |
|---|---|---|
| Basic script | "dev": "nodemon src/index.js" | Runs a command |
| Chained scripts | "build": "npm run clean && tsc" | Runs sequentially |
| Pre/post hook | "pretest": "npm run lint" | Runs automatically |
| Pass arguments | npm run test -- --verbose | Forwards flags |
| Env variable | cross-env NODE_ENV=production | Works cross-platform |
| Parallel | npm-run-all --parallel a b | Runs simultaneously |
| Grouped names | test:unit, test:e2e | Convention only (colon) |