DEV Community

Alex Chen
Alex Chen

Posted on

npm Scripts and package.json Mastery (2026)

npm Scripts and package.json Mastery (2026)

Your package.json is more than a dependency list — it's the command center of your project. Here's how to master it.

package.json Deep Dive

{
  "name": "my-awesome-project",
  "version": "2.1.0",
  // Semantic versioning: MAJOR.minor.patch
  // MAJOR = breaking changes, minor = new features (backward compatible), patch = bug fixes

  "description": "A well-configured Node.js project",
  "private": true,           // Prevents accidental npm publish!

  "type": "module",          // Makes .js files use ES Modules
  "engines": {               // Enforce Node.js version range
    "node": ">=18.0.0"
  },

  "main": "./dist/index.cjs",        // Entry point for CommonJS consumers
  "module": "./dist/index.mjs",       // Entry point for ESM consumers
  "types": "./dist/index.d.ts",      // TypeScript types entry
  "bin": {
    "my-tool": "./dist/cli.js"        // CLI command name → entry file
  },
  "exports": {               // Explicit exports (prevents internal file access)
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    },
    "./utils": {
      "import": "./dist/utils.mjs",
      "require": "./dist/utils.cjs"
    }
  },
  "files": [                 // Files included when publishing (whitelist!)
    "dist/",
    "README.md",
    "LICENSE"
  },

  "scripts": {                // The real power of npm! See below...
  },

  "config": {                 // Custom config accessible via npm_package_config_*
    "port": "3000"
  },

  "dependencies": {           // Production dependencies (installed in prod)
    "express": "^4.18.0",     // ^ = compatible with version 4.x.x (>=4.18.0 <5.0.0)
    "lodash": "~4.17.21"      // ~ = patch updates only (>=4.17.21 <4.18.0)
  },
  "devDependencies": {       // Development-only (not installed in production)
    "jest": "^29.0.0",
    "typescript": "^5.0.0",
    "eslint": "^8.0.0"
  },
  "peerDependencies": {       // Required but not auto-installed (host provides)
    "react": ">=18.0.0"
  },
  "optionalDependencies": {   // Nice to have; install continues if they fail
    "fsevents": "^2.3.0"     // macOS-only package
  },

  "browserslist": [           # Target browsers for autoprefixer/babel
    "> 1%",                    // > 1% global usage
    "last 2 versions",         // Last 2 versions of each browser
    "not dead"                 // Exclude dead browsers
  ]
}
Enter fullscreen mode Exit fullscreen mode

Powerful npm Scripts

{
  "scripts": {
    // Basic scripts
    "start": "node dist/server.js",
    "dev": "node --watch src/server.js",   // Node.js 18+ built-in watch mode!
    "build": "tsc && node scripts/build.js",
    "test": "jest --coverage",
    "lint": 'eslint src/ --ext .ts,.js',

    // Pre/post hooks (auto-run before/after script):
    "pretest": "npm run lint",             // Runs automatically before `npm test`
    "postinstall": "node scripts/setup.js", // Runs after `npm install`
    "prepublishOnly": "npm run build && npm test", // Runs before `npm publish` only!

    // Combined scripts (run multiple in sequence with && or in parallel with &)
    "check": "npm run lint && npm run typecheck && npm run test",
    "dev:full": "concurrently \"npm run dev\" \"npm run watch:css\"",

    // Environment variables in scripts:
    "start:prod": "NODE_ENV=production node dist/server.js",
    "start:staging": "NODE_ENV=staging PORT=8080 node dist/server.js",

    // Cross-platform scripts (no bash-ism!):
    "clean": "rimraf dist",              // rimraf works on Windows too
    "copy-assets": "cpy src/public dist/public",

    // Using npm variables:
    "info": "echo 'Project: %npm_package_name% v%npm_package_version%'",
    "env-info": "env | grep npm_package_",  // See all available npm env vars

    // Database operations:
    "db:migrate": "prisma migrate deploy",
    "db:seed": "prisma db seed",
    "db:reset": "prisma migrate reset --force",
    "db:studio": "prisma studio",

    // Deployment:
    "deploy:staging": "npm run build && rsync -avz dist/ user@staging:/app/",
    "deploy:prod": "npm run build && docker build -t myapp . && docker push myapp:latest"
  }
}
Enter fullscreen mode Exit fullscreen mode
# Running scripts:
npm run dev                  # Run a script
npm start                   # Shorthand for npm run start (only works for start/test/stop/restart)
npm run build -- --verbose   # Pass arguments after --
npx jest                     # Run without adding to package.json

# Script lifecycle hooks (execution order):
# npm publish:
# prepublishOnly → prepack → pack → postpack → postpublish

# npm install:
# preinstall → install → postinstall

# Useful built-in npm environment variables in scripts:
# $npm_package_name        → Project name
# $npm_package_version     → Version
# $npm_node_execpath       → Path to Node.js binary
# $npm_config_prefix       → npm prefix path
# $INIT_CWD                → Current working directory when script runs
Enter fullscreen mode Exit fullscreen mode

Dependency Management

# Installing packages:
npm install express            # Add to dependencies
npm install -D jest            # Add to devDependencies
npm install -P typescript      # Explicitly add to dependencies
npm install --save-peer react  # Add to peerDependencies
npm install --save-optional fsevents  # Add to optionalDependencies

# Version ranges explained:
"express": "4.18.0"            # Exact version (pinned)
"express": "~4.18.0"           // ~4.18.0 <= v < 4.19.0 (patch updates)
"express": "^4.18.0"           # >=4.18.0 <5.0.0 (minor+patch updates)
"express": ">=4.17.0 <5.0.0"  # Range expression
"express": "latest"            # Always latest (risky for production!)
"express": "github:user/repo"  # Install from GitHub directly

# Auditing and updating:
npm outdated                   # Check for newer versions
npm update                     # Update per semver ranges in package.json
npm update lodash              # Update specific package
npx npm-check-updates -u      # Interactive version bump tool
npm audit                      # Security vulnerability check
npm audit fix                  # Auto-fix vulnerabilities (when safe)

# Understanding lock files:
# package-lock.json: Exact versions of ALL dependencies (including transitive!)
# Always commit this file! It ensures reproducible installs.
# yarn.lock / pnpm-lock.yaml: Same concept for other package managers

# Removing packages:
npm uninstall lodash           # Remove from dependencies + node_modules
npm prune                      # Remove packages not listed in dependencies

# Why use npx?
npx create-react-app my-app    # Run package without installing globally
npx http-server ./dist         # One-time use of a CLI tool
npx @typescript-eslint/init    # Run specific version of a package
Enter fullscreen mode Exit fullscreen mode

Workspaces (Monorepo Basics)

// Root package.json (monorepo with npm workspaces)
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",       // Auto-discover all folders under packages/
    "apps/*"            // Also include apps/
  ]
}
Enter fullscreen mode Exit fullscreen mode
# Commands from root affect all workspaces:
npm install              # Install all workspace deps at once
npm run build            # Build all workspaces
npm run test --workspace=packages/shared  # Run in specific workspace only
npm ls                  # Shows dependency graph across workspaces

# Cross-workspace references:
# In packages/web/package.json:
# "dependencies": { "@my-monorepo/shared": "*" }  # * = use local workspace version
# npm will symlink it instead of downloading from registry!
Enter fullscreen mode Exit fullscreen mode

What's your favorite npm trick? How do you manage dependencies in large projects?

Follow @armorbreak for more practical developer guides.

Top comments (0)