Lesson 33: Modules and Packages
Learning Objectives
- Split code across files with
exportandimport, and recognize the olderrequirestyle. - Explain what
package.jsonis for, and why ES modules need"type": "module". - Read object shorthand and destructuring, such as
const { total } = results. - Install a package from npm, and explain what
node_modulesandpackage-lock.jsonare. - Decide whether a package is worth installing, and check whether a built-in feature would do.
33.1 Code in More Than One File
As a project grows, one file gets long. The usual answer is to split it into modules: files that each hold related code, and share it with the others. You did something similar in JADE, in Lesson 28. Sharing and Reusing Code, with jade_modules. Node has a standard way to do it, and it's the same way modern JavaScript does it in browsers too.
Here's the question, in a new chat with your learner profile from Lesson 32. Installing Node and Running Scripts:
I have a file garden.js with two functions, totalKilograms(harvests) and kilogramsByBed(harvests). I want to use them from another file, report.js, in the same folder. How do I do that?sendGood question! This uses a Node.js feature you haven't covered yet: modules — the system Node uses to share code between files. Let me walk you through it.
Step 1: Export the functions in garden.js
At the bottom of
garden.js, add:module.exports = { totalKilograms, kilogramsByBed }
module.exportsis a special object Node gives every file.- Whatever you put on it becomes available to other files that load this one.
{ totalKilograms, kilogramsByBed }is a shorthand for{ totalKilograms: totalKilograms, kilogramsByBed: kilogramsByBed }— it packages both functions into one object.Step 2: Import them in report.js
At the top of
report.js, add:const { totalKilograms, kilogramsByBed } = require("./garden.js")
require("./garden.js")runs that file and gives you back whatever it put inmodule.exports.const { totalKilograms, kilogramsByBed } = ...pulls those two functions out of that object and gives you local names for them, so you can call them just like normal functions.- The
./means "look in this same folder" — it's a relative path, like the ones you've used for web pages.Step 3: Use them
Now in
report.jsyou can write:const harvests = [ ["Tomatoes", "A", 5], ["Beans", "B", 3] ] console.log(totalKilograms(harvests)) console.log(kilogramsByBed(harvests))Running it
In the terminal, make sure you're in the folder with both files, then run:
node report.jsWant me to show the full contents of both files together so you can see it end to end?
Read. garden.js puts its functions on module.exports, and report.js gets them with require("./garden.js"). The ./ means "in this folder," the relative path you know. Two pieces of syntax are new, and the assistant explained both:
{ totalKilograms, kilogramsByBed }is an object written in shorthand. When a property has the same name as the variable holding its value, you can write the name once. It means{ totalKilograms: totalKilograms, kilogramsByBed: kilogramsByBed }.const { totalKilograms, kilogramsByBed } = require(...)is destructuring: it creates two variables and fills each with the property of the same name from the object on the right. It's a shorter way of writingconst garden = require(...), thenconst totalKilograms = garden.totalKilograms, and so on.
Try both here:
const bed = "B3"
const kilograms = 14.4
// shorthand: the property names come from the variable names
const result = { bed, kilograms }
console.log(result)
// destructuring: variables filled from the properties of the same names
const { bed: whichBed, kilograms: howMuch } = result
const { kilograms: total } = result
console.log(whichBed, howMuch, total)
(A colon inside destructuring gives the variable a different name from the property, as bed: whichBed does, so that this example doesn't clash with the bed variable above.)
Destructuring — Creating variables from the properties of an object, or the items of an array, in one statement:
const { crop, bed } = planting.
33.2 Two Ways to Share Code
The reply works, but it uses the older of two module systems. So ask for the other:
My textbook uses import and export instead of require and module.exports. How would I write it that way?sendGood catch — Node supports two module systems, and your textbook is using the newer one: ES modules (
import/export), instead of the older CommonJS style (require/module.exports). Here's how to write it that way.Step 1: Export the functions in garden.js
Instead of a separate
module.exportsline, you putexportin front of each function:export function totalKilograms(harvests) { // ...existing code } export function kilogramsByBed(harvests) { // ...existing code }
exportmarks a function as available to other files.Step 2: Import them in report.js
At the top of
report.js:import { totalKilograms, kilogramsByBed } from "./garden.js"
- This pulls in just those two names from
garden.js.- Notice there's no
constand norequire—importis its own statement, not a value you're storing.Step 3: Tell Node to use this system
By default, Node expects the older
requirestyle. To useimport/export, you need a small config file.Create a file named
package.jsonin the same folder, with this content:{ "type": "module" }
- This tells Node: "treat
.jsfiles in this project as ES modules," which turns onimport/exportand turns offrequire.Running it
Same as before:
node report.jsWant me to show the full contents of both files together with this style, so you have a working example to compare against?
That's exactly right. JavaScript has two module systems, and you'll see both:
- CommonJS, Node's original system:
require(...)andmodule.exports. Most Node code written before about 2020 uses it, so assistants, which learned from that code, often reach for it first. - ES modules, the standard built into the JavaScript language itself:
importandexport. Browsers use them, and modern Node projects do too. This book uses them.
ES module — A JavaScript file that shares code with
exportand uses others' code withimport. It's the language's standard module system, used by browsers and modern Node projects.
Node needs to be told which system a project uses, which is what "type": "module" in package.json does. (Naming a file with .mjs instead of .js also marks it as an ES module. Recent versions of Node can often detect import statements on their own, with a warning, but saying so in package.json makes it certain.) Mixing the two systems gives errors you'll learn to recognize:
SyntaxError: Cannot use import statement outside a modulemeans Node treated the file as CommonJS. Add"type": "module"topackage.json.ReferenceError: require is not defined in ES module scopemeans the reverse:requirein an ES module. Change it toimport.
When an assistant's reply has require, it's CommonJS, and it's worth asking for the import version, as you'll make a profile rule to do.
33.3 package.json
package.json is a project's settings file, in JSON. You can write it by hand, as the reply did, or create it by running npm init -y in the project folder, which fills in some standard fields. A typical one looks like this:
{
"name": "garden-scripts",
"version": "1.0.0",
"type": "module",
"scripts": {
"report": "node report.js"
},
"dependencies": {
"date-fns": "^4.4.0"
}
}
typechooses the module system.scriptsgives commands short names: with this one,npm run reportrunsnode report.js. For a project with several tools, it's a handy list of what the project can do.dependencieslists packages the project uses, which the next section explains.
33.4 Packages and npm
A package is code someone has published for others to use, and npm, which comes with Node, is the tool and the online registry for sharing them. There are millions of packages, for nearly everything. Here's a question about one, in a new chat:
My project uses import and export, with type set to module in package.json. I want my garden report to show dates like Saturday, June 12. Is there an npm package that does that, and how do I use it?sendFor this, you don't need an npm package — JavaScript itself has a built-in tool for formatting dates:
Intl.DateTimeFormat. It's not on your "known" list, so here's a quick explanation before I show you the code.
Intl.DateTimeFormattakes a locale (like"en-US") and an options object describing which parts of the date you want (weekday, month, day), and gives you back a formatter. Calling.format()on it with aDateturns that date into text.const gardenDate = new Date(2025, 5, 12) // June 12, 2025 (month is 0-based, so 5 = June) const formatter = new Intl.DateTimeFormat("en-US", { weekday: "long", // "Saturday" month: "long", // "June" day: "numeric" // "12" }) const dateText = formatter.format(gardenDate) console.log(dateText) // Saturday, June 12What each line does: -
new Date(2025, 5, 12)creates the date you already know how to make. -new Intl.DateTimeFormat("en-US", { ... })builds a formatter set up to spell out the weekday and month name. -formatter.format(gardenDate)runs that date through the formatter and returns the text.This works with plain Node.js — no
npm install, no import needed, sinceIntlis a global built into JavaScript.If you'd rather use a package anyway (some projects do, for more formatting options), the common ones are
date-fnsanddayjs. Both would neednpm installand animportline, and their docs are npm's own registry pages I'd want to point you to rather than guess exact syntax from memory. But for something as simple as "Saturday, June 12,"Intl.DateTimeFormatshould do everything you need.Want to try it with a date read from a sheet or file, or is this just for a single report date?
This is good advice, and it's the first thing to know about packages: check whether JavaScript can already do the job. Intl.DateTimeFormat is built in, in Node and in browsers, and formats dates in any language's style. You can try it right here.
But check the example before trusting it. The comment says the result is "Saturday, June 12," for June 12, 2025. Run it:
const formatter = new Intl.DateTimeFormat("en-US", {
weekday: "long",
month: "long",
day: "numeric"
})
console.log(formatter.format(new Date(2025, 5, 12)))
console.log(formatter.format(new Date(2027, 5, 12)))
June 12, 2025 was a Thursday. The assistant wrote the output it expected, from your request, instead of the output the code produces. (June 12, 2027, in the club's season, is the Saturday.) A comment that shows a result is a claim, and running the code is how you check it.
33.4.1 Installing a package
Built-in features won't always be enough. When you need a package, installing one looks like this, run in the project folder:
npm install date-fns
Three things happen:
- The package, and any packages it depends on, are downloaded into a folder named
node_modulesin your project. - It's added to
dependenciesinpackage.json. - A file named
package-lock.jsonrecords the exact version of every package installed, so the project can be installed the same way again later.
Then you import it by name, without ./, and use it according to its documentation:
import { format } from "date-fns"
console.log(format(new Date(2027, 5, 12), "EEEE, MMMM d"))
That prints Saturday, June 12. The "EEEE, MMMM d" is a pattern from date-fns's documentation: EEEE is the weekday's full name, MMMM the month's, and d the day. The assistant said it would rather point you to the documentation than guess this syntax from memory, which, as you know by now, is a sign of a good answer.
node_modules can hold thousands of files. Never edit them, and don't copy or share the folder: anyone with your package.json and package-lock.json can recreate it by running npm install. If you use Git later, node_modules is always left out.
npm — The package manager that comes with Node, and the online registry of published packages.
npm install nameadds a package to a project.
33.5 Is a Package Safe?
Installing a package means running a stranger's code on your computer. So, continuing the chat:
How do I know whether an npm package is safe to install?sendGood instinct to ask before running
npm install— that command downloads code from strangers and runs setup scripts on your computer automatically. Here are practical things to check:Before installing:
- Look at weekly downloads and GitHub stars on the npmjs.com page for the package. A package with millions of downloads (like
date-fns) has been used and scrutinized by a lot of people. One with 12 downloads a week is riskier — not necessarily bad, but unproven.- Check when it was last updated. A package untouched for 5+ years might be abandoned, though for something simple that's not always bad. A package updated yesterday by a brand-new account is worth more caution.
- Look at the number of dependencies. A date-formatting package that pulls in 40 other packages has a bigger "supply chain" — more chances something in that chain is compromised. Fewer dependencies is generally safer.
- Read the README quickly. Does it look like a real project with documentation, or a thin wrapper with no explanation?
- Check who maintains it. Is it a known organization (like the
date-fnsteam) or a solo unnamed account?A command that helps:
npm auditRun this in your project folder after installing. It checks your installed packages against a database of known security problems and tells you if anything needs an update.
General rule of thumb:
For something small and common — like date formatting — stick to well-known, heavily-downloaded packages (
date-fns,dayjs,lodash, etc.). For obscure one-off packages solving a tiny problem, ask yourself whether you could just write the few lines of code yourself instead, like we did withIntl.DateTimeFormatabove. Fewer installed packages means fewer strangers' code running on your machine.Want me to walk through checking a specific package you're considering?
That's a solid checklist, and it's the same question you asked about gists in Lesson 28. Sharing and Reusing Code and plugins in Lesson 31. Scripting Your Notes in Obsidian: how widely used is it, is it maintained, and who's behind it? Two more things to add:
- Check the name carefully. Attackers publish packages with names one letter off from popular ones, hoping for typos, such as
date-fsnfordate-fns. An assistant can also suggest a package name that doesn't exist, or suggest a real package for the wrong job. Look the package up on npmjs.com before installing it. - Installing can run code. Some packages run a setup script when installed, before you've used them at all. That's another reason to install only packages you've checked.
The assistant's closing advice is the most useful: for small jobs, a few lines of your own code, or a built-in feature, is often better than a package. Every package you add is code you're trusting, and code that can break when it updates.
33.6 Your Learner Profile
The first rule heads off the most common module mix-up. The second builds this lesson's best advice into every request.
33.7 Summary
Modules let a project's code live in several files. ES modules share code with export and use it with import, and need "type": "module" in package.json; the older CommonJS system, with require and module.exports, is still common in code and AI replies. package.json holds a project's settings, including its module system, named scripts and dependencies. npm installs packages into node_modules, and package-lock.json records their exact versions. Before installing a package, check whether JavaScript already has what you need, as Intl.DateTimeFormat did for dates, and check the package's name, popularity, maintenance and authors. Comments that show a result are claims, so run the code. Next, you'll use Node's built-in modules to work with files and folders.