Modules, npm and package.json Explained

Node.js and Express: Build a Back End API from Scratch · Node.js Basics · 5 min read · Updated

Your project at a glance

  1. package.json describes your project and lists the packages it needs.
  2. npm install reads that list and downloads the packages from the npm registry.
  3. The packages are stored in the node_modules folder. Do not edit it and do not commit it.
  4. Your own files then load those packages and your other files with import.

As a program grows, you cannot keep everything in one file. A module is simply a file whose code you can use from other files. Node.js supports the same module system as modern browsers, called ES modules, with import and export. In this lesson you will split code into files, start a proper project with package.json, install your first package and add scripts.

Turn on ES modules

Node can read two module styles. The older one, CommonJS, uses require. The newer one, ES modules, uses import. Most new code and most documentation now use import, so this course does too. To tell Node that your project uses it, create a package.json file. The easiest way is to let npm do it. Run this inside taskapi.

npm init -y
npm pkg set type=module

npm init -y creates package.json with default answers. npm pkg set type=module adds one line, "type": "module", which makes every .js file in the project an ES module. Without that line, using import gives an error such as *Cannot use import statement outside a module*.

Export and import

Create math.js. It shares three things: a named function, a named constant, and a default export.

export function add(a, b) { return a + b; }
export const TAX = 0.18;
export default function price(net) { return Math.round(net * (1 + TAX)); }

A file can have many named exports but only one default export. Now create app.js and use them.

import price, { add, TAX } from './math.js';
import os from 'node:os';
console.log(add(2, 3), TAX, price(100));
console.log(typeof os.cpus().length);
node app.js
5 0.18 118
number

Three rules to remember. First, named imports go inside curly braces and must match the exported names. A default import has no braces, and you choose any name for it. Second, for your own files you must write the file extension, ./math.js, and not just ./math. Browsers and bundlers sometimes let you skip it, but Node does not. Third, paths for your own files start with ./ or ../. A name with no dot, such as express, means a package from node_modules.

Built-in modules and the node: prefix

Node ships with many modules that need no installation, such as node:fs for files, node:path for file paths, node:os for facts about the computer, node:http for servers, node:crypto for security tools and node:test for tests. Write the node: prefix when you import them, as in import os from 'node:os'. It shows the reader, and Node, that this is built in and not a package from the internet.

npm and packages

Most real projects use packages that other people wrote and shared on the npm registry, a huge public library. To add one, use npm install. Try it with the package we will use later.

npm install express

Three things happen. A folder named node_modules appears with the package and everything it needs. package.json gets a dependencies section that lists express with a version. And a file named package-lock.json records the exact versions installed, so every computer gets the same result.

  • `node_modules` can be huge. Never edit it by hand and never commit it to git. Add node_modules to .gitignore.
  • `package.json` lists what your project needs. Commit it.
  • `package-lock.json` locks exact versions. Commit it too.
  • To rebuild node_modules on another computer, run npm install and npm reads the two files.

Some packages are needed only while you develop, such as test or formatting tools. Install those with npm install --save-dev name, and npm lists them under devDependencies. On a server, npm install --omit=dev skips them.

Version numbers

A version such as 5.3.0 has three parts: major, minor and patch. A patch is a bug fix. A minor adds features without breaking old code. A major may break old code. In package.json you will see something like ^5.1.0. The caret means npm may install newer minor and patch versions, but not a new major one. That protects you from surprise breaking changes while still receiving fixes.

npm scripts

The scripts section of package.json holds short names for commands. Add these three. We will create the files they use in later lessons, so do not run them yet.

"scripts": {
  "start": "node --env-file=.env server.js",
  "dev": "node --watch --env-file=.env server.js",
  "test": "node --test"
}

Run npm start or npm test directly. Other names need run, for example npm run dev. The flag --watch restarts the program every time you save a file, so you do not need a separate tool. The flag --env-file=.env loads settings from a file named .env. You will meet it in lesson 11.

Safety with packages

A package is code from strangers, and it runs with your permissions. Choose well-known packages with many users and recent updates. Read the name carefully, because fake packages with names that look like popular ones exist. Run npm audit now and then to see reported problems in what you have installed, and keep dependencies updated.

Your task

  1. Run npm init -y and npm pkg set type=module in taskapi.
  2. Create math.js and app.js as shown and run node app.js.
  3. Change TAX to 0.05, run again, and check that the price of 100 changes.
  4. Run npm install express, then look at package.json and the new files.
  5. Create .gitignore containing the line node_modules.

How to check it worked

  • node app.js prints 5 0.18 118 and then number.
  • package.json shows "type": "module" and a dependencies section with express.
  • Deleting node_modules and running npm install brings it back.

Frequently asked questions

What is the difference between dependencies and devDependencies?

dependencies are needed to run the app. devDependencies are only for development, such as test tools. Install them with --save-dev.

Why must I write the file extension in imports?

In Node.js ES modules, import paths for your own files must include the extension, such as ./math.js. Without it, Node cannot find the file.