Command Palette

Search for a command to run...

Hectal
PHASE 4Beginner ~29 min· topic 1 of 7

Topic 4.1

Moving to Your Computer: Node, Vite and npm

In one line

A page you double-click cannot read data files or import its own code, because the browser treats file:// pages as unsafe. A small local server (Vite, run by Node) fixes that, and npm fetches D3 for you. This topic sets up the project the rest of the course uses.

Think of it like this

Until now you cooked in a shared kitchen: the editor on this page, then one file you opened by double-click. A shared kitchen is fine for a sandwich. For a real meal you want your own kitchen with a proper stove, and a delivery service that brings the ingredients. Today you move in: Node is the stove, npm is the delivery service, and Vite is the helper who opens the shop window so visitors can see what you cook.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

file://
The address style of a file opened from your disk. Browsers give such a page almost no permissions.
HTTP server
A program that answers browser requests for files. Vite includes one for development.
Node.js
A program that runs JavaScript outside the browser. npm and Vite are written for it.
LTS
Long Term Support: the stable Node line that gets fixes for years. Install this one.
terminal
A text window where you type commands. Every command runs in the folder the terminal is in.
npm
Node's package manager: it downloads libraries such as D3 and runs project commands such as npm run dev.

Step by step

01See why a double-clicked page cannot read data

A double-clicked page has a file:// address and an "origin" of null: the browser treats it as a page that could be anything, so it blocks its reading of other files. A page served at http://localhost:5173 has a real origin, and it may read files from the same origin.

The first live example below does the same five reads from this page: only addresses on the page's own site work. A relative name like daily.csv, another server, and a file:// address all fail with TypeError: Failed to fetch.

See why a double-clicked page cannot read datadiagram
Rendering diagram…

02Install Node and check it

Download the LTS installer from nodejs.org and run it with the default choices. Close and reopen your terminal (an already open one does not see the new program), then check both tools. Your versions will be newer or the same; any Node 22 or 24 line works.

If you see The term 'node' is not recognized as the name of a cmdlet, function, script file, or operable program (PowerShell) or command not found (macOS, Linux), the install did not finish or the terminal was not reopened.

terminal
$ node --version
npm --version
── expected output ──
v24.11.0
11.6.1
Captured on 15 October 2026. The newest Node 24 then was 24.22.0.

03Create the project with Vite

Go to the folder where you keep projects (for example cd Documents) and run the command below. It asks four questions, one at a time. Move with the arrow keys and press Enter. The prompts and choices are the ones create-vite 9.2.1 asks.

The last question, "Install with npm and start now?", needs the answer No: you will run the two steps by hand to see what each one does.

the questions npm create vite askswhole filetext

Written from create-vite 9.2.1's source: a typed capture of arrow-key menus cannot be saved here.

$ npm create vite@latest
Project name:   lumen-dashboard
Select a framework:   Vanilla       <- the first choice in the list
Select a variant:     JavaScript    <- the second choice: press the down arrow first
Install with npm and start now?   No

04Create it without questions, and see what you get

The same result in one command: after --, the options go to create-vite. This is the real output of that command on 15 October 2026 (npm may print two header lines first).

Vite made a small starter: an index.html with an empty <div id="app">, a src/main.js, a counter button and a few images. You will delete the starter parts as the dashboard replaces them.

what create-vite made (starter files marked)whole filetext
lumen-dashboard/
├─ .gitignore       lists files git must skip, such as node_modules
├─ index.html       the page: <div id="app"> and <script type="module" src="/src/main.js">
├─ package.json     name, scripts (dev, build, preview), and devDependencies: vite
├─ public/
│  ├─ favicon.svg   starter file
│  └─ icons.svg     starter file
└─ src/
   ├─ assets/       starter pictures
   ├─ counter.js    starter file
   ├─ main.js       starter page code
   └─ style.css     starter styles
terminal
$ npm create vite@latest lumen-dashboard -- --template vanilla --no-interactive
── expected output ──
│
◇ Scaffolding project in D:\projects\lumen-dashboard...
│
└ Done. Now run:
 
cd lumen-dashboard
npm install
npm run dev
Your folder path will differ.

05Install, then add D3

