Skip to content

npm and package.json

You’ve used npm before to install packages for Vite projects. Now you’ll understand exactly what’s happening — because as a backend developer you’ll be managing packages directly rather than relying on scaffolding tools.

Every Node.js project has a package.json at its root. It describes the project and records its dependencies:

{
"name": "bulletin-api",
"version": "1.0.0",
"description": "Bulletin community board API",
"main": "dist/index.js",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"express": "^4.18.2",
"better-sqlite3": "^9.4.3",
"bcrypt": "^5.1.1",
"jsonwebtoken": "^9.0.2",
"cors": "^2.8.5",
"helmet": "^7.1.0",
"dotenv": "^16.4.1"
},
"devDependencies": {
"typescript": "^5.4.2",
"tsx": "^4.7.1",
"@types/node": "^20.11.5",
"@types/express": "^4.17.21",
"@types/better-sqlite3": "^7.6.8",
"@types/bcrypt": "^5.0.2",
"@types/jsonwebtoken": "^9.0.5",
"@types/cors": "^2.8.17"
}
}

dependencies are packages needed at runtime — the code that runs on the server in production. Express, bcrypt, SQLite go here.

devDependencies are only needed during development and building — TypeScript types, build tools, testing frameworks. When you deploy to Railway, Railway runs npm install --production and skips devDependencies.

Install to the right place:

Terminal window
npm install express # → dependencies
npm install --save-dev typescript # → devDependencies

Package versions follow MAJOR.MINOR.PATCH:

  • ^4.18.2 — caret: accept 4.x.x updates (same major, any minor/patch)
  • ~4.18.2 — tilde: accept 4.18.x updates (same major.minor, any patch)
  • 4.18.2 — exact: only this version

The ^ prefix is the npm default and what you’ll see most often.

When you run npm install, npm creates package-lock.json — a file that records the exact version of every installed package (and their dependencies). Always commit package-lock.json to git. It ensures everyone on the team (and your production server) installs identical versions.

Packages are installed into node_modules/. This directory can contain thousands of files and should never be committed to git. Add it to .gitignore:

node_modules/
dist/
.env

Whoever clones your repo runs npm install to recreate node_modules from package-lock.json.

Scripts in package.json are run with npm run <name>. They can chain commands:

{
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc --noEmit && tsc",
"lint": "eslint src/",
"clean": "rm -rf dist"
}
}

npm run lists all available scripts. Scripts have access to locally-installed binaries in node_modules/.bin/ — that’s why tsx works without installing it globally.

  1. In your bulletin-api project, install all the dependencies listed above in one command.
  2. Install all the devDependencies with --save-dev.
  3. Check your package.json and confirm the packages went to the right section.
  4. Open node_modules/ and note how many packages were installed (there will be many more than the 7 direct dependencies — they each have their own dependencies).
  • package.json records project metadata, scripts, and dependencies.
  • dependencies are needed at runtime; devDependencies are only needed during development.
  • Semantic versioning: ^ (same major), ~ (same major.minor), exact.
  • Always commit package-lock.json; never commit node_modules/.
  • npm scripts run with npm run <name> and have access to local binaries.