JavaScript20 min readIntermediate

Modules, npm & Tooling

ES Modules, package.json, npm, and how a modern JS project is laid out.

ES Modules

JavaScript code is split across files via modules. The modern syntax is ES Modules (ESM): each file is its own module, you choose what to export, and import only what you need.

javascript
// math.js
export function square(x) { return x * x; }
export const PI = 3.14159;
export default function greet(n) { return `Hello ${n}`; }

// main.js
import greet, { square, PI } from "./math.js";
console.log(greet("Ada"), square(5), PI);
  • Named exports — `export function foo() {}`. Multiple per file. Imported with braces.
  • Default export — `export default ...`. One per file. Imported without braces.
  • Imports are static — they're resolved before code runs. This lets bundlers tree-shake unused code.
  • Modules run in strict mode automatically; top-level `this` is undefined.

CommonJS — the older style

Older Node code (and many libraries) use CommonJS (CJS): `require("x")` and `module.exports = ...`. New code should use ESM, but you'll see CJS for years to come. In Node, files ending in `.mjs` are ESM; `.cjs` are CommonJS; `.js` follows the `"type"` field in package.json.

npm and package.json

npm is the package manager that ships with Node.js. It downloads libraries from the npm registry — over 2 million packages. Every project has a `package.json` file that describes the project and its dependencies.

bash
npm init -y                    # create package.json
npm install zod                # add a dependency
npm install -D vitest          # dev dependency (testing, build tools)
npm uninstall zod
npm update
npm run dev                    # run a script defined in package.json
json
{
  "name": "my-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "node --watch src/index.js",
    "test": "vitest"
  },
  "dependencies": {
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "vitest": "^1.0.0"
  }
}

The version specifier `^3.22.4` means "anything compatible with 3.22.4" — npm can pick up 3.22.5 or 3.99.0 but not 4.0.0. The exact resolved versions are pinned in `package-lock.json`. ALWAYS commit the lock file.

Alternatives to npm

  • pnpm — faster, uses a content-addressable store; no duplicate copies of the same package across projects.
  • Yarn — older alternative; still common.
  • Bun — JS runtime + package manager + bundler; very fast; pickier about edge cases.

A practical example

javascript
import { z } from "zod";

const User = z.object({
  name: z.string(),
  age: z.number().int().nonnegative(),
  email: z.string().email().optional(),
});

const parsed = User.parse({ name: "Ada", age: 36 });
console.log(parsed);

// Catches bad data:
try {
  User.parse({ name: "Ada", age: -1 });
} catch (e) {
  console.log("validation failed:", e.errors);
}