RizTech Academy logo
RizTech Academy
Getting StartedLesson 3 of 320 min

Running JavaScript: script tags and Node

Everything you typed into the console is gone. Reload the page and your variables vanish, which is fine for a question and useless for a programme. Code you want to keep goes in a file — and there are two completely different places to run that file.

A file inside a page

Make a folder with two files in it. First the page:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Tiffin tracker</title>
  </head>
  <body>
    <h1 id="heading">Loading…</h1>
    <script src="app.js" defer></script>
  </body>
</html>

Then app.js beside it:

const plates = 3;
const ratePerPlate = 80;

document.getElementById('heading').textContent =
  `${plates} plates today — ₹${plates * ratePerPlate}`;

Open index.html in your browser by double-clicking it. The heading reads 3 plates today — ₹240.

Type it in rather than copying it. The muscle memory for document.getElementById is worth having, and you will discover your own typos, which is the actual skill.

Three things to notice. The script tag has src pointing at a separate file, because mixing JavaScript into the page itself gets unmanageable fast. The JavaScript reached into the page and changed it — that is the DOM, and module 7 is all of it. And the backtick string with ${...} inside it is a template literal, which arrives properly in module 2.

Where the script tag goes, and the bug you get for free

Move the script tag into the head and take defer off:

<head>
  <meta charset="utf-8" />
  <title>Tiffin tracker</title>
  <script src="app.js"></script>
</head>

Reload. The heading still says Loading…, and the console says:

Uncaught TypeError: Cannot set properties of null (setting 'textContent')

This is the single most common beginner bug in browser JavaScript, and it is worth understanding rather than memorising a fix.

The browser reads the page top to bottom. When it hits a plain script tag it stops, fetches the file, and runs it completely before continuing. At that moment the h1 further down the page does not exist yet. So document.getElementById('heading') finds nothing and returns null, and null has no textContent to set.

Read the error again with that in mind. "Cannot set properties of null" is the browser telling you precisely this: you asked for an element, you got nothing, and you tried to use the nothing.

The fix is defer, which is why it was there to begin with:

<script src="app.js" defer></script>

defer means: download the file now, but do not run it until the page has been fully parsed. The element exists by then.

Form When it runs Use it when
<script src="…"> Immediately, blocking the page Almost never.
<script src="…" defer> After the page is parsed, in order The default choice.
<script src="…" async> As soon as it downloads, order not guaranteed Independent things like analytics.
<script type="module" src="…"> After parsing, deferred automatically Module 9, when you split files up.

Putting the tag at the very end of body works too, and you will see it in older code. defer is better: it downloads in parallel with the page rather than after it, which on a mid-range phone on mobile data is a difference you can feel.

One trap while you are here. When you start using type="module" in module 9, opening the file by double-clicking stops working — you get a CORS error, because modules are not allowed over file://. That needs a local server, and module 9 sets one up. Until then, plain defer and double-clicking is fine.

If a change to app.js seems to have no effect, the browser is caching it. Hard reload with Ctrl + Shift + R, or Cmd + Shift + R on macOS.

The other place: Node

The browser is not the only home for JavaScript. Node.js runs it on your machine with no page involved.

Get it from nodejs.org and take the version marked LTS — this course assumes Node 22 or newer. On macOS or Linux, nvm is worth the extra ten minutes if you expect to juggle versions later. Check it worked:

node --version
npm --version
v22.14.0
10.9.2

Two commands because Node brings npm with it, which is module 9.

Now make total.js:

const orders = [3, 5, 2, 4];
const ratePerPlate = 80;

let plates = 0;
for (const count of orders) {
  plates += count;
}

console.log(`${plates} plates this week — ₹${plates * ratePerPlate}`);

Run it:

node total.js
14 plates this week — ₹1120

No browser, no page, no script tag. Node also has its own console: type node with no filename for a REPL like the browser console, and .exit or Ctrl + D to leave.

The two environments are genuinely different

This is the distinction that causes the most wasted time early on, so be explicit about it.

Put console.log(document) in a file and run it with node:

ReferenceError: document is not defined

Not a broken installation. There is no document, because there is no page. window, alert and every other browser instruction are equally absent.

It goes the other way too: Node can read files off your disk and listen on a port, and browser JavaScript cannot, because a web page being able to read your documents would be a catastrophe.

Browser Node
document, window Yes No
Change a page, handle clicks Yes No
Read and write local files No Yes
Listen on a port No Yes
console.log, fetch Yes Yes
The language itself Identical Identical

The last row is the important one. Loops, functions, objects, async/await — all the same. Only the surroundings change.

So when a snippet from the internet fails with "document is not defined", you have not found a bug. You have put browser code into Node.

Which one to use on this course

Both, and the lessons always say which.

  • Modules 2 to 6 and 9 — the language itself. Use Node. It is a faster loop: edit, node file.js, read the output. No page to maintain.
  • Module 7, the DOM — browser, obviously. There is a page.
  • Module 8, async — both. fetch works in each.
  • Module 10, the capstone — browser, because it is a page somebody uses.

For VS Code, install it and open your folder with it. You do not need an extension to start. Its built-in terminal (Ctrl + backtick) is where node total.js goes, which saves switching windows constantly.

Check your work

The page with defer shows "3 plates today — ₹240". 3 × 80 = 240, written into the h1 by textContent.

With the script in head and no defer, you get Uncaught TypeError: Cannot set properties of null (setting 'textContent'). The script ran before the h1 was parsed, so getElementById returned null, and null has no properties to set. Adding defer fixes it because it delays execution until the page is parsed.

Reading instead of setting gives a different message. heading.textContent on a null reports Cannot read properties of null (reading 'textContent'). Same cause, and the word "read" or "set" tells you which line to look at.

At ₹95 and 7 plates the total is ₹665. If you predicted ₹665 before reloading, you are reading the code rather than the output, which is the habit worth building.

node total.js prints "14 plates this week — ₹1120". 3 + 5 + 2 + 4 = 14 plates, times ₹80 = ₹1120.

console.log(document) under Node gives ReferenceError: document is not defined. There is no page in Node, so there is no document. window and alert fail the same way. Nothing is wrong with your installation.

Which environment for a click handler? The browser. Node has nothing to click.

Practice

  1. Build the two-file page exactly as above and confirm the heading changes. Type it, do not paste it.
  2. Cause the bug on purpose. Move the script into head, remove defer, reload, and read the error. Then put defer back. Do not skip this — you will meet this error for real, and you want to recognise it in one second rather than twenty minutes.
  3. With the script back in head and defer on it, confirm the page works again. This proves it is defer doing the work, not the tag's position.
  4. Change the rate to ₹95 and the plates to 7, and predict the total before reloading. Then check.
  5. Install Node and confirm both node --version and npm --version answer.
  6. Write and run total.js. Then add a fifth day to the array and predict the new total before running it.
  7. Put console.log(document) in a file, run it with node, and read the error. Then try window and alert and confirm they fail the same way.
  8. Finish the experiment from last lesson. Put let total = 100; twice in a file and run it with node. You get SyntaxError: Identifier 'total' has already been declared — the error the console refused to give you. Now you have seen both halves of the trap.
  9. Open the Node REPL with node, work out 80 * 14 in it, and leave with .exit. Note that it behaves like the browser console but has no document.

That is module one. You know where JavaScript runs, what it is not for, how to use the console without being frightened by undefined, and how to get code into a file in both environments — including why a script in the head finds nothing.

Next module: the language itself. Variables, types, and the coercion rule behind "5" + 2 giving "52" — starting with why var was retired and what replaced it.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship