What is Node.js?
Node.js is an open-source, cross-platform runtime environment that executes JavaScript outside the browser — on servers, on your laptop, inside containers, on edge networks, anywhere there is a CPU. It was created in 2009 by Ryan Dahl, who combined three things: Google Chrome's V8 JavaScript engine, a C library called libuv for asynchronous I/O, and a set of C++ bindings and JavaScript APIs that expose the operating system to your code.
Before Node.js, JavaScript was confined to the browser. To write the server side of a web application you reached for PHP, Ruby, Python, Java, or C#. Node.js broke that wall: it took the language millions of developers already knew and gave it the system-level capabilities a server needs — a file system, networking (TCP/UDP/HTTP), processes, streams, timers, and cryptography. The result is one language across the entire stack: the same engineer can write the React front end and the API that feeds it, sharing types, validation logic, and even whole modules between them.
Runtime vs language vs framework
This distinction trips up almost every beginner, and getting it straight now will save you confusion later. These are three different layers:
Term | What it is | Examples |
|---|---|---|
Language | The syntax and rules, standardized as ECMAScript | JavaScript, TypeScript (compiles to JS) |
Runtime | A program that executes the language and exposes platform APIs | Node.js, Deno, Bun, the browser |
Framework / library | Code you install on top of a runtime to build faster | Express, NestJS, Fastify, Next.js |
What is "a runtime," concretely?
A JavaScript engine like V8 only knows the pure language — variables, functions, objects, Math, JSON, promises. It has no idea how to read a file or open a network socket, because those are not part of JavaScript; they are operating-system features. A runtime is the engine plus the bridge to the outside world.
Node.js is that bridge. Architecturally it stacks like this:
The Node.js stack
┌─────────────────────────────────────────────┐ │ Your application code (JavaScript) │ ├─────────────────────────────────────────────┤ │ Node.js core API (fs, http, crypto, ...) │ ← JavaScript ├─────────────────────────────────────────────┤ │ Node.js bindings (C++ glue layer) │ ← C++ ├──────────────────────┬──────────────────────┤ │ V8 engine │ libuv │ ← C++ / C │ (runs your JS, │ (event loop, │ │ manages memory) │ async I/O, │ │ │ thread pool) │ ├──────────────────────┴──────────────────────┤ │ Operating system (files, sockets, timers) │ └─────────────────────────────────────────────┘
We explore each layer separately: V8 runs your JavaScript and manages memory; libuv provides the event loop and asynchronous I/O; and the whole assembly is the subject of Node.js Architecture Overview.
Your first taste
Create a file called app.js and run it with node app.js:
app.js
// This runs on the server / your machine — no browser involved.
console.log('Hello from Node.js!')
console.log('Running on Node', process.version)
console.log('Platform:', process.platform, process.arch)
// 'process' does not exist in a browser — it is pure Node.
console.log('This file:', __filename)Hello from Node.js! Running on Node v20.11.0 Platform: darwin arm64 This file: /Users/you/app.js
Both process and __filename exist in Node.js but not in a browser. Those two names alone hint at everything Node.js adds: introspection of the running process and access to the file system.
The defining idea: an event-driven, non-blocking runtime
Node.js was built around two tightly linked ideas that, together, make it exceptional for I/O-heavy network applications:
A single-threaded event loop. Instead of dedicating one operating-system thread to each incoming request (the traditional "thread-per-request" model used by classic Apache/PHP and Java servlets), Node.js runs your JavaScript on one thread and uses a loop to react to events as they occur.
Non-blocking, asynchronous I/O. When Node.js reads a file, queries a database, or calls another API, it does not sit and wait for the answer. It hands the slow work off to the operating system (or to a background thread pool), registers a callback, and immediately moves on to serve other work. When the result is ready, the callback is queued and the event loop runs it.
The pay-off: one Node process can juggle tens of thousands of simultaneous connections, because it spends its time doing useful work rather than blocked, waiting on slow operations. We unpack the mechanics in How Node.js Works and Single-Threaded, Non-Blocking I/O.
Blocking vs non-blocking — see the difference
This contrast is the single most important thing to internalize about Node.js. Both snippets read a file; only one keeps the program responsive.
Two ways to read a file
import fs from 'node:fs'
// BLOCKING: the whole process freezes here until the file is fully read.
// On a server, EVERY other user waits during this pause.
const dataSync = fs.readFileSync('big.log', 'utf8')
console.log('A: read finished (blocking)')
// NON-BLOCKING: the read happens in the background. Node keeps running.
fs.readFile('big.log', 'utf8', (err, data) => {
if (err) throw err
console.log('C: read finished (non-blocking)')
})
console.log('B: this prints before C, while the file is still loading')A: read finished (blocking) B: this prints before C, while the file is still loading C: read finished (non-blocking)
A tiny server — where Node becomes a backend
The browser can never open a listening socket. Node.js can, using the built-in http module and no framework at all:
server.js
import http from 'node:http'
const server = http.createServer((req, res) => {
// This callback runs once PER request, on the single main thread.
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ message: 'Hello from a Node.js server' }))
})
server.listen(3000, () => console.log('Listening on http://localhost:3000'))Run it with node server.js and visit the URL. One process, one thread, yet it can serve many clients concurrently because each request handler is short and any slow work inside it would be asynchronous.
Node.js, Deno, and Bun
Node.js is no longer the only server-side JavaScript runtime. Knowing the landscape helps you understand Node's design choices and where it is heading.
Runtime | Engine | Distinctive traits |
|---|---|---|
Node.js (2009) | V8 | The mature standard; huge ecosystem; CommonJS + ESM; the de-facto choice |
Deno (2018) | V8 | Built by Node's creator; secure-by-default permissions; TypeScript built in; web-standard APIs |
Bun (2022) | JavaScriptCore | Speed-focused; bundler + test runner + package manager built in; mostly Node-compatible |
The competition has pushed Node.js forward: it now ships a native test runner, a built-in fetch, watch mode, and .env file loading — features inspired by its rivals.
The npm ecosystem
A runtime is only as useful as the code available for it. Node's package manager, npm, hosts the largest software registry in the world — over two million packages. Need to validate input, sign a JWT, talk to PostgreSQL, parse a CSV, or send email? A well-tested package almost certainly exists. This ecosystem is arguably Node's single biggest advantage — and, because a typical app pulls in hundreds of transitive dependencies, also its biggest source of security and supply-chain risk. We cover the package manager in depth starting at Introduction to npm.
Where Node.js shines (and where it does not)
APIs & web servers — REST and GraphQL backends that mostly shuttle data between clients and databases. Ideal: almost pure I/O.
Real-time apps — chat, collaboration, multiplayer games, live dashboards over WebSockets, where many connections sit mostly idle.
Microservices & serverless — small, fast-starting, low-memory services and functions.
Tooling & CLIs — Webpack, Vite, ESLint, Prettier, and most of the front-end toolchain run on Node.js.
Streaming — processing large files or data feeds chunk-by-chunk without loading everything into memory.
Node.js is a poor fit for heavy CPU-bound number crunching — video encoding, image processing at scale, scientific computing, machine-learning training — because a long computation blocks the single thread. See When to Use (and Not Use) Node.js for the honest trade-offs and the ways to work around them.