DEV Community

Alex Chen
Alex Chen

Posted on

npm Scripts and package.json: The Complete Guide (2026)

npm Scripts and package.json: The Complete Guide (2026)

Most developers only use npm start and npm install. Here's everything else you're missing.

Understanding package.json

{
  "name": "my-awesome-project",
  "version": "1.2.3",           // Semantic versioning (MAJOR.MINOR.PATCH)
  "description": "A brief description",
  "type": "module",             // ESM! Use "module" for import/export (Node 18+ default)

  // ⚠️ NEVER commit these files:
  "private": true,              // Prevents accidental `npm publish`

  "bin": {
    "mytool": "./cli.js"       // CLI command name  entry point
  },

  "main": "./dist/index.js",   // CommonJS entry point (require())
  "exports": {                  // Modern entry points (ESM)
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    }
  },

  "files": [                   // What gets published to npm
    "dist",
    "README.md"
  ],

  "engines": {
    "node": ">=18.0.0"         // Minimum Node.js version
  },

  "scripts": { /* ... */ },     // We'll cover this in detail

  "dependencies": {             // Production dependencies
    "express": "^4.21.0",
    "lodash": "~4.17.21"
  },

  "devDependencies": {           // Development-only dependencies
    "typescript": "^5.6.0",
    "jest": "^29.7.0",
    "eslint": "^9.14.0"
  },

  "peerDependencies": {          // Required but not installed by this package
    "react": ">=18.0.0"
  },

  "optionalDependencies": {      // Won't fail install if missing
    "fsevents": "^2.3.3"        // macOS-specific, for example
  },

  "browserslist": [              # For autoprefixer/postcss/babel
    "> 0.5%",
    "not dead",
    "not op_mini all"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Version Ranges Explained

{
  "express": "4.21.0",          // Exact version  always use exactly 4.21.0
  "express": "~4.21.0",         // ~ = Patch-level updates: >=4.21.0 <4.22.0
  "express": "^4.21.0",         // ^ = Minor-level: >=4.21.0 <5.0.0 (DEFAULT!)
  "express": ">=4.18.0",        // Any version 4.18.0 or higher
  "express": ">=4.18.0 <5.0.0", // Range with upper bound
  "express": "latest",           // Always latest (risky for production!)
  "express": "github:user/repo", // Install from GitHub directly
  "express": "link:../local-pkg",// Link to local package (development)
  "express": "workspace:*",      // npm workspace (monorepo)
  "express": ""                  // Empty = any version (wildcard, avoid!)
}
Enter fullscreen mode Exit fullscreen mode

My recommendation for most projects:

  • Dependencies: ^ (caret) — get patches and minor features
  • DevDependencies: exact versions or ^ depending on team preference
  • Lock file (package-lock.json) pins exact versions regardless

Scripts: The Power You're Not Using

Built-in Script Variables

{
  "scripts": {
    // These variables are available in ALL scripts:
    // $npm_package_name      "my-awesome-project"
    // $npm_package_version   "1.2.3"

    "echo:name": "echo Package name is: $npm_package_name",

    // These are available when running via 'npm run':
    // npm_config_*           Any npm config value as env var
    // npm_lifecycle_event    Current script name ("start", "build", etc.)

    "what-am-i": "echo Running: $npm_lifecycle_event"
  }
}
Enter fullscreen mode Exit fullscreen mode

Lifecycle Scripts (Automatic)

{
  "scripts": {
    // These run AUTOMATICALLY at specific times:

    "preinstall": "echo 'About to install...'",
    "install": "node scripts/post-install.js",
    "postinstall": "npm run build || true",  // Runs after every npm install!

    "prepublishOnly": "npm run test && npm run build", // Before npm publish ONLY

    // Git hooks (via husky or similar):
    // "precommit": "lint-staged",
    // "prepush": "npm test"
  }
}
Enter fullscreen mode Exit fullscreen mode

Practical Script Patterns

{
  "scripts": {
    // === Development ===
    "dev": "node --watch server.js",
    "dev:debug": "node --inspect --watch server.js",

    // === Building ===
    "build": "tsc && esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node",
    "build:watch": "tsc --watch",
    "clean": "rm -rf dist node_modules/.cache",

    // === Testing ===
    "test": "node --test",
    "test:watch": "node --test --watch",
    "test:coverage": "node --experimental-test-coverage --test",
    "test:e2e": "playwright test",

    // === Linting & Formatting ===
    "lint": "eslint src/ --max-warnings 0",
    "lint:fix": "eslint src/ --fix",
    "format": "prettier --write 'src/**/*.{js,ts,json}'",
    "format:check": "prettier --check 'src/**/*.{js,ts,json}'",

    // Type checking (separate from build!)
    "typecheck": "tsc --noEmit",

    // Combined quality check (run before commits/pushes)
    "quality": "npm run lint && npm run typecheck && npm run test",
    "quality:fix": "npm run lint:fix && npm run format && npm run typecheck",

    // === Database ===
    "db:migrate": "node scripts/migrate.js",
    "db:seed": "node scripts/seed.js",
    "db:reset": "npm run db:migrate -- --force && npm run db:seed",
    "db:studio": "prisma studio",  // Visual DB browser

    // === Docker ===
    "docker:build": "docker build -t myapp .",
    "docker:up": "docker compose up -d",
    "docker:down": "docker compose down",
    "docker:logs": "docker compose logs -f",

    // === Deployment ===
    "deploy:staging": "npm run build && rsync -avz dist/ staging:/app/",
    "deploy:prod": "npm run build && rsync -avz dist/ prod:/app/",

    // === Utility ===
    "prepare": "husky",                    // Husky git hooks setup
    "release": "standard-version",          // Auto-changelog + version bump
    "update:deps": "npx npm-check-updates -u && npm install", // Update all deps
    "outdated": "npm outdated",             // Check for outdated packages
    "audit": "npm audit",                  // Security audit
    "audit:fix": "npm audit fix",          // Fix vulnerabilities
    "clean:cache": "rm -rf node_modules/.cache .eslintcache",

    // === Monorepo (if using workspaces) ===
    "workspaces:list": "npm ws ls",
    "workspaces:run": "npm ws exec -- npm run build",
    "workspaces:test": "npm ws exec -- npm run test"
  }
}
Enter fullscreen mode Exit fullscreen mode

Running Scripts: Tips & Tricks

# Basic usage
npm run dev            # Run the "dev" script
npm test               # Shorthand for "test" (no "run" needed for test/start/stop)

# Pass arguments
npm run dev -- --port 8080    # Everything after "--" goes to the script
npm test -- --grep "auth"    # Pass args to test runner

# Run multiple scripts sequentially
npm run lint && npm run test

# Run scripts in parallel (Unix)
npm run lint & npm run typecheck & wait

# Pre/Post hooks run automatically:
npm run build
# Actually runs: prebuild → build → postbuild (if defined)

# Environment variables (cross-platform!)
npm_config_production=1 npm run build
# Or use cross-env: npx cross-env NODE_ENV=production npm run build

# List all available scripts
npm run

# See what a script would do without running it
npm run build --dry-run  # (if using certain tools)

# Run a script from a dependency's package.json
npx jest                     # Runs jest from node_modules
npx tsc --init              # Runs typescript's init
Enter fullscreen mode Exit fullscreen mode

Managing Dependencies

# Install (adds to package.json automatically)
npm install express              # Save to dependencies
npm install -D typescript         # Save to devDependencies (-D = --save-dev)
npm install -P husky             # Save to optionalDependencies (-P = --save-optional)

# Install specific version
npm install express@4.18.0
npm install express@">=4.18 <5"

# Install from GitHub
npm install github:user/repo
npm install github:user/repo#v1.0.0  # Specific tag/branch/commit

# Global installs (CLI tools)
npm install -g nodemon pnpm
# But prefer npx for one-off usage!

# Remove
npm uninstall lodash
npm uninstall -D jest

# Update
npm update                      # Updates all deps per version ranges
npm update express              # Updates only express
npm install express@latest      # Force latest (updates range too!)

# Check status
npm outdated                    # Shows outdated packages
npm ls express                  # Shows why express was installed (dependency tree)
npm ls express --depth=0        # Top level only

# Auditing
npm audit                       # Check for security vulnerabilities
npm audit fix                   # Auto-fix if possible
npm audit fix --force           # Force fix (may break things, review changes!)

# Clean install (like fresh clone)
rm -rf node_modules package-lock.json && npm install

# Dedupe (flatten dependency tree)
npm dedupe                       # Merges duplicate dependencies where possible
Enter fullscreen mode Exit fullscreen mode

package-lock.json: Why It Matters

package-lock.json locks EXACT versions of EVERY dependency.
Including transitive dependencies.

Why it matters:
→ Teammates install identical versions (reproducible builds)
→ CI/CD installs identical versions
→ Prevents "works on my machine" bugs
→ npm ci uses it for fast, exact installs

RULES:
1. Commit it to version control ✅
2. Don't edit it manually ❌
3. Use npm ci (not npm install) in CI environments
4. If you hit weird bugs: rm package-lock.json && npm install (last resort)
Enter fullscreen mode Exit fullscreen mode

npm vs pnpm vs yarn (2026)

Feature npm pnpm yarn (v1/v4)
Speed Fast Fastest (hard links) Fast
Disk usage Duplicates in nested node_modules Efficient (content-addressable) Moderate
Strict mode --strict flag Default strict Optional
Workspaces ✅ npm workspaces
Lockfile package-lock.json pnpm-lock.yaml yarn.lock
CI install npm ci pnpm install --frozen-lockfile yarn install --frozen-lockfile
Popularity Default (ships with Node) Growing fast Stable

My take: npm is perfectly fine for most projects. pnpm wins for monorepos with many packages.

Common package.json Mistakes

//  Missing "private": true
// Risk: Someone could accidentally npm publish your code!

//  "type": missing (defaults to CommonJS)
// In 2026, you almost certainly want "type": "module"

//  Files not in .gitignore but shouldn't be published
// Fix: Add "files" array or .npmignore

//  Scripts that don't work cross-platform
{ "clean": "rm -rf dist" }  // Fails on Windows!
// Fix: Use rimraf or shelljs: { "clean": "rimraf dist" }

//  Not pinning engines
// Fix: Always add "engines": { "node": ">=18" }

//  Huge dependencies for tiny tasks
// Don't add lodash just for _.map()  use Array.prototype.map()
// Don't add moment.js  use date-fns or native Intl API

//  Outdated dependencies causing security issues
// Fix: Run `npm audit` regularly, set up Dependabot/GitHub Actions
Enter fullscreen mode Exit fullscreen mode

What's your favorite npm script trick?

Follow @armorbreak for more practical developer guides.

Top comments (0)