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 your project's command center. Master npm scripts and supercharge your workflow.

package.json Anatomy

{
  "name": "my-awesome-project",
  "version": "1.2.3",                    // Semantic versioning: MAJOR.MINOR.PATCH
  "description": "A really cool project",
  "main":dist/index.js",                // Entry point for require()
  "type": "module",                     // ES Modules mode (import/export)
  "bin": {                              // CLI commands (for npm publish)
    "mytool": "./bin/cli.js"
  },
  "files": [                            // Files to include when publishing
    "dist",
    "README.md",
    "LICENSE"
  ],
  "scripts": {
    // Your command center → see below
  },
  "dependencies": {
    // Runtime dependencies (installed in production)
  },
  "devDependencies": {
    // Development-only dependencies (not installed in production)
  },
  "peerDependencies": {
    // Required but not auto-installed (host project provides)
  },
  "optionalDependencies": {
    // Nice to have, won't fail install if missing
  },
  "engines": {
    "node": ">=18.0.0",                 // Minimum Node.js version
    "npm": ">=9.0.0"
  },
  "config": {                           // Custom config for scripts
    "port": 3000,
    "env": "development"
  }
}
Enter fullscreen mode Exit fullscreen mode

Essential Scripts Every Project Needs

{
  "scripts": {
    "start": "node dist/index.js",
    "dev": "nodemon --watch src src/index.js",
    "build": "tsc && esbuild --bundle --minify",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "lint": "eslint 'src/**/*.{js,ts}'",
    "lint:fix": "eslint 'src/**/*.{js,ts}' --fix",
    "format": "prettier --write 'src/**/*.{js,ts,json,md}'",
    "typecheck": "tsc --noEmit",
    "clean": "rm -rf dist node_modules/.cache",
    "prepare": "husky install",
    "prepublishOnly": "npm run build && npm test"
  }
}
Enter fullscreen mode Exit fullscreen mode

Script Composition Patterns

{
  "scripts": {
    "build": "npm run clean && npm run build:compile && npm run build:copy",
    "build:compile": "tsc",
    "build:copy": "cp -r src/public dist/",

    "test": "npm run test:unit && npm run test:integration",
    "test:unit": "jest --testPathPattern=unit",
    "test:integration": "jest --testPathPattern=integration",

    "deploy": "npm run test && npm run lint && npm run build && npm run deploy:push",
    "deploy:push": "git push origin main && ssh server './deploy.sh'"
  }
}
// Use colon-separated names for sub-tasks
// Chain with && (stops on failure) or ; (runs all regardless)

// Cross-platform compatible:
//  Windows incompatible: "clean": "rm -rf dist"
//  Cross-platform:   "clean": "rimraf dist"  (or use shx, del-cli)
Enter fullscreen mode Exit fullscreen mode

Environment Variables in Scripts

# Method 1: Inline (simple, but messy for long values)
"scripts": {
  "start": "PORT=3000 NODE_ENV=production node index.js"
}

# Method 2: .env files (use dotenv or env-cmd)
# Install: npm install -D dotenv
"scripts": {
  "dev": "dotenv -- node-dev index.js",
  "start": "dotenv -- node index.js"
}
# .env file:
# PORT=3000
# DATABASE_URL=postgres://localhost/mydb

# Method 3: cross-env (works on Windows too!)
# Install: npm install -D cross-env
"scripts": {
  "build": "cross-env NODE_ENV=production webpack --mode production"
}

# Method 4: npm config variables (built-in since npm v7+)
# In package.json:
"config": {
  "port": "3000"
}
# In scripts: use $npm_package_config_port
"scripts": {
  "start": "echo Port is $npm_package_config_port && node index.js"
}

# Best practice: Use a .env.example file committed to git:
# PORT=3000
# DATABASE_URL=
# API_KEY=
# JWT_SECRET=
# (Actual values in .env, which is gitignored)
Enter fullscreen mode Exit fullscreen mode

