HomebaseWorks
Lesson 4 of 4

npm, package.json, scripts and semantic versioning

Read a package.json with confidence, know what ^ and ~ allow, and use the lockfile and npm ci so installs match everywhere.

EWritten by the HomebaseWorks editorial team · reviewed 26 Oct 2026

Before you write a line of application code on a Node.js team, you will install dependencies, run scripts and probably fix a broken install. Interviewers ask about package.json because it shows whether you understand how a project is assembled. These are the parts that matter.

Advertisement

Reading package.json

{
  "name": "orders-api",
  "version": "1.4.2",
  "main": "src/server.js",
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js",
    "test": "jest",
    "lint": "eslint src"
  },
  "dependencies": {
    "express": "^4.19.2"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "nodemon": "^3.1.0",
    "eslint": "^8.57.0"
  }
}

dependencies are needed to run the app in production. devDependencies are needed only to build or test it. npm install express adds to the first; npm install --save-dev jest adds to the second. A production install with npm ci --omit=dev skips the dev tools.

Scripts

npm run dev runs the dev script. npm start and npm test are shortcuts that do not need run. Scripts can call tools from node_modules/.bin by name, which is why jest works without a path. A script named pretest runs automatically before test, and posttest after. npx eslint src runs a package without adding a script.

What ^ and ~ allow

Semantic versioning is MAJOR.MINOR.PATCH. Major means breaking changes, minor means new features, patch means fixes.

  • ^4.19.2 allows anything from 4.19.2 up to but not including 5.0.0.
  • ~4.19.2 allows 4.19.2 up to but not including 4.20.0.
  • 4.19.2 with no symbol pins that exact version.
  • Below 1.0.0 the caret is stricter: ^0.3.1 allows 0.3.x only, because early versions treat minor bumps as breaking.

Worked example: a package.json says ^2.5.0 and the registry has 2.5.3, 2.9.0 and 3.0.1. A fresh install picks 2.9.0. The lockfile is what stops that from changing under you next week.

package-lock.json records the exact version of every package and sub-package that was installed. Commit it. npm install may update it; npm ci deletes node_modules and installs exactly what the lockfile says, failing if the two files disagree. Use npm ci in CI pipelines and on servers so every machine gets identical code. npm outdated lists packages with newer versions and npm audit lists known vulnerabilities.

Common mistakes and a practice habit

  • Committing node_modules. Add it to .gitignore.
  • Deleting the lockfile to fix an error. This usually hides the real problem and changes versions for everyone.
  • Installing project tools globally, so the next developer's machine behaves differently.
  • Putting a test runner in dependencies, which bloats the production image.

Practice habit: create an empty project with npm init -y, add one dependency and one dev dependency, write a test script, then open the lockfile and find the exact version that was resolved. Repeat with a tilde range and compare.

Advertisement
Continue with Node.js4 lessons · 1 interview set · 1 test · 1 certificate
Not tested

More in Web and app