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.
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.
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.
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? No04Create 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.
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 styles05Install, 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.
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.
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.
<!-- before: <script> ...your Phase 1-3 code... </script> -->
<script type="module" src="/src/main.js"></script>
</body>Try it yourself
- 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 whatpackage-lock.jsonpinned, whilepackage.jsonholds the ranges (^7.9.0`). - 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"]torows. Both printallowed: a caret allows any newer minor or patch in the same major version. Then add["^1.2.3", "2.0.0"]: it printsrefused. - 3
Start a second server
Leave
npm run devrunning, open a second terminal in the same folder and runnpm run devagain. The second one printsPort 5173 is in use, trying another one...and thenLocal: http://localhost:5174/. Both serve the same files. Stop each with Ctrl+C in its own terminal.
Code & diagrams
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
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 fetchA 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
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 refusedNot 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).
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.
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.
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".
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.0is 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.jsonis what makes a build reproducible, so commit it. For0.xversions the caret is narrower (^0.2.3stops before0.3.0) because0.xmeans "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 intonode_modules/.vite/deps, where D3 was pre-bundled once so its 30 small packages are not requested one by one).npm run buildis the other mode: it bundles, minifies and writesdist/. Production hosting servesdist/, never the dev server.
Remember this
- 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 withfetch, and refuse to let it import its own JavaScript files. Real data needs both. A tiny local web server (an address that starts withhttp://localhost) removes the problem, and Vite starts one with a single command. - 2
**What the browser does with
file://(measured in Chromium).**fetch("data.csv")from a double-clicked page logsFetch API cannot load file:///…/data.csv. URL scheme "file" is not supported.and rejects withTypeError: Failed to fetch. A<script type="module" src="./mod.js">that imports another file logsAccess 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
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
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), andlson macOS and Linux ordiron Windows (list what is here). - 5
npm and package.json. npm (Node Package Manager) downloads libraries.
npm install d3fetches D3 and everything D3 needs (38 packages on 15 October 2026) into a folder callednode_modules, and writes"d3": "^7.9.0"intopackage.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.jsonrecords the exact versions installed, so a teammate gets the same ones.node_modulesis only a download cache: never edit it, never copy it, never share it.npm installrebuilds it frompackage.json. - 6
Vite is the development server.
npm run devstarts it, and it prints ahttp://localhost:5173/address. It serves yourindex.html, rewritesimport * as d3 from "d3"so the browser finds the file insidenode_modules(browsers alone cannot resolve a bare name like"d3": they sayFailed to resolve module specifier "d3"), and reloads the page when you save. Everything in thepublic/folder is served at the site's root, sopublic/data/lumen/daily.csvis 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
Moving your Phase 1–3 code in.
index.htmlstays at the project root. The<script>code you wrote inside it moves intosrc/main.js, andindex.htmlloads 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 insrc/charts/. If you forgettype="module"and the file has animport, Chromium reportsUncaught SyntaxError: Cannot use import statement outside a module.
Explain it without notes
Why does fetch("data.csv") work from npm run dev but not from a double-clicked page?
What is the difference between package.json, package-lock.json and node_modules?
What does the ^ in "d3": "^7.9.0" mean, and why does it not accept 8.0.0?
Practice
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/.
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 --versionandnpm --versionprint version numbers.My
lumen-dashboardproject runs withnpm run dev, with D3 inpackage.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.csvopens in my browser.