Every page after this one will hand you a program and expect you to change it. You cannot do that until you can read one, so that is what this page is for. All you need going in is a terminal, the window where you type commands, and a text editor. Every word that is new gets explained the first time it appears.
Here is the shape underneath everything on this track. A program is a text file of instructions. A runtime is the program that reads that file and does what it says, one line at a time, from the top.
This track uses Node as its runtime. Node runs JavaScript, the language web pages are written in, without needing a browser. You type one command, Node opens the file, and the instructions run.
flowchart LR you["you type<br/>node hello.mjs"] --> node["Node<br/>opens the file"] --> lines["reads each line<br/>top to bottom"] --> out["prints to<br/>the terminal"]
Install Node and prove it is there
If you do not have Node yet, download it from nodejs.org and pick the version marked LTS, which stands for long-term support, the one that will keep working for years. Run the installer. Then open a terminal and type:
node --version
You should see a line starting with v and a number, such as v22.23.2. The examples on this page were run on that version. If the terminal says the command was not found, close it, open a new one, and try again, a terminal opened before the install finished does not know Node exists yet.
One more word before the commands start: the shell. The terminal is the window; the shell is the program inside it that reads what you type. macOS and Linux usually run bash or zsh, Windows runs PowerShell, and a few commands differ between them. Every command on this page works in all three; where a later page hits a difference, it shows both forms.
The smallest program
Make a folder anywhere, read-a-program works, and open it in your editor. Create a file called hello.mjs with one line in it. The ending .mjs tells Node the file is a module, a file that can borrow tools from other files; later pages rely on that. This course also calls its numbered pages modules; the two senses share a word and nothing else.
console.log("hello from a file");
console.log is the instruction that prints text. The quoted part is a string: the quotes mark where the text starts and ends, and they are not part of the text itself. Now go into the folder in your terminal and run the file:
cd read-a-program
node hello.mjs
hello from a file
That is a whole program: one file, one instruction, one line of output. Everything else on this page adds lines to that same idea.
Read one that does something
The next file lists the names in the folder it runs in. Read it before you run it, that is the habit every later page assumes. Two things to know going in. Lines that start with // are comments: notes for the person reading, which Node skips. And the comments say directory where this page says folder; they are the same thing under two names. The annotations under the file explain each new word; read them in order.
list-files.mjs Prints every name in the current folder, then how many there were.
// List the files in the folder this program runs in.
//
// "import" brings in a tool Node already has. readdir reads a directory and
// hands back the names inside it. The "node:" prefix means it is built in,
// not something you installed.
import { readdir } from "node:fs/promises";
// "." means "the folder I am in right now". readdir takes a moment, so the
// program has to wait for it; "await" is the word for that wait.
const names = await readdir(".");
// A loop: do the indented line once for every name in the list.
for (const name of names) {
console.log(name);
}
// One more line so you can see the program reached the end.
console.log(names.length + " entries"); hello.mjs The first program. One instruction.
// A program is a text file of instructions. Node reads it from the top and
// does each line in order. This one has one instruction: print a line.
console.log("hello from a file"); server.mjs A program that does not finish. It waits for a browser.
// The smallest server: a program that does not finish. It waits for a browser
// to ask for a page, answers, and keeps waiting until you stop it.
import { createServer } from "node:http";
// This function runs once per request. "req" is what the browser asked for;
// "res" is how you answer it.
const server = createServer((req, res) => {
console.log("a browser asked for " + req.url);
res.end("hello from a server");
});
// 3000 is a port: a numbered door on your machine. The browser knocks on it
// with the address http://localhost:3000. "localhost" means this computer.
server.listen(3000, () => {
console.log("waiting on http://localhost:3000 (press Ctrl+C to stop)");
}); Those programs have more comment than code at this stage, and that is deliberate, we want you reading before you run. Now run it:
node list-files.mjs
hello.mjs
list-files.mjs
2 entries
Two files in the folder, two names printed, and the count at the end. If you added other files there, they show up too. The program did not hard-code what was in the folder; it asked at run time.
Change one line and run it again
This is where reading turns into doing, and it is the step every later Build callout repeats. Change the line inside the loop so it prints found in front of each name:
console.log("found " + name);
Save, run node list-files.mjs again, and each line now starts with found. You changed one line; the output changed in exactly the way you expected. When that stops being true, when the output surprises you, look at the gap between what you thought a line did and what it actually does. That gap is where every bug lives.
Four steps, in this order. Every Build callout on this track climbs the same ladder: run it, read it, change one line, then build something new.
- Run it. Run each file as written and check the output matches what is printed above.
- Read one file. Read
list-files.mjsaloud, one line at a time, saying what each line does without looking at the annotations. - Change one line. Change the loop line to
console.log(names.indexOf(name) + 1 + ”. ” + name);, which numbers the lines, and run it again. Predict the output before you look. - Build. Make a third file,
count.mjs, that prints only the count and nothing else. You already have every line it needs.
A program that does not finish
One more idea shows up in server.mjs before you run it. The (req, res) => { ... } handed to createServer is a function: a block of lines that runs when something calls it, not when Node reads past it. Here the server calls it once per request, which is why its lines run again on every refresh. Handing a function over so something else can call it later is the most common move in the code ahead. Back in list-files.mjs , const names = ... made a variable: a name attached to a value so later lines can use it.
hello.mjs runs and stops. server.mjs runs and waits. Start it:
node server.mjs
waiting on http://localhost:3000 (press Ctrl+C to stop)
Notice what happened: the terminal does not give you the prompt back, the line where you type your next command. Later pages also use prompt for the text you send to Claude; the two senses share a word and nothing else. The program is still running, listening on port 3000. Open a browser and go to http://localhost:3000, where localhost means this computer. The page says hello from a server, and the terminal prints a browser asked for /, the browser asked, the program answered, and it logged what happened. Refresh the page and it logs again. Press Ctrl+C in the terminal to stop it.
That shape is what a server is: a program that waits for requests and answers them. Request means what the browser asked for; response means the answer. Every web app in this track is a server on one side and a browser on the other. Module 15 puts an agent behind the server, a Claude session that a program, rather than a person, starts and talks to.
What a package is
So far every tool came with Node: readdir, createServer. Most real projects need more than that. A package is code someone else wrote that you download into your project and import the same way. The Agent SDK in module 14 is a package. SDK is short for software development kit, a package made for building programs on top of someone else’s product.
Packages are managed by npm, a command installed alongside Node. In your folder, run:
npm init -y
Wrote to /Users/you/read-a-program/package.json:
{
"name": "read-a-program",
"version": "1.0.0",
...
}
That creates package.json, a file that lists your project’s name and, once you add any, the packages it depends on. The word for those is dependencies. The format inside the file is JSON: structured data written with curly braces, names, and values. The -y is a flag, a dash-word that changes what a command does, this one answers yes to every question npm init would otherwise ask, so it prints the file it wrote and stops.
npm install <name> downloads a package into a folder called node_modules and writes its name into package.json. You will run that for the first time on the next page, against the Agent SDK. Nothing to install here yet.
One error is waiting for you this week, and you get to meet it here first, while nothing is at stake. The practice box below starts with you standing inside the read-a-program folder. The answers are a script written into this page; nothing you type in it touches your machine. Run the six commands in order and watch the same command work, fail, and work again while the only thing that changes is the folder underneath you.
-
node hello.mjsrun the file while standing in its folder. It works. -
cd ..the two dots mean the folder above this one. This moves you out. -
node hello.mjsthe same command in the wrong folder. Watch it fail. -
pwdwhere am I? Not in read-a-program, which is the whole problem. -
cd read-a-programmove back in. -
node hello.mjsworking again. Nothing about the file or the command changed.
You open a terminal, type node hello.mjs, and get this:
node:internal/modules/cjs/loader:1433
throw err;
^
Error: Cannot find module '/Users/you/hello.mjs'The file exists. You can see it in your editor. What went wrong is the folder, a terminal is always standing in some folder, called its working directory, and node hello.mjs means “the file called hello.mjs in the folder I am standing in right now.” The path in the error, the full address of the place Node looked, is not where your file lives. Type cd followed by the path to your folder to move there. To see where you already are, type pwd, which works on macOS, Linux, and PowerShell; only the old Windows cmd.exe needs a bare cd instead. Then run it again. Every “cannot find” error on this track is this mistake or a typo in the filename; check the folder first.
Yes, all of it. Every language has a runtime you install, a file you run with one command, comments the runtime skips, a way to borrow tools, a loop, a way to wait for slow things, and a package manager with a file that lists dependencies. The names change: Python has pip and requirements.txt, Rust has cargo and Cargo.toml. The shape you learned on this page is the shape you will meet in all of them, and the wrong-folder error is the same error everywhere.
Two roads from here
Everything on this page is shared ground. From here the site branches two ways, and both paths come back together later. Module 14 puts a Claude agent inside a program you write, the SDK road, which the rest of this track walks. Build an MCP server takes the other road: one file that gives tools to any harness, using nothing beyond what this page taught plus one package. Read whichever pulls at you; each links to the other where their subjects meet.
Check yourself
node list-files.mjsprints2 entriesin one folder and9 entriesin another. Which line of the program is responsible for the difference, and which line prints it?- You start
server.mjs, and the terminal shows the waiting line and nothing else. Is the program broken, finished, or working? What would you do to find out? - Two people run
node hello.mjsfrom two different folders. One getshello from a file, the other gets an error. Without seeing either screen, name the one thing that differs between them.