npm install reads package.json and downloads Vite and the 39 packages it needs into node_modules. npm install d3 adds D3 (38 more packages) and records it in package.json. Run both inside the project folder: cd lumen-dashboard first.

After the second command package.json has a dependencies block with "d3": "^7.9.0". The found 0 vulnerabilities line is npm's security check of what it just installed.

terminal
$ cd lumen-dashboard
npm install
npm install d3
── expected output ──
added 40 packages, and audited 41 packages in 3s
 
18 packages are looking for funding
run `npm fund` for details
 
found 0 vulnerabilities
 
added 38 packages, and audited 79 packages in 2s
 
18 packages are looking for funding
run `npm fund` for details
 
found 0 vulnerabilities
Timings differ on your machine. "Looking for funding" is a request from package authors, not a problem.

06Start the server and open the page

npm run dev starts Vite. Open the Local address in your browser. You should see a white page with a purple and yellow picture, the words "Get started" and a button "Count is 0". That page is the starter, and it proves the server works. Press Ctrl+C in the terminal to stop the server.

If port 5173 is already taken (a second copy of the server, or another program), Vite does not fail: it says Port 5173 is in use, trying another one... and uses 5174. Always open the address the latest message prints. To choose a port yourself: npm run dev -- --port 3000.

terminal
$ npm run dev
── expected output ──
> lumen-dashboard@0.0.0 dev
> vite
 
 
VITE v8.3.4 ready in 645 ms
 
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
A second server started while the first runs prints "Port 5173 is in use, trying another one..." and then Local: http://localhost:5174/.

07Put the data in public/ and move your code in

Create public/data/lumen/ and put the Lumen files in it. The script in the first code block below downloads all 13 of them from the course site with Node's built-in fetch. Save it as download-data.mjs next to package.json and run node download-data.mjs once (it needs Node 18 or newer). The data folder is about 680 KB.

Then move your page code. In index.html replace the inline <script>…</script> by one line, and paste the old code into src/main.js. Save: Vite reloads the page by itself.

index.html (the end of <body>)add to filehtml
  <!-- before: <script> ...your Phase 1-3 code... </script> -->
  <script type="module" src="/src/main.js"></script>
</body>

