Skip to content

node_01 - Node Introduction

Ryan Dahl stood up at JSConf 2009 and changed backend development forever
He took Google's V8 engine , wrapped it in C++ bindings , added libuv for async I/O , and called it Node. The idea was simple: JavaScript on the server. The industry laughed. Then they realized event-driven non-blocking I/O could handle thousands of concurrent connections without Apache's thread-per-connection memory explosion , and suddenly everyone was a Node developer

what Node.js is

Node is three things bolted together:

  1. V8 - Google's JavaScript engine , written in C++ , compiles JS to machine code via JIT
  2. libuv - the async I/O library that handles file system , networking , DNS , and thread pooling
  3. Node bindings - the C++ glue that exposes OS capabilities to JavaScript (fs , net , http , child_process)

When you run node app.js , V8 parses and compiles your JavaScript , libuv handles all the I/O operations in the background , and the bindings translate between JS calls and system calls. Your JS code never touches the kernel directly - it goes through libuv's event loop

// This is Node.js in its simplest form
const http = require('http')

const server = http.createServer((req , res) => {
  res.writeHead(200 , { 'Content-Type': 'text/plain' })
  res.end('Hello from Node\n')
})

server.listen(3000 , () => {
  console.log('Server running at http://localhost:3000/')
})

Run it: node app.js and hit http://localhost:3000. That's your first Node server , 8 lines , zero dependencies

why JavaScript on the server

The question every developer asks and most answer wrong
JavaScript was a browser-only language for 14 years. Putting it on the server seemed like a joke. But Dahl saw something others missed: event-driven programming with callbacks was perfect for I/O-bound workloads

The traditional model (Apache , IIS) spawns a thread per connection. Each thread consumes memory (default 8MB stack on Linux) and context-switching between threads costs CPU. At 10,000 concurrent connections , you're looking at 80GB of RAM just for thread stacks before any actual work happens

Node's model: one thread , one event loop , thousands of concurrent connections. Each connection is just a callback in a queue. No thread overhead , no context switching , no memory explosion

// This handles 10,000 concurrent connections on a single thread
// Apache would need 10,000 threads - Node just needs callbacks
const server = http.createServer((req , res) => {
  // This callback fires for each request
  // While waiting for I/O (database, file read, API call)
  // The event loop processes OTHER requests
  // No threads needed, no context switching
  res.end('hello')
})

security implications of server-side JS

The paradigm shift that most people ignore until they get pwned
Browser JavaScript runs in a sandbox with restricted capabilities. Your frontend code can't read files from the server , can't open raw network sockets , can't spawn processes. This is baked into the browser security model

Node JavaScript runs with the full privileges of the user who started the process. The same fs.writeFileSync('/etc/passwd' , 'owned') that's impossible in a browser is trivial in Node. The same child_process.exec('rm -rf /') that would violate browser security policies runs without complaint on your server

// Browser - impossible, sandboxed
// fs.writeFileSync('/etc/pwned', 'owned')  // ReferenceError: fs is not defined

// Node - runs with full OS privileges
const fs = require('fs')
const cp = require('child_process')

// This actually works if the user has permissions
fs.writeFileSync('/tmp/test' , 'pwned')
cp.execSync('whoami') // returns the OS user the process runs as

This means every vulnerability in your Node app is potentially a full system compromise. XSS in a browser app steals cookies. RCE in a Node app steals the server. The stakes are higher and most tutorials don't mention this

the event-driven model explained badly then properly

Bad explanation: "Node is non-blocking so it's fast"
Proper explanation: Node registers callbacks for I/O operations and continues executing other code while waiting. When the I/O completes , the callback gets added to the event loop queue and executes when the stack is empty. This is cooperative multitasking at the application level - no preemption , no races , but also no parallelism for CPU work

// Synchronous (bad for I/O-bound servers)
const data = fs.readFileSync('/big/file.txt') // blocks entire process
console.log('File loaded')

// Asynchronous (Node's model)
fs.readFile('/big/file.txt' , (err , data) => {
  // This callback runs when the file is loaded
  // Meanwhile the event loop is processing other requests
  console.log('File loaded')
})
console.log('This runs immediately, before file is loaded')

The output order proves the model:

This runs immediately, before file is loaded
File loaded

The second console.log runs first because readFile returns immediately (non-blocking) and the callback executes later when libuv finishes reading the file

prerequisites

node_00 - Node.js HOME


next -> node_02_get_started.md