Advanced Script Techniques

{
  "scripts": {
    # Sequential execution (&& = stop if one fails):
    "ci": "npm run lint && npm run typecheck && npm run test && npm run build",

    # Parallel execution (& = runs simultaneously):
    "parallel": "npm run lint & npm run typecheck & wait",

    # Conditional execution:
    "precommit": "lint-staged",

    # Using npm lifecycle hooks (automatic!):
    # npm run X automatically runs preX before and postX after
    # Example: "npm run build" runs:
    #   1. prebuild script (if defined)
    #   2. build script
    #   3. postbuild script (if defined)

    "prebuild": "echo 'Starting build...'",
    "build": "tsc",
    "postbuild": "echo 'Build complete!' && npm run size-check",

    # Passing arguments to scripts:
    # Usage: npm run test -- --grep "auth" --verbose
    # The -- separates npm flags from script arguments

    # Reading package.json values in scripts:
    "info": "echo \"Name: $npm_package_name\" && echo \"Version: $npm_package_version\""
  }
}
Enter fullscreen mode Exit fullscreen mode

Dependency Management

# Installing dependencies:

npm install lodash           # Save to dependencies (runtime)
npm install -D jest          # Save to devDependencies (development only)
npm install -P typescript     # Save to peerDependencies
npm install -O optional       # Save to optionalDependencies

# Exact versions (reproducible builds):
npm install express@4.18.2    # Exact version
npm install express@^4.18.2   # Compatible (>=4.18.2 <5.0.0) ← DEFAULT
npm install express@~4.18.2   # Patch only (>=4.18.2 <4.19.0)
npm install express@latest    # Always latest

# Updating dependencies:
npm outdated                  # Check what's outdated
npm update                    # Update to latest within version range
npx npm-check-updates -u      # Update package.json to latest versions
npm install                   # Reinstall based on updated package.json

# Auditing:
npm audit                     # Check for vulnerabilities
npm audit fix                 # Auto-fix where possible
npm audit fix --force         # Force fix (may break things, use carefully!)

# Removing:
npm uninstall lodash          # Remove from dependencies AND node_modules

# Why devDependencies vs dependencies?
# dependencies: Code your project imports at runtime
#   Examples: express, lodash, axios, mongoose
# devDependencies: Tools used during development/build/test
#   Examples: typescript, eslint, jest, prettier, webpack

# Common mistake: Putting dev tools in dependencies
# This bloats production installs and increases attack surface!
Enter fullscreen mode Exit fullscreen mode

Workspaces (Monorepo Management)

// Root package.json (monorepo setup):
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ],
  "scripts": {
    "build": "npm run build --workspaces",
    "test": "npm run test --workspaces",
    "dev": "npm run dev --workspaces --if-present",
    "clean": "npm exec --workspaces -- rm -rf dist node_modules"
  }
}

# Structure:
# my-monorepo/
# ├── package.json          (root, workspaces defined here)
# ├── packages/
#    ├── shared-utils/     (shared library)
#       └── package.json
#    └── ui-components/    (UI component library)
#        └── package.json
# └── apps/
#     ├── web/              (frontend app)
#        └── package.json
#     └── api/              (backend API)
#         └── package.json

# Benefits:
# - Single node_modules directory (deduped, saves disk space)
# - Shared dependencies between packages
# - Can reference local packages: "@myorg/shared-utils": "*"
# - Run commands across all packages: npm run test --workspaces
# - Install all at once: npm install (from root)

# Common monorepo tools that extend npm workspaces:
# - Turborepo (remote caching, parallel execution)
# - Nx (smart caching, code generation, affected projects)
# - Lerna (legacy, mostly replaced by built-in workspaces)
Enter fullscreen mode Exit fullscreen mode

What's your favorite npm script trick? How do you organize your package.json?

Follow @armorbreak for more practical developer guides.

Top comments (0)