Try it yourself

  1. 1

    Ask npm what is installed

    In your project folder run npm ls --depth=0. It lists only the libraries you asked for, not the dozens of packages underneath:

    text lumen-dashboard@0.0.0 D:\projects\lumen-dashboard +-- d3@7.9.0 -- vite@8.3.4 `` The exact versions here are what package-lock.json pinned, while package.json holds the ranges (^7.9.0`).

  2. 2

    Try a version range of your own

    In the second example add the pair ["^7.9.0", "7.9.1"] and ["^1.2.3", "1.9.0"] to rows. Both print allowed: a caret allows any newer minor or patch in the same major version. Then add ["^1.2.3", "2.0.0"]: it prints refused.

  3. 3

    Start a second server

    Leave npm run dev running, open a second terminal in the same folder and run npm run dev again. The second one prints Port 5173 is in use, trying another one... and then Local: http://localhost:5174/. Both serve the same files. Stop each with Ctrl+C in its own terminal.

Code & diagrams

Which addresses can a page read with fetch? New tab

This page is served from the course site, so only that site's own files can be read, just as your page at localhost:5173 can read its own public/ folder. A wrong path is an answer (404), not an exception: only the three blocked reads throw.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

a file on this site -> 200 OK
a file that is not there -> 404 not found
a relative name (like a double-clicked page) -> TypeError: Failed to fetch
your own Vite server -> TypeError: Failed to fetch
a file:// address -> TypeError: Failed to fetch
What does ^7.9.0 in package.json allow? New tab

A teaching copy of the rule, checked against npm's own semver library for these nine pairs. It leaves out pre-release versions and ^0.0.x.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

^7.9.0 7.9.0   allowed
^7.9.0 7.10.2  allowed
^7.9.0 8.0.0   refused
^7.9.0 7.8.9   refused
~7.9.0 7.9.5   allowed
~7.9.0 7.10.0  refused
7.9.0  7.9.1   refused
^0.2.3 0.2.9   allowed
^0.2.3 0.3.0   refused
download-data.mjsjs

Not live (it writes files to your disk). Tested against a local copy of the course data: it saved all 13 files, and with a wrong address it stops with `stations.csv: the server answered 404`.

// Run once from your project folder:  node download-data.mjs
import { mkdir, writeFile } from "node:fs/promises";

const BASE = process.env.BASE ?? "https://hectal.in/data/lumen/";
const FILES = [
  "stations.csv", "daily.csv", "daily-dirty.csv", "hourly-june.csv", "station-month.csv", "trips-10k.csv",
  "bikes.json", "status.json", "flows.json", "fleet.json", "routes.json", "ardenvale.topojson", "world-110m.json",
];

await mkdir("public/data/lumen", { recursive: true });
for (const name of FILES) {
  const response = await fetch(BASE + name);
  if (!response.ok) throw new Error(name + ": the server answered " + response.status);
  const bytes = Buffer.from(await response.arrayBuffer());
  await writeFile("public/data/lumen/" + name, bytes);
  console.log("saved", name, bytes.length, "bytes");
}

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Fetch a data file from a double-clicked page

Put a page with <script>fetch("data.csv").then(r => r.text()).catch(e => console.log(e.message))</script> next to a file data.csv and double-click it. Open the Console (F12).

terminal
$ Console (Chromium)
── what you'll see ──
Fetch API cannot load file:///C:/lumen-dashboard/data.csv. URL scheme "file" is not supported.
Failed to fetch

Break #2

Import your own file from a double-clicked page

Make <script type="module" src="./main.js"></script> where main.js has import "./other.js". Double-click the page.

terminal
$ Console (Chromium)
── what you'll see ──
Access to script at 'file:///C:/lumen-dashboard/other.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, chrome-untrusted, data, http, https.

Break #3

Run a command in the wrong folder

Open a terminal in a folder that has a package.json without a dev script (or one folder above your project) and run npm run dev.

terminal
$ npm run dev
── what you'll see ──
npm error Missing script: "dev"
npm error
npm error To see a list of scripts, run:
npm error npm run
npm error A complete log of this run can be found in: C:\Users\you\AppData\Local\npm-cache\_logs\2026-10-15T09_13_00_573Z-debug-0.log

Break #4

Forget type="module"

In index.html write <script src="/src/main.js"></script> (no type) while main.js starts with import * as d3 from "d3".

terminal
$ Console (Chromium)
── what you'll see ──
Uncaught SyntaxError: Cannot use import statement outside a module

Myth vs fact

Myth

I can keep double-clicking index.html: it works for the examples.

Fact

It works only while the page needs no other file. The moment you fetch a CSV or import a second JavaScript file, the browser blocks it. That is why this phase moves you to a local server.

Myth

node_modules is my project's code, so I must keep it and share it.

Fact

It is a download cache of other people's code, often tens of megabytes. Your project is package.json, package-lock.json, index.html and src/. Share those and let npm install rebuild the folder.

Myth

Installing Node means I have to write a back-end now.

Fact

You install Node to run tools. The dashboard's own code still runs only in the browser, and nothing here needs a server program of your own.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    ^7.9.0 is a promise by the library author, not a guarantee. D3 follows semantic versioning (major.minor.patch), so a new 7.x should not break you. package-lock.json is what makes a build reproducible, so commit it. For 0.x versions the caret is narrower (^0.2.3 stops before 0.3.0) because 0.x means "anything may change".

  • ▸

    Vite's dev server does not bundle your code. It sends each file as its own ES module and rewrites only the bare imports ("d3" becomes a path into node_modules/.vite/deps, where D3 was pre-bundled once so its 30 small packages are not requested one by one). npm run build is the other mode: it bundles, minifies and writes dist/. Production hosting serves dist/, never the dev server.

Remember this

  1. 1

    The problem this solves: a page opened by double-click has an address that starts with file://. Browsers refuse to let such a page read other files with fetch, and refuse to let it import its own JavaScript files. Real data needs both. A tiny local web server (an address that starts with http://localhost) removes the problem, and Vite starts one with a single command.

  2. 2

    **What the browser does with file:// (measured in Chromium).** fetch("data.csv") from a double-clicked page logs Fetch API cannot load file:///…/data.csv. URL scheme "file" is not supported. and rejects with TypeError: Failed to fetch. A <script type="module" src="./mod.js"> that imports another file logs Access to script at 'file:///…/mod.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, chrome-untrusted, data, http, https. An inline module with no imports still runs. Firefox and Safari word these errors differently, but all three refuse.

  3. 3

    Node.js runs JavaScript outside a browser, on your computer. You will not write Node programs here. You install it because the tools you need (npm and Vite) are Node programs. Install the LTS version (Long Term Support: the stable line that gets fixes for years) from nodejs.org. On 15 October 2026 the LTS line is 24, named "Krypton", and its newest release was 24.22.0; Node 26 was the "Current" line, not yet LTS (both read from nodejs.org's release list that day). Vite 8 needs Node 20.19 or newer, or 22.12 or newer, so any current LTS works.

  4. 4

    The terminal is a window where you type commands instead of clicking. Open it in the right folder, because every command runs "where you are". On Windows, press the Start key, type PowerShell, press Enter (or in VS Code use Terminal → New Terminal, which opens already inside your folder). On macOS, press Cmd+Space, type Terminal, press Enter. On Linux, press Ctrl+Alt+T. Three commands cover 90 % of the work: cd folder (go into a folder), cd .. (go up one), and ls on macOS and Linux or dir on Windows (list what is here).

  5. 5

    npm and package.json. npm (Node Package Manager) downloads libraries. npm install d3 fetches D3 and everything D3 needs (38 packages on 15 October 2026) into a folder called node_modules, and writes "d3": "^7.9.0" into package.json, the project's list of what it uses. The ^ means "7.9.0, or any later 7.x", but never 8 (a new major version may break your code). package-lock.json records the exact versions installed, so a teammate gets the same ones. node_modules is only a download cache: never edit it, never copy it, never share it. npm install rebuilds it from package.json.

  6. 6

    Vite is the development server. npm run dev starts it, and it prints a http://localhost:5173/ address. It serves your index.html, rewrites import * as d3 from "d3" so the browser finds the file inside node_modules (browsers alone cannot resolve a bare name like "d3": they say Failed to resolve module specifier "d3"), and reloads the page when you save. Everything in the public/ folder is served at the site's root, so public/data/lumen/daily.csv is reachable at /data/lumen/daily.csv. That is exactly the URL the examples on this page use, so your fetch code moves over unchanged.

  7. 7

    Moving your Phase 1–3 code in. index.html stays at the project root. The <script> code you wrote inside it moves into src/main.js, and index.html loads it with <script type="module" src="/src/main.js">. Your <style> can stay where it is. From now on each chart gets its own file in src/charts/. If you forget type="module" and the file has an import, Chromium reports Uncaught SyntaxError: Cannot use import statement outside a module.

Explain it without notes

01

Why does fetch("data.csv") work from npm run dev but not from a double-clicked page?

02

What is the difference between package.json, package-lock.json and node_modules?

03

What does the ^ in "d3": "^7.9.0" mean, and why does it not accept 8.0.0?

Practice

01

Write the exact terminal commands, in order, that take you from an empty folder to a running dashboard project with D3 installed. Then say which of them you would run again on a teammate's computer that already has your package.json and src/.

02

A teammate sends you a zip of the project that includes node_modules (tens of megabytes for this project, often far more). What do you do, and what do you tell them to send next time?

Trade-offs

  • ↔

    A build tool and a package manager add setup and a vocabulary to learn, and give you real libraries, module imports, a local server and a deployable build. For one throwaway chart a single HTML file with a CDN script is simpler; for a dashboard that grows over 15 phases, the project folder pays back in the first week.

Done when you can

  • I can say why a double-clicked page cannot fetch a file or import a module.

  • I installed Node LTS and node --version and npm --version print version numbers.

  • My lumen-dashboard project runs with npm run dev, with D3 in package.json.

  • I can read Vite's printed address and know what to do when the port is busy.

  • The Lumen data is in public/data/lumen/ and /data/lumen/daily.csv opens in my browser.