Skip to content
elephantoo

Node.js & npm basics

Lesson 32 of 34 18 min read

Run scripts with Node, use built-in modules, manage packages with npm and write npm scripts.


Node.js runs JavaScript outside the browser — on servers, in command-line tools and in the build tools (Vite, TypeScript, ESLint, Prettier) that every front-end project uses. npm is the package manager that comes with Node and gives you access to millions of open-source packages. Even if you only build front-ends, you'll use both every day.

Installing and checking Node#

Install the current LTS release (Node 24 at the time of writing):

Terminal
# Recommended: a version manager such as nvm (Linux/macOS)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash
nvm install --lts
nvm use --lts

# Or system packages (often older versions):
sudo apt install nodejs npm   # Ubuntu/Debian
sudo dnf install nodejs       # Fedora

node --version
npm --version

Version managers let you switch Node versions per project — many projects record the version they need in a .nvmrc file. Windows users can use the installer from nodejs.org or fnm.

Running code#

Terminal
node                     # interactive REPL (.exit to quit)
node script.js           # run a file
node --watch server.js   # re-run automatically when files change
node -e "console.log(1 + 1)"

What's different from the browser?#

BrowserNode.js
window, document, DOMNo DOM; globalThis, process
localStorageFiles and databases
Runs code from websites — sandboxedFull access to files, network and processes
Same language, same fetch, URL, setTimeout, structuredClone, crypto.randomUUID()✅ also available in Node

Built-in modules#

Node ships with modules for files, paths, networking and more. Import them with the node: prefix:

JavaScript
// files.mjs
import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
import path from "node:path";
import os from "node:os";

const dir = path.join(os.tmpdir(), "elephantoo-demo");
await mkdir(dir, { recursive: true });

const file = path.join(dir, "notes.json");
await writeFile(file, JSON.stringify({ lesson: "nodejs-and-npm", done: true }, null, 2));

const data = JSON.parse(await readFile(file, "utf8"));
console.log(data.lesson);                 // nodejs-and-npm
console.log(await readdir(dir));          // [ 'notes.json' ]
console.log(path.extname(file));          // .json
console.log(path.basename(file, ".json")); // notes
  • node:fs/promises — read, write, list and delete files.
  • node:path — join and split file paths portably (Windows uses \).
  • node:os, node:process — information about the machine and current process.
  • node:http — build web servers (frameworks like Express or Fastify build on it).
  • node:crypto, node:child_process, node:events, node:stream, node:test…

Process: arguments, environment and exit codes

JavaScript
// greet.mjs – run with: node greet.mjs Ada --shout
const [, , name = "world", ...flags] = process.argv;
const greeting = `Hello, ${name}!`;
console.log(flags.includes("--shout") ? greeting.toUpperCase() : greeting);

console.log("Running in", process.env.NODE_ENV ?? "development");
if (!name) process.exit(1); // non-zero exit code = failure
Terminal
node greet.mjs Ada --shout
Output
HELLO, ADA!
Running in development

Keep secrets like API keys in environment variables, not in code. Node 20.6+ can load a .env file directly with node --env-file=.env app.js — and never commit .env to git.

A tiny web server#

JavaScript
// server.mjs
import { createServer } from "node:http";

const courses = [{ slug: "javascript", lessons: 34 }];

const server = createServer((req, res) => {
  if (req.method === "GET" && req.url === "/api/courses") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify(courses));
    return;
  }
  res.writeHead(404, { "Content-Type": "text/plain" });
  res.end("Not found");
});

server.listen(3000, () => console.log("Listening on http://localhost:3000"));

Run node server.mjs and open http://localhost:3000/api/courses. Real projects usually use a framework like Express, Fastify or Hono for routing and middleware.

npm and package.json#

Every Node project has a package.json describing it. Create one:

Terminal
mkdir my-tool && cd my-tool
npm init -y
JSON
{
  "name": "my-tool",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "test": "node --test"
  },
  "dependencies": {
    "chalk": "^6.0.1"
  },
  "devDependencies": {
    "prettier": "^3.9.9"
  }
}

