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.
// 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.
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{
"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
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);
}