πŸ“š Sanjay's Study Notes Home Roadmap HTML CSS JavaScript TypeScript Node SQL & Prisma React Tailwind Auth Apps Git & Deploy

05 Β· Node.js & Fastify β€” JavaScript leaves the browser

Until now, every line of your JavaScript has lived inside a browser tab. Phase 4 breaks it out: the same language, running directly on your computer, powering a server β€” a program that answers requests. Your Library app is about to get a backend.

Where the name comes from In 2009, Ryan Dahl took V8 β€” the JavaScript engine Google built for Chrome β€” and lifted it OUT of the browser, wrapping it so it could read files and talk over networks. A "node" is a point in a network β€” the name says what it's for: JS programs that live on the network, not on a page. Your machine already has it: you verified Node v24.13.1 back in Phase 0, and every time you ran tsc, Node was the thing actually running the compiler.

1. Same language, different world

Node is NOT a new language. It's a different place for the language you already know to run β€” with different powers, because the world around the code is different:

Browser JS (what you've done)Node JS (Phase 4)
Started bya <script> tag in a pagenode file.js in the terminal
Can seedocument, DOM, buttons, alert, prompt, localStoragefiles on disk, the network, environment β€” no DOM at all
Output goes tothe page + DevTools consolethe terminal
Who runs itevery visitor's machineYOUR machine (or a rented server)
Still the samevariables, functions, arrays, objects, map/filter/find, arrows, JSON.stringify/parse, console.log, Dates β€” everything from Phase 2/3 travels with you
Predict the crash (you know enough to) Put document.getElementById("bookRows") in a Node file and run it. What happens? β†’ ReferenceError: document is not defined β€” the same error family as your Task 2.1 code is not defined. There is no page, so there is no document. Node isn't "worse" β€” it simply lives where pages don't exist. Mirror image: the browser has no fs (file system), because letting web pages read your disk would be a disaster.
You already crossed this border once Session 5, your genuine question: "where in the browser do I run tsc?" β€” and the answer was the terminal, tsc is a program on your computer. That WAS Node: tsc is JavaScript running in Node. Phase 4 just makes you the author of such programs instead of a user.

2. Running Node β€” two ways

# 1. run a file (the normal way)
node hello.js

# 2. REPL β€” an interactive console, like DevTools but in the terminal
node
> 2 + 2
4
> [1,2,3].map(n => n * 5)
[ 5, 10, 15 ]
> .exit
REPL = Read, Eval, Print, Loop The same try-things-out habit you built in the browser console works here. When unsure what some Node code does, don't guess β€” open node and run it. Same rule as always: observe, don't predict-and-hope.

3. npm & package.json β€” how projects declare what they need

Why package managers exist Real apps stand on other people's code (Fastify, Prisma, React…). Before package managers, you downloaded zip files by hand and prayed the versions matched. npm (Node Package Manager) automates it: a registry of ~3 million packages + a file that records exactly which ones your project uses, so any machine can rebuild the setup with one command.
# inside your project folder β€” creates package.json (asks a few questions)
npm init

# install a package LOCALLY into this project
npm install fastify

After that, three things exist β€” know what each is:

ThingWhat it isYour rule
package.jsonyour project's identity card: name, scripts, and the dependencies list ("fastify: ^5…")you edit this (carefully)
node_modules/the actual downloaded code of every dependency (thousands of files)NEVER edit β€” regenerable, like app.js. Delete it any time; npm install rebuilds it from package.json
package-lock.jsonthe exact resolved versions, so installs are identical everywheredon't edit, don't delete β€” npm maintains it
Local vs global β€” your Task 4.1 knowledge, completed npm install -g typescript put tsc in AppData\Roaming\npm β€” machine-wide, a tool you run by name. npm install fastify (no -g) puts code in THIS project's node_modules β€” a dependency your code imports. Rule of thumb: commands you type β†’ global; code you import β†’ local. And like tsc --init, plain npm install cares which folder you're standing in β€” always run it in the project root (where package.json lives).

4. import / export β€” and the crash from Phase 3, finally explained

So far your whole app is ONE file. Real projects split code into modules β€” files that explicitly say what they share (export) and what they borrow (import):

// books.ts β€” shares things
export interface Book { id: number; title: string; }
export function findBook(books: Book[], id: number) {
    return books.find(b => b.id === id);
}

// server.ts β€” borrows them
import { Book, findBook } from "./books.js";
Why modules exist One 250-line app.ts is fine. A 25,000-line one is not: name collisions, no idea what depends on what, can't find anything. Modules give each file a private scope (nothing leaks unless exported) and make dependencies visible β€” the import lines at the top of a file are its ingredient list. Your real projects (Pharmacy, Restaurants) are built entirely from these.
YOU ALREADY MET THIS β€” the exports is not defined crash (2026-08-27) Now the full story. There are TWO module systems: CommonJS (2009-era Node: require() / module.exports β€” you'll see it in older code everywhere) and ESM (the official standard: import / export). When your tsconfig said module: "nodenext", tsc translated your file into CommonJS for Node β€” emitting plumbing like exports.foo = .... The browser has no exports variable β†’ ReferenceError, then the cascade. Same code, wrong world β€” exactly the document-in-Node crash, mirrored. In Phase 4 the code is FOR Node, so those settings finally become correct instead of a trap.
How Node knows which system a file uses "type": "module" in package.json β†’ .js files are ESM (import/export). Without it β†’ CommonJS (require). We'll set "type": "module" and use modern import/export everywhere. One wrinkle to expect: in ESM, import paths need the extension β€” from "./books.js" (yes, .js even in a .ts file β€” you import what the COMPILED file will be called).

5. What a server actually is

Strip the mystique: a server is a program that waits for requests and sends back responses. That's the whole job. It runs forever in a loop: listen β†’ request arrives β†’ build an answer β†’ send it β†’ listen again.

Browser (client) your Library page REQUEST: GET /books RESPONSE: 200 + JSON [{"id":1,...}] Server (Node) localhost:3000
Every website you've ever used is this picture, repeated millions of times.
localhost and ports β€” the address system localhost (= the IP 127.0.0.1) means "this same machine" β€” your browser talking to your own Node process, no internet involved. A port is a numbered door on a machine, because one computer runs many networked programs at once β€” they can't all answer the same knock. Web defaults: 80 (http), 443 (https); dev servers pick free high numbers like 3000. So http://localhost:3000/books reads as: "this machine, door 3000, path /books".
Why the Library needs one at all Right now your data lives in localStorage β€” inside one browser on one machine. Open the app on your phone: different browser, different localStorage, empty library. A server gives the data ONE home that every client asks. This is the two-sources-of-truth problem from your capstone, scaled up β€” and the whole reason backends exist.

6. HTTP β€” the request language

Requests follow a standard: HTTP. A request names a method (what kind of action) and a path (what thing). You already built all four actions in the Library β€” HTTP just gives them official names:

MethodMeaningYour Library equivalent
GETread, change nothingrenderBooks, searchBooks, showLowStock
POSTcreate something newaddBook, borrowBook (creates a Loan)
PUT / PATCHupdate something existingreturnBook (flips returned, copies+1)
DELETEremovedeleteBook

The response carries a status code β€” a number stating how it went:

CodeMeansYour app's version of it
200 OKworked, here's your dataa successful render
201 CreatedPOST succeeded, thing now existsaddBook pushing a new book
400 Bad Requestclient sent nonsenseyour addBook validation (isNaN β†’ alert)
404 Not Foundno such thingyour if (!book) return; guard
500 Server Errorserver itself crashedan unhandled bug in your code
JSON is the wire format β€” and you're already fluent Client and server exchange data as JSON text. JSON.stringify to send, JSON.parse to receive β€” the EXACT pair you've used since localStorage. Same skill, longer cable. (And the same trap travels too: Dates cross the wire as ISO strings β€” your capstone rule "store facts, wrap in new Date() for math" applies unchanged.)

7. Fastify β€” your first server, ~10 lines

Why Fastify (and not raw Node, or Express) Node CAN serve HTTP bare, but you'd hand-parse URLs and JSON β€” boilerplate everyone outsources to a framework. Express (2010) is the famous one β€” you'll read it in tutorials forever. Fastify is the modern take: faster, built-in validation, first-class TypeScript support. Your real projects use this stack, so we learn it from day one.
// server.ts β€” a complete working server
import Fastify from "fastify";

const app = Fastify();

app.get("/hello", () => {
    return { message: "Library server is alive" };
});

await app.listen({ port: 3000 });
console.log("Listening on http://localhost:3000");

Run it, then visit http://localhost:3000/hello in the browser β€” JSON appears. Read the shape: app.get(path, handler) β€” "when a GET request hits this path, run this function and send back what it returns." Return an object β†’ Fastify auto-converts to JSON (JSON.stringify'd for you).

The terminal LOOKS stuck — that's success When you run a server, the prompt does NOT come back. The program is doing its job: waiting, forever. It's not frozen. Stop it with Ctrl+C; edit code → restart (or run node --watch server.js and it restarts itself on every save — the server-world's version of your edit→tsc→refresh chain, with the refresh automated).

8. Routes β€” the server's API takes shape

Each app.get/app.post/... line is a route. The Library's backend will grow into this list β€” read it and notice it's exactly your function list, renamed:

GET    /books          β†’ all books           (renderBooks' data source)
GET    /books/:id      β†’ one book            (your find-by-id)
POST   /books          β†’ add a book          (addBook)
DELETE /books/:id      β†’ remove a book       (deleteBook)
POST   /loans          β†’ borrow              (borrowBook)
PATCH  /loans/:id      β†’ return a loan       (returnBook)

Two new pieces inside handlers:

// :id is a URL PARAMETER β€” /books/3 β†’ request.params.id is "3"
app.get("/books/:id", (request) => {
    const id = Number((request.params as { id: string }).id);  // params arrive as STRINGS
    const book = books.find(b => b.id === id);
    // ...guard β†’ 404, else return book
});

// POST bodies: the JSON the client sent, already parsed for you
app.post("/books", (request) => {
    const newBook = request.body as Book;
    // ...validate β†’ 400, else push + return 201
});
Params are strings β€” your oldest enemy returns /books/3 delivers "3", a STRING β€” the form-input trap from Phase 2, back in a new coat. Compare b.id === "3" with === and it's silently false for every book (number vs string never strictly equal). Same cure as always: Number() at the border, immediately. Everything that crosses a boundary β€” form, localStorage, URL, HTTP β€” arrives as a string.

9. The Phase 4 arc β€” Library grows a backend

The plan, in order β€” each step is a working app:

StepWhat changesWhat it teaches
1. Hello serverFastify answers /hellosetup, routes, the waiting loop
2. GET /booksserver owns the books array (in memory)routes returning real data
3. Frontend fetchesindex.html calls fetch("http://localhost:3000/books") instead of reading localStoragefetch, async/await β€” the missing JS piece
4. Full CRUD routesadd/delete/borrow/return move server-sidePOST bodies, params, status codes
5. Save to a JSON fileserver writes books.json/loans.json to disk (fs)persistence without a browser β€” localStorage's server cousin
6. β†’ Phase 5the JSON file becomes a real databaseSQL β€” and your bookId foreign key becomes literal
What happens to your current app.ts It SPLITS. The data functions (find, push, splice, the arrays) move to the server. The screen functions (renderBooks, renderLoans, reading inputs) stay in the browser. The data-vs-presentation line you've been drawing since your first render becomes a physical wall with HTTP in between β€” you'll finally see WHY we kept insisting on that separation.

10. TypeScript on the server

Same two-step you already live by, new runner: edit .ts β†’ tsc β†’ node server.js (instead of refresh). And here's the satisfying part: tsconfig's module: "nodenext" β€” the setting that CRASHED your browser app β€” is the correct setting now, because the output finally runs where it was designed to run: Node. Types get more valuable here too β€” no DevTools on a server, so the more bugs die at compile time, the better.

One project, two worlds β€” keep them separate The server code will live in its own folder with its own tsconfig (Node settings), apart from the browser code (browser settings). Mixing the two tsconfigs is exactly how the exports-crash happened. Folder = world.

11. The mental model to keep

IdeaOne line to remember
Nodethe browser's JS engine, freed β€” same language, no DOM, plus files & network
npm local vs -gcommands you type β†’ global; code you import β†’ local
node_modulesregenerable, never edited β€” the app.js rule, scaled up
import/exporteach file a private room; exports are what's passed through the door
Servera program that waits: request in β†’ response out, forever
HTTP methodsGET read Β· POST create Β· PATCH update Β· DELETE remove β€” your CRUD, officially named
Boundarieswhatever crosses one (form, URL, HTTP, storage) arrives as a string β€” convert at the border
The splitdata lives on the server, presentation in the browser, JSON between them

← Prev: TypeScript  Β·  Back to contents  Β·  Next: SQL & Prisma β†’