The problem that created Node.js, how V8 works, Node vs browser JavaScript, installing Node, and running your very first program.
Module F-1 — What Is Node.js and Why Does It Exist?
What this module covers: Before you write a single line of Node.js, you need a correct mental model of what it actually is. Not "JavaScript on the server" — that's a description, not a model. This module covers the problem Node.js was built to solve, what the V8 engine does, how Node.js differs from browser JavaScript, and how to get your environment set up correctly. By the end you will have Node installed, your first program running, and a clear picture of when Node.js is the right tool — and when it isn't.
The Problem That Created Node.js
The year is 2008. You are building a web application. Your backend is written in Java, PHP, or Ruby. Every time a user makes a request — for a page, for data, for a file upload — your server creates a new thread to handle it.
A thread is expensive. It costs memory (typically 2–8 MB per thread). The operating system has to context-switch between them. Under heavy load, you spawn hundreds of threads. Most of them are not computing anything — they're just waiting. Waiting for the database to return rows. Waiting for a file to be read from disk. Waiting for an external API to respond.
This is the thread-per-request model, and its fundamental problem is that it conflates concurrency with CPU usage. You need concurrent connections. You don't necessarily need concurrent CPU work. Most web requests spend 80–95% of their time on I/O — waiting for something external — not on computation.
Ryan Dahl, a software engineer, noticed this in 2009. He was experimenting with non-blocking I/O in C and Lua when he came across Google's V8 JavaScript engine, which Google had just open-sourced as part of Chrome. V8 was fast — dramatically faster than other JavaScript engines at the time. And JavaScript, by its design in the browser, was already event-driven. You attached callbacks to button clicks. You didn't block waiting for a user to click.
His insight: take V8 out of the browser, add APIs for the file system and networking, and you have a runtime where I/O is inherently non-blocking. You never wait. You register a callback and go handle something else.
He presented Node.js at JSConf EU in November 2009. The demo — a web server in 8 lines of JavaScript — caused immediate excitement. Node.js was not just a new language runtime. It was a different way of thinking about concurrency.
What Node.js Actually Is
Node.js is three things bundled together:
1. V8 — Google's JavaScript engine, written in C++. V8 compiles your JavaScript to machine code and executes it. It is the same engine that runs JavaScript in Chrome and Edge.
2. libuv — A C library that handles asynchronous I/O. It provides the event loop, thread pool, and cross-platform abstractions for networking (TCP, UDP), file system, DNS, and more. libuv is what makes Node.js non-blocking — when you read a file or make a network request, libuv handles the OS-level call and notifies Node.js when it's done.
3. The Node.js standard library — A set of built-in JavaScript modules: fs for files, http for servers, path for paths, crypto for cryptography, and many others. These are the Node.js APIs you call in your code.
When you run node server.js, what actually happens:
- V8 parses and compiles your JavaScript to machine code
- Your code runs on a single thread managed by V8
- When your code calls an I/O operation (read a file, open a socket), it hands the work to libuv
- libuv queues the operation with the operating system
- The event loop continues running — handling other work, timers, callbacks
- When the OS signals the I/O is complete, libuv puts the callback into the event queue
- The event loop picks it up and runs it
This is the core model. One thread. Non-blocking I/O. Event-driven callbacks. We will go much deeper into the event loop mechanics in Phase 3 — but this mental model is all you need for Phase 1 and Phase 2.
Node.js vs Browser JavaScript
JavaScript runs in two environments: the browser and Node.js. The language itself is the same — the same syntax, the same Array, Map, Promise. But the environment around it is completely different.
| Feature | Browser | Node.js |
|---|---|---|
window object | ✅ | ❌ |
document, DOM | ✅ | ❌ |
process object | ❌ | ✅ |
fs (file system) | ❌ | ✅ |
http (TCP server) | ❌ | ✅ |
require() / import | Limited | ✅ Full module system |
fetch | ✅ | ✅ (Node 18+) |
localStorage | ✅ | ❌ |
setTimeout / setInterval | ✅ | ✅ |
console.log | ✅ | ✅ |
In the browser, JavaScript's job is to manipulate the DOM and respond to user events. The global object is window. APIs exist to talk to the user's screen.
In Node.js, JavaScript's job is to build servers, process files, and communicate over networks. The global object is global (though in modern code you rarely reference it directly). APIs exist to talk to the operating system.
This is why code you write for Node.js cannot run in the browser (it uses fs, http, process) and browser-specific code cannot run in Node.js (it uses document, window.location, localStorage).
Installing Node.js
There are two ways to install Node.js. The direct approach works fine for a single machine. nvm is better for any serious development work.
Option 1: Direct Install
Go to nodejs.org and download the LTS (Long Term Support) version. Run the installer. Done.
Option 2: nvm (Recommended)
nvm — Node Version Manager — lets you install and switch between multiple Node.js versions on the same machine. This matters because different projects require different Node.js versions.
bash# Install nvm (macOS / Linux) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # Restart your terminal, then verify nvm --version # Install the current LTS version nvm install --lts # Use it nvm use --lts # Verify Node.js and npm are installed node --version # e.g. v22.11.0 npm --version # e.g. 10.9.0
On Windows, use nvm-windows — a separate project with a similar interface.
LTS vs Current
Node.js maintains two release lines:
- LTS (Long Term Support) — Even-numbered versions (18, 20, 22). Stable. Supported for 3 years. Use this for all production work and new projects.
- Current — Odd-numbered versions (19, 21, 23). Latest features, 6-month support window. Use to experiment with new APIs, not for production.
When in doubt, use LTS. If you are joining an existing project, check the engines field in package.json to see which version it requires.
Your First Node.js Program
Create a file called hello.js:
javascript// hello.js console.log('Hello from Node.js!'); console.log('Node version:', process.version); console.log('Platform:', process.platform); console.log('Current directory:', process.cwd());
Run it:
bashnode hello.js
Output:
Hello from Node.js!
Node version: v22.11.0
Platform: darwin
Current directory: /Users/jatin/projects
A few things to notice:
processis a global object in Node.js — no import needed. It gives you access to the runtime environment: Node version, OS platform, environment variables, command-line arguments, and more.process.versionis the Node.js version running your program.process.platformisdarwin(macOS),linux, orwin32.process.cwd()is the current working directory — where you rannodefrom, not where the file lives.
Your First HTTP Server
This is where Node.js starts to feel different. Paste this into server.js:
javascript// server.js const http = require('http'); const server = http.createServer((req, res) => { // req = the incoming request // res = the response we will send back console.log(`${req.method} ${req.url}`); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello from Node.js!\n'); }); server.listen(3000, () => { console.log('Server running at http://localhost:3000/'); console.log('Press Ctrl+C to stop.'); });
Run it:
bashnode server.js
Open http://localhost:3000 in your browser. You will see "Hello from Node.js!" in the browser and GET / logged in your terminal.
Press Ctrl+C to stop the server.
What just happened:
http.createServer()creates a TCP server that listens for HTTP requests- The callback function
(req, res) => { ... }runs every time a request arrives res.writeHead(200, ...)sends the HTTP status code and headersres.end(...)sends the body and closes the connectionserver.listen(3000)starts listening on port 3000
This is the foundation. In practice you will use the Express framework (covered in F-6) which adds routing, middleware, and much more on top of this. But every Express server is ultimately using http.createServer() under the hood.
Reading Error Messages
Errors are inevitable. Learning to read them quickly is a skill that saves enormous time.
A typical Node.js error:
/Users/jatin/projects/server.js:5
res.end(undefinedVariable);
^
ReferenceError: undefinedVariable is not defined
at Server.<anonymous> (/Users/jatin/projects/server.js:5:11)
at node:events:518:28
at node:http:1393:3
at Server.emit (node:events:518:28)
at parserOnIncoming (node:_http_server:1061:12)
at HTTPParser.parserOnHeadersComplete (node:_http_incoming:76:8)
Breaking this down:
- Line 1: The file and line number where the error occurred —
server.js:5 - The caret
^: Points to the exact position on the line - Error type:
ReferenceError— you used a variable that was never declared - Error message:
undefinedVariable is not defined - Stack trace: The call stack at the moment of the error, from most recent (your code) to oldest (Node.js internals)
Common error types:
| Error Type | Meaning |
|---|---|
ReferenceError | Used a variable that doesn't exist |
TypeError | Called a method on undefined or null, or wrong argument types |
SyntaxError | Invalid JavaScript — missing bracket, typo in keyword |
RangeError | Number out of valid range (e.g. invalid array length) |
Error | General error — thrown by throw new Error('message') |
When you see an error, read from top to bottom: what went wrong, where it went wrong, how it got there. Ignore the Node.js internal stack frames (the lines starting with node:) and focus on your own files.
What Node.js Is Good For
Node.js excels at tasks where the bottleneck is I/O, not CPU:
✅ API servers and REST backends — Handling thousands of concurrent HTTP connections efficiently. This is the most common use case.
✅ Real-time applications — Chat apps, live notifications, collaborative tools. The event-driven model is a natural fit for WebSockets.
✅ Streaming data — Processing large files or data streams without loading everything into memory at once.
✅ CLI tools and scripts — Node.js is excellent for build tools, automation scripts, and developer tooling. Webpack, ESLint, Prettier, and Vite are all Node.js programs.
✅ Microservices — Lightweight, fast-starting services that communicate over HTTP or message queues.
✅ BFF (Backend for Frontend) — A thin API layer that aggregates data from multiple services for a specific frontend client.
What Node.js Is NOT Good For
There are tasks where Node.js is the wrong choice:
❌ CPU-intensive computation — Video encoding, image processing, machine learning inference, cryptographic mining. Node.js runs JavaScript on a single thread. A CPU-heavy operation blocks the event loop and stops everything else from running. (Phase 3 covers techniques for mitigating this — worker threads, native addons — but the baseline is: if your workload is 80% computation and 20% I/O, consider a different runtime.)
❌ Memory-intensive number crunching — Scientific computing, numerical simulations. Languages like Python (NumPy), Julia, or C++ are better suited.
❌ When your team has no JavaScript experience — Using Node.js because it's popular is not a good reason if nobody on your team knows JavaScript well. The wrong tool used by experts beats the right tool used badly.
The decision tree is simple: if your workload is mostly I/O (database queries, file reads, HTTP calls, WebSocket connections), Node.js is an excellent choice. If your workload is mostly CPU, use something better suited.
Summary
- Node.js = V8 + libuv + standard library. V8 runs your JavaScript. libuv handles all I/O asynchronously. The standard library gives you fs, http, path, and everything else.
- Single-threaded, non-blocking. One thread runs your JavaScript. When you do I/O, you don't wait — you register a callback and move on. The event loop calls your callback when the I/O completes.
- Node.js is not the browser. No
window, nodocument, no DOM. You haveprocess,fs,http, and the full OS available to you. - Install via nvm.
nvm install --ltsgives you the stable version. Switch versions per project withnvm use. - Read errors top-to-bottom. Error type → message → your file and line number → ignore Node.js internals.
- Best for I/O-heavy workloads. APIs, real-time apps, streaming, CLI tools. Not for CPU-intensive computation.
In the next module we look at the module system — how Node.js files talk to each other, the difference between require() and import, and the module resolution algorithm that determines exactly which file gets loaded when you write require('./utils').