(Add "type": "module" yourself so .js files use import/export.)

Installing packages

Terminal
npm install chalk            # add a runtime dependency (short: npm i chalk)
npm install -D prettier      # add a dev dependency
npm install                  # install everything listed in package.json
npm ci                       # clean, exact install from package-lock.json (use in CI)
npm uninstall chalk
npm update                   # update within allowed version ranges
npm outdated                 # see what's out of date

Packages go into node_modules/ — never commit that folder (add it to .gitignore). Anyone can recreate it with npm install.

JavaScript
// src/index.js
import chalk from "chalk";
console.log(chalk.green("✔ Build succeeded"));

Semantic versioning

Versions look like MAJOR.MINOR.PATCH (e.g. 6.0.1):

  • MAJOR — breaking changes
  • MINOR — new features, backwards compatible
  • PATCH — bug fixes
RangeAllows
"5.4.1"exactly 5.4.1
"~5.4.1"5.4.x (≥ 5.4.1)
"^5.4.1"5.x.y (≥ 5.4.1, < 6.0.0) — npm's default

The lockfile

package-lock.json records the exact version of every package (including dependencies of dependencies). Commit it, so teammates, CI and production all install identical code.

npm scripts#

scripts are named commands, run with npm run <name>:

Terminal
npm run dev
npm test          # shortcut for npm run test
npm start         # shortcut for npm run start

Scripts can use any installed package's command-line tool without a global install, because npm adds node_modules/.bin to the PATH:

JSON
{
  "scripts": {
    "format": "prettier --write .",
    "lint": "eslint .",
    "build": "vite build",
    "check": "npm run lint && npm test"
  }
}

npx: run a package without installing it

Terminal
npx prettier --check .
npx serve .                    # quick static file server
npm create vite@latest my-app  # scaffold a project (runs create-vite)

Testing with the built-in test runner#

Node has a test runner — no extra packages needed:

JavaScript
// src/math.js
export const add = (a, b) => a + b;
JavaScript
// src/math.test.js
import { test } from "node:test";
import assert from "node:assert/strict";
import { add } from "./math.js";

test("adds two numbers", () => {
  assert.equal(add(2, 3), 5);
});

test("handles negatives", () => {
  assert.equal(add(-2, -3), -5);
});
Terminal
node --test
Output
✔ adds two numbers (0.6ms)
✔ handles negatives (0.1ms)
ℹ tests 2
ℹ pass 2
ℹ fail 0

(Front-end projects usually use Vitest, which has a similar API and understands Vite configs.)

Package safety#

Every dependency is code you run with full access to your machine:

  • Prefer well-maintained, widely used packages; check the repository, download stats and last release.
  • Run npm audit to check for known vulnerabilities.
  • Watch for typo-squatting (reqeust instead of request).
  • Don't add a package for a one-liner you could write yourself.

Alternatives you'll hear about#

  • pnpm and Yarn — alternative package managers (pnpm saves disk space with a shared store).
  • Deno and Bun — alternative JavaScript runtimes with built-in TypeScript support and tooling; both can run most npm packages.

Common mistakes#

  • Committing node_modules or .env.
  • Installing project tools globally (npm i -g) instead of as dev dependencies — teammates won't get the same version.
  • Deleting package-lock.json to "fix" problems.
  • Mixing require and import — choose ESM with "type": "module".
  • Using sudo npm install -g on Linux. Use nvm, which installs to your home directory.

What's next#

Next: TypeScript, which adds static types to JavaScript and catches whole classes of bugs before your code runs.

Check your understanding

Quick quiz

0/3 answered
  1. 1.What is the difference between dependencies and devDependencies in package.json?

  2. 2.Why should you commit package-lock.json?

  3. 3.What does the version range "^2.4.1" allow?

Finished reading?

Mark this lesson complete to track your progress.