The "scripts" field in package.json is one of the most powerful and underused features of npm. It lets you define short, memorable command aliases for the long shell commands you run every day — starting servers, running tests, building for production, linting code, and seeding databases. npm scripts run in the context of your project’s node_modules/.bin, which means you can use locally installed CLI tools (like nodemon, jest, and eslint) directly in scripts without installing them globally. In MERN development, a well-designed set of npm scripts becomes the standard interface everyone on the team uses to interact with the project.
How npm Scripts Work
# Running a script
npm run script-name
# Special scripts that have shorthand (no "run" needed)
npm start # runs "start" script
npm test # runs "test" script
npm stop # runs "stop" script
# All other custom scripts need "run"
npm run dev
npm run lint
npm run build
npm run seed
node_modules/.bin to the PATH when running scripts. This means you can call locally installed CLI tools directly in scripts without their full path. For example, if nodemon is installed locally in your project, "dev": "nodemon index.js" works — you do not need ./node_modules/.bin/nodemon index.js or a global install.pre and post prefix hooks to automatically run scripts before or after another script. Define "prestart" to run before "start", or "posttest" to run after "test". For example: "prebuild": "npm run lint" automatically lints your code before every build — failing the build if lint errors exist.&&, ||, environment variable syntax) may not work on Windows. For cross-platform scripts, use the cross-env package for environment variables and rimraf instead of rm -rf.Server Scripts (server/package.json)
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest --runInBand --detectOpenHandles",
"test:watch": "jest --watch",
"lint": "eslint src/ --ext .js",
"lint:fix": "eslint src/ --ext .js --fix",
"seed": "node src/scripts/seedDatabase.js",
"seed:clear": "node src/scripts/clearDatabase.js"
}
}
Client Scripts (client/package.json)
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives",
"lint:fix": "eslint . --ext js,jsx --fix"
}
}
Root Scripts — Run Both Servers Together (root package.json)
{
"scripts": {
"dev": "concurrently -n SERVER,CLIENT -c blue,green \"npm run server\" \"npm run client\"",
"server": "cd server && npm run dev",
"client": "cd client && npm run dev",
"build": "cd client && npm run build",
"test": "cd server && npm test",
"lint": "concurrently \"cd server && npm run lint\" \"cd client && npm run lint\"",
"install:all": "npm install && cd server && npm install && cd ../client && npm install"
},
"devDependencies": {
"concurrently": "^8.2.2"
}
}
Practical Script Patterns
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"prebuild": "npm run lint",
"build": "echo 'Server has no build step'",
"db:seed": "node src/scripts/seed.js",
"db:clear": "node src/scripts/clear.js",
"db:reset": "npm run db:clear && npm run db:seed"
}
}
Chaining Scripts
{
"scripts": {
"lint": "eslint src/",
"test": "jest",
"check": "npm run lint && npm run test",
"db:clear": "node scripts/clear.js",
"db:seed": "node scripts/seed.js",
"db:reset": "npm run db:clear && npm run db:seed"
}
}
Chaining operators in npm scripts:
&& runs next command only if previous succeeded (exit code 0)
|| runs next command only if previous failed
; always runs both commands regardless of exit code
| pipes output from one command to another
Example:
"check": "npm run lint && npm run test"
→ Runs lint first. If lint fails (any errors), test is skipped.
→ Both must pass for the check script to succeed.
→ CI pipelines use this pattern to gate deployments.
Using Environment Variables in Scripts
{
"scripts": {
"start": "NODE_ENV=production node index.js",
"dev": "NODE_ENV=development nodemon index.js",
"test": "NODE_ENV=test jest"
}
}
# For cross-platform compatibility (works on Windows too) use cross-env:
npm install -D cross-env
{
"scripts": {
"start": "cross-env NODE_ENV=production node index.js",
"dev": "cross-env NODE_ENV=development nodemon index.js",
"test": "cross-env NODE_ENV=test jest"
}
}
Common Mistakes
Mistake 1 — Using globally installed tools in scripts
❌ Wrong — script relies on a globally installed package that other developers may not have:
"scripts": {
"dev": "nodemon index.js" // assumes nodemon is installed globally
}
// Another developer clones the repo, nodemon is not global → script fails
✅ Correct — install the tool as a local devDependency so it is always available via node_modules/.bin:
npm install -D nodemon # now available locally to all scripts ✓
Mistake 2 — Duplicating long commands everywhere instead of using scripts
❌ Wrong — every developer manually types the full command:
NODE_ENV=development nodemon --watch src --ext js,json index.js
# Different developers use slightly different flags — inconsistent behaviour
✅ Correct — define it once in package.json and everyone uses the same command:
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon --watch src --ext js,json index.js"
}
Mistake 3 — Not testing the start script before deploying
❌ Wrong — only ever running npm run dev locally and assuming npm start (used by Render/Heroku) works the same:
npm run dev → nodemon index.js → works
npm start → "start" script not defined → "Missing script: start" error on deployment
✅ Correct — always define and test both start (production) and dev (development) scripts:
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
Quick Reference
| Script | Command to run | Purpose |
|---|---|---|
| start | npm start |
Production — plain Node.js |
| dev | npm run dev |
Development — nodemon with auto-restart |
| test | npm test |
Run Jest test suite |
| lint | npm run lint |
Check code for ESLint errors |
| lint:fix | npm run lint:fix |
Auto-fix ESLint errors |
| build | npm run build |
Build React app for production |
| seed | npm run seed |
Populate database with test data |
| install:all | npm run install:all |
Install deps for all sub-projects at once |