npm, package.json, Scripts and Environment Variables (dotenv)

MERN Stack Full Course: MongoDB, Express, React and Node.js · Node.js and Express · 4 min read · Updated

From package.json to a running app

  1. package.json lists the name, the scripts and the packages your project needs.
  2. npm install reads that list and downloads the packages.
  3. They are saved in node_modules, a folder that is ignored by Git and can always be recreated.
  4. Secrets and settings go in a .env file that dotenv loads into process.env. It is never committed.
  5. The .env.example file shows other people the names they must fill in, without the secrets.

Almost nobody writes a server from nothing. You use packages: small libraries other people wrote and published. npm is the tool that downloads them, and package.json is the file that lists what your project needs and how to run it. This lesson creates the server folder of the Job Tracker and sets up the two things every real project needs from day one: scripts to run it, and a safe way to keep settings out of the code.

package.json: the identity card of a project

npm init -y creates a package.json. The fields that matter are these.

  • `"type": "module"` switches the project to modern ES modules, so you write import and export instead of require. The whole course uses this.
  • `scripts` are named commands. npm run dev runs the dev script. start and test are special: you can write npm start and npm test without run.
  • `dependencies` are packages your program needs while it runs. `devDependencies` are only for development, such as test or build tools.
  • Version numbers like ^5.2.1 mean “5.2.1 or any newer 5.x version”. The exact versions that were installed are recorded in package-lock.json, which you should commit: it makes everyone, including your server, install the same versions.
{
  "name": "job-tracker-server",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "node --watch index.js"
  },
  "dependencies": {
    "dotenv": "^17.3.1"
  }
}

Installing and the node_modules folder

npm install express downloads Express and its own dependencies into node_modules/ and adds it to dependencies. npm install with no name installs everything listed in package.json, which is what you run after cloning a project. Never edit node_modules, never commit it (the .gitignore already excludes it), and delete it freely if something seems broken: npm install rebuilds it.

Restarting automatically while you work

Node.js reads your code once, when it starts. To see a change you must stop and start again, which gets tiring. Since Node.js 18.11 there is a built-in fix: node --watch index.js restarts on every file change. That is our dev script, so you need no extra package such as nodemon.

Environment variables and .env

Passwords, database addresses and secret keys must not be written in code, because code goes into Git and Git history is permanent. They also differ from computer to computer: your laptop uses a local database and your server uses a hosted one. The solution is environment variables, read in code as process.env.NAME.

During development it is awkward to set variables in the terminal each time, so the dotenv package reads a file called .env and loads its lines into process.env. The top line of index.js below does it: import 'dotenv/config'. The .env file stays on your machine (it is in .gitignore). What you do commit is .env.example, a copy with the names but no secrets, so that a new developer knows what to fill in.

# Copy this file to .env and fill it in. Never commit .env to Git.
PORT=5000
// Lesson 8: package.json, npm scripts and environment variables.
import 'dotenv/config'; // reads server/.env into process.env

const port = Number(process.env.PORT) || 5000;
console.log(`Job Tracker server would listen on port ${port}`);
console.log(`Running in ${process.env.NODE_ENV || 'development'} mode`);

Always read numbers with Number(...) and give a sensible default, as in Number(process.env.PORT) || 5000. Environment variables are always text, and a missing one is undefined.

Your task

  1. Create server/.env by copying .env.example (do not commit it). Run cd server && npm install && npm run dev.
  2. Change PORT in .env. Because node --watch watches your code files and not .env, stop the server with Ctrl + C and start it again to see the new value.
  3. Run npm run dev with NODE_ENV=production set in your shell and read the second printed line.
  4. Check that git status does not list .env (it should be ignored) but does list .env.example.

How to check it worked

  • The server prints the port from your .env.
  • git status never shows .env or node_modules.

Summary

  • package.json lists dependencies and scripts; commit package-lock.json.
  • node --watch restarts on changes, with no extra package.
  • Secrets and settings live in .env, never in Git; commit .env.example instead.

Download the solution (mern-lesson-08.zip)

Frequently asked questions

Should I commit package-lock.json?

Yes. It records the exact versions that were installed, so your teammates and your server install the same ones. Without it, a new install could pick a newer version and behave differently.

What is the difference between dependencies and devDependencies?

dependencies are needed when the app runs in production. devDependencies, such as test runners and build tools, are only needed while developing. On a server you can install with --omit=dev to skip them.

Why is .env not committed to Git?

It holds secrets such as database passwords. Anything committed stays in Git history even after you delete it, and anyone with access to the repository could read it. Commit .env.example with empty values instead.