This is Part 1 of a 3-part series where an uncle (30 years in backend systems, still uses a wired mouse) explains Node.js internals to his nephew (2nd year into his career, addicted to system design videos) over chai on a Saturday. No jargon-dump. No "just trust me." Every answer earns the next question.
Saturday, 10 AM
👦 Nephew: Uncle, free? I'm stuck.
👨🦳 Uncle: Stuck on what — code, or life? Because for life I charge extra.
👦 Nephew: 😂 Code. I've been writing Node.js for two years, but honestly... I don't know Node.js. I know req and res. I know app.get(). But if you ask me what's happening underneath — I go blank.
👨🦳 Uncle: That's actually a good realization to have. Most developers never stop to ask that. Tell me the topic name, we'll go one at a time. No rushing.
👦 Nephew: Let's start from zero. Why does Node.js even exist?
👨🦳 Uncle: Perfect starting point. Sit properly, this one needs your full brain, not just your fingers.
Part 1.1 — Why Was Node.js Even Born?
👨🦳 Uncle: Before 2009, JavaScript had exactly one home.
Browser
|
HTML
CSS
JavaScript
👨🦳 Uncle: And its job was small — validate a form, animate a button, change some colors. The moment you clicked "Submit" and needed to save something, JavaScript had to hand the job over to someone else.
👦 Nephew: Someone else meaning... another language?
👨🦳 Uncle: Exactly.
Browser (JavaScript)
|
HTTP Request
|
PHP / Java / Python / .NET
|
Database
👨🦳 Uncle: So a company building, say, an Instagram clone needed two separate teams speaking two separate languages. Frontend guy writes JS. Backend guy writes Java. Different tools, different bugs, different Slack channels arguing with each other.
👦 Nephew: Sounds like a messy marriage.
👨🦳 Uncle: It was. And then one man, Ryan Dahl, looked at this mess and asked one dangerously simple question:
"JavaScript is already a solid language. Why can't it run outside the browser?"
👨🦳 Uncle: That one question is the entire origin story of Node.js.
Part 1.2 — So What Actually IS Node.js?
👦 Nephew: Ok so is Node.js a new language then?
👨🦳 Uncle: No — and this is where 80% of juniors get it wrong on day one. Node.js is not a language. It's a runtime environment.
👦 Nephew: Runtime... environment. That phrase always sounded like corporate decoration to me.
👨🦳 Uncle: Think of JavaScript as an engine — just the engine, no car around it.
JavaScript (engine)
|
├──> installed in Chrome → runs in a browser
|
└──> installed in Node.js → runs on your machine/server
👨🦳 Uncle: Same engine. Different car around it. Chrome's car has a steering wheel called DOM, window, alert(). Node's car has a steering wheel called fs, http, process. The engine — the actual JavaScript language, if, loops, functions — stays identical in both.
👦 Nephew: So Node.js is just... JavaScript wearing a different uniform?
👨🦳 Uncle: Exactly that. And once JS put on that uniform, it could suddenly do things the browser was never allowed to do:
- Read and write files
- Open a server and listen for requests
- Talk directly to a database
- Run scheduled/background jobs
- Make outbound calls to other APIs
👦 Nephew: Wait — why was browser JS "not allowed" to do this before? Was it a technical limitation or a rule?
👨🦳 Uncle: Good — you jumped straight to the real question. Grab the diagram, this needs one.
Part 1.3 — The Browser Sandbox (Why JS Was Caged)
👨🦳 Uncle: Imagine you open some shady website — free-recharge-totally-legit.com. If JavaScript on that page had full access to your machine, it could quietly do this:
read("C:/Users/Suraj/Documents/passwords.txt")
read("bank_statement.pdf")
👦 Nephew: That's basically every villain-hacker movie scene.
👨🦳 Uncle: Right, and to prevent exactly that, browsers lock JavaScript inside a sandbox.
Your Computer
+--------------------------------------+
| Documents Photos Bank Files |
| |
| +-------------------------------+ |
| | Browser Sandbox | |
| | JavaScript runs here only | |
| | | |
| | ❌ can't read your disk | |
| | ❌ can't open a server | |
| | ❌ can't touch a database | |
| +-------------------------------+ |
+--------------------------------------+
👨🦳 Uncle: The browser gives it a small, safe playground — manipulate the page, make HTTP calls, store small bits of data, read a file only if you explicitly picked it via a file input. Nothing more.
👦 Nephew: Ok that makes sense for the browser. But here's what confused me at work — why can't my React app just talk to MongoDB directly? We already validate signup and login inside React itself.
👨🦳 Uncle: Ah — now we're at the second-most-asked interview question in backend engineering. Let's slow down here.
Part 1.4 — Why the Frontend Never Touches the Database Directly
👨🦳 Uncle: Suppose your React code had this, hypothetically:
const dbPassword = "mySecretPassword123";
connectToDatabase(dbPassword);
👦 Nephew: That looks fine on my screen.
👨🦳 Uncle: It looks fine until you remember — React code doesn't stay a secret. The moment it's built, that whole JavaScript bundle is shipped to every visitor's browser. Anyone can open DevTools, go to Sources, and read it line by line.
Your React Code
|
npm run build
|
Bundle sent to EVERY user's browser
|
Anyone can open DevTools and read it
👦 Nephew: So my database password would just be sitting there in plain sight for the whole internet.
👨🦳 Uncle: Worse — even without a password baked in, if the browser could reach the DB directly, any random person could open DevTools and type:
db.users.deleteMany({})
👦 Nephew: 💀 That's not a bug, that's a national incident.
👨🦳 Uncle: Exactly why we never expose a database to the browser. Ever. Instead, we put a bouncer in between.
React (Browser)
|
HTTP Request
|
Node.js ← the bouncer
|
Database
👨🦳 Uncle: When someone signs up, the flow is:
- React collects email + password.
- Sends it to Node.js.
- Node.js validates it — properly, not just "looks nice."
- Node.js hashes the password.
- Node.js applies business rules.
- Node.js saves to the database.
- Node.js sends back only what the browser is allowed to know.
👦 Nephew: But we already validate email format and password length in React. Isn't that enough?
👨🦳 Uncle: That validation is for user experience, not security. It stops a genuine user from submitting a typo. It does nothing to stop an attacker who skips your React app entirely and fires requests straight at your server using Postman or curl.
curl -X POST https://yourapi.com/signup \
-d '{"email":"x","password":"1"}'
👨🦳 Uncle: Postman doesn't run your React validation. It doesn't care about your required attribute. So the golden rule:
Never trust the client. Validate everything again on the server — always.
👦 Nephew: Ok, one more doubt — when I open DevTools → Network tab during signup, I can literally see my password in the request body. Isn't that already a leak?
👨🦳 Uncle: Ha, good catch, but no — that's your own browser showing you what you are sending. Nobody else can open your Chrome and peek at your Network tab. The actual danger is the wire between your browser and the server — and that's solved by HTTPS.
Without HTTPS:
Browser --------- plain password --------- Server (dangerous)
With HTTPS:
Browser === encrypted gibberish ===> Server (safe)
👨🦳 Uncle: DevTools shows it before encryption, because your browser knows exactly what it's about to send. Once it actually leaves onto the wire, HTTPS scrambles it.
Part 1.5 — The Big Realization
👨🦳 Uncle: So far, two things should be permanently glued into your brain:
| Concept | What it means |
|---|---|
| Browser sandbox | Protects the user's machine from malicious websites |
| Backend (Node.js) | Protects the company's database and logic from malicious clients |
👦 Nephew: So Node.js exists because JS needed a body that isn't caged like the browser.
👨🦳 Uncle: Exactly. And because it's still the same language on both ends —
React (frontend)
|
Node.js (backend)
|
MongoDB (database)
— one engineer can now touch the entire stack. Same syntax, same mental model, shared code between client and server. That's the entire reason the MERN stack exploded in popularity. One hiring pipeline instead of two.
👦 Nephew: Okay that part is settled in my head now. But uncle — this is where I get genuinely lost. When I say require('fs') in one file and import fs from 'fs' in another — are these even the same thing? My repo has both styles and it confuses me every single time.
👨🦳 Uncle: laughs — every Node developer hits this wall eventually. Fine, let's open that door.
Part 1.6 — CommonJS vs ES Modules (The War Nobody Told You About)
👨🦳 Uncle: Node.js didn't always have a clean module system. In fact, JavaScript itself didn't have one officially for years. So Node invented its own — CommonJS.
// math.js (CommonJS)
function add(a, b) { return a + b; }
module.exports = { add };
// app.js
const { add } = require('./math');
👦 Nephew: And ES Modules is the "official" one that came later?
👨🦳 Uncle: Right. Once JavaScript itself standardized modules, we got this:
// math.mjs (ES Modules)
export function add(a, b) { return a + b; }
// app.mjs
import { add } from './math.mjs';
👦 Nephew: So what's actually different, not just syntax-wise?
👨🦳 Uncle: Good question, because the syntax is the boring part. The real difference is when each one resolves things.
| CommonJS | ES Modules | |
|---|---|---|
| Loading | Synchronous, runtime | Static, analyzed before execution |
| Keyword |
require / module.exports
|
import / export
|
| Can be conditional? | Yes — if (x) require('./a') works |
No — imports must be top-level |
| File extension default |
.js (CommonJS by default in Node) |
.mjs, or .js with "type": "module" in package.json |
| Used historically in | Old Node ecosystem, most npm packages | Modern JS, browsers, newer tooling |
👨🦳 Uncle: Because ES Modules are analyzed before the code even runs, tools can do smarter things — like removing unused code (“tree-shaking”), something CommonJS structurally can't support well since require() can be called conditionally, hidden inside an if, anywhere.
👦 Nephew: Then why does CommonJS still exist everywhere in old projects?
👨🦳 Uncle: Because for years it was the only option, and the entire npm ecosystem was built on it. Switching an existing giant codebase is expensive, so plenty of production code still runs on CommonJS today — that's not shame, that's just history.
👦 Nephew: One more thing — does require() cache a module, or does it re-run the file every time I import it elsewhere?
👨🦳 Uncle: It caches. First require('./math') actually executes the file. Every subsequent require('./math') anywhere else in your app just returns the same cached object — it does not re-run the file.
require('./math') → file executes, result cached
require('./math') → returns cached result, no re-execution
require('./math') → returns cached result, no re-execution
👨🦳 Uncle: Same is true for import — modules are singleton-loaded once per process.
👦 Nephew: Interview-style, one line — how would I answer "CommonJS vs ESM" in 30 seconds?
👨🦳 Uncle: Something like:
"CommonJS is Node's original, synchronous module system using
require/module.exports, resolved at runtime. ES Modules is the official JavaScript standard usingimport/export, statically analyzed before execution, which enables optimizations like tree-shaking. Node supports both today, but a huge amount of legacy npm packages still ship CommonJS."
Nephew: Got it. So every file I write needs a module system. But what actually tells npm and Node "this is a project, here's its name, here's what it needs to run"?
👨🦳 Uncle: That, nephew, is package.json — the ID card of every Node project.
Part 1.7 — package.json, The ID Card
👨🦳 Uncle: Every serious Node project starts with:
npm init -y
which produces something like:
{
"name": "my-app",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"express": "^4.19.0"
},
"devDependencies": {
"nodemon": "^3.0.0"
}
}
👦 Nephew: I've edited this file a hundred times without really understanding half these fields.
👨🦳 Uncle: Let's fix that once and for all.
| Field | What it actually does |
|---|---|
name / version
|
Identity of your package, used if you publish to npm |
main |
The entry file loaded when someone does require('your-package')
|
scripts |
Shortcuts — npm run dev just runs whatever command is mapped there |
dependencies |
Packages your app needs to run in production |
devDependencies |
Packages only needed while developing (linters, test runners, nodemon) |
^ before a version |
Allows automatic minor/patch updates, not major |
👦 Nephew: And package-lock.json — what's that ghost file that keeps changing on its own?
👨🦳 Uncle: That's npm locking down the exact version tree it installed — including all your dependencies' dependencies — so that your teammate, or your production server, installs the identical versions you tested with. Without it, "works on my machine" becomes a certainty, not a joke.
👦 Nephew: Okay — and now the thing that's genuinely bugged me for two years: what's the actual difference between npm and npx? I use both but I couldn't explain it if you paid me.
👨🦳 Uncle: grins — finally, a question I get to make fun of you for not knowing.
Part 1.8 — npm vs npx
👨🦳 Uncle: npm — Node Package Manager — installs packages, either globally or into your project's node_modules.
npm install express
npm install express
|
node_modules/express created
|
package.json updated
👦 Nephew: Okay that part's clear. What about npx?
👨🦳 Uncle: npx executes a package — often without permanently installing it at all.
npx create-react-app my-app
👨🦳 Uncle: Here, create-react-app isn't sitting in your node_modules forever bloating your machine. npx downloads it temporarily (or uses a cached copy), runs it once, and lets it go.
npm → "install this, and keep it around"
npx → "run this, once, right now"
👦 Nephew: So when do I actually reach for which?
👨🦳 Uncle: Simple rule:
| Situation | Use |
|---|---|
You need a library your code will require/import (like express) |
npm install |
| You need to run a one-time CLI tool or scaffold a project | npx <tool> |
You have a local dev tool already in devDependencies (like eslint) and want to run it without a global install |
npx eslint . |
👦 Nephew: That actually just clicked for the first time. I feel personally attacked by how simple that was.
👨🦳 Uncle: Most of Node.js is like that — simple once someone draws the picture, terrifying when it's just words in a blog post nobody explains properly.
Part 1.9 — A Quick Tour of Node's Built-in Toolbox
👦 Nephew: Before we go deeper into internals next time — can you at least name-drop the core modules I should know exist?
👨🦳 Uncle: Sure, quick tour, no deep dive yet — that's for later parts.
| Module | Job |
|---|---|
fs |
Read/write files on disk |
path |
Safely build and manipulate file paths across OSes |
os |
Info about the machine — CPU cores, memory, platform |
http |
Create servers, make HTTP requests |
crypto |
Hashing, encryption, random tokens |
events |
The EventEmitter class — the backbone of Node's async design |
stream |
Handle data in chunks instead of all at once (files, uploads, videos) |
👦 Nephew: events being "the backbone of async design" sounds like foreshadowing.
👨🦳 Uncle: It absolutely is. Because next time, we're going into the room where the real magic happens — the event loop, libuv, why async doesn't mean multi-threaded, how the V8 engine actually stores your variables, and why a Buffer doesn't even live in the same memory as your normal JavaScript objects.
👦 Nephew: That sounds like it's going to hurt.
👨🦳 Uncle: Good pain. The kind that makes you dangerous in interviews. Same time next week — bring two cups of chai, this one's long.
What we covered in Part 1
- Why Node.js was created (Ryan Dahl's original question)
- Node.js = runtime, not a language
- The browser sandbox and why it exists
- Why frontend never talks to a database directly
- HTTPS vs what DevTools shows you
- CommonJS vs ES Modules — real differences, not just syntax
-
package.jsonandpackage-lock.json -
npmvsnpx - A first look at Node's core built-in modules
Next up — Part 2: "The Engine Room" — the event loop, libuv, the thread pool, worker threads, V8's stack vs heap, garbage collection, and why Buffer.alloc() doesn't touch the JS heap at all.
Top comments (0)