A brief history of Node.js
By Flavio Copes
Node.js history from Ryan Dahl in 2009 through io.js, left-pad, LTS, ES modules, fetch, TypeScript type stripping, and the 2026 schedule.
Believe it or not, Node.js is about 17 years old.
In comparison, JavaScript is about 31 years old and the web as we know it (after the introduction of Mosaic) is about 33 years old.
17 years is still a short time for a technology, but Node.js seems to have been around forever.
I’ve had the pleasure to work with Node since the early days when it was just 2 years old, and despite the little information available, you could already feel it was a huge thing.
In this post, I want to draw the big picture of Node in its history, to put things in perspective. If you need the practical side of staying current, I also wrote how to update Node.
- A little bit of history
- 2009
- 2010
- 2011
- 2012
- 2013
- 2014
- 2015
- 2016
- 2017
- 2018
- 2019
- 2020
- 2021
- 2022
- 2023
- 2024
- 2025
- 2026
A little bit of history
JavaScript is a programming language that was created at Netscape as a scripting tool to manipulate web pages inside their browser, Netscape Navigator.
Part of the business model of Netscape was to sell Web Servers, which included an environment called Netscape LiveWire, which could create dynamic pages using server-side JavaScript. So the idea of server-side JavaScript was not introduced by Node.js, but it’s old just like JavaScript - but at the time it was not successful.
One key factor that led to the rise of Node.js was timing. JavaScript since a few years was starting being considered a serious language, thanks to the “Web 2.0” applications that showed the world what a modern experience on the web could be like (think Google Maps or GMail).
The JavaScript engines performance bar raised considerably thanks to the browser competition battle, which is still going on. Development teams behind each major browser work hard every day to give us better performance, which is a huge win for JavaScript as a platform. V8, the engine that Node.js uses under the hood, is one of those and in particular it’s the Chrome JS engine.
But of course, Node.js is not popular just because of pure luck or timing. It introduced much innovative thinking on how to program in JavaScript on the server.
2009
Ryan Dahl created Node.js and showed it at JSConf EU in Berlin, in November 2009.
Netscape had already tried JavaScript on the server with LiveWire. What was new was the event loop.
Servers like Apache gave each connection its own thread. Threads are expensive. When a request waited on a file or a database, that thread sat idle and you ran out of them under load.
Browsers already did non-blocking I/O. You start a request, and a callback runs when the data arrives. Dahl took that model and put it on the server, with Google’s V8 engine running the JavaScript. One process, one thread for your code, and the kernel telling Node when the socket is ready.
npm started the same year, created by Isaac Schlueter. It became the package manager Node is hard to imagine without.
2010
Two libraries that still define how a lot of Node apps look showed up this year.
Express gave you app.get('/users', ...) and middleware. Most Node HTTP tutorials still start there.
Socket.io made WebSockets usable. Chat, live dashboards, multiplayer games: you could push data to the browser without polling.
2011
npm hit 1.0. The registry was no longer a side project.
Big companies started running Node in production. LinkedIn moved its mobile backend to it. Uber built services on it. The pitch was the same as in 2009: lots of open connections, not a lot of CPU per request.
Hapi was born at Walmart, as a more structured alternative to Express.
Node 0.6 shipped with official Windows support. Until then, Node was a Unix thing.
2012
Ryan Dahl stepped away from the project.
Isaac Schlueter took over as lead. Joyent still employed the core team and owned the trademarks. That company-led model worked while the project was small. It started to creak as the community grew, and it is the background for the fork two years later.
Node 0.8 shipped. Adoption kept climbing. The conversation moved from “is this real?” to how you deploy it.
2013
Ghost launched as a Node blogging platform. A real product, not a demo, running on the same stack you used for APIs.
Koa came from the Express team. It used generators (and later async/await) so you could write middleware without the callback pyramid. Express stayed the default. Koa showed where the style of Node HTTP code was going.
2014
This is the year Node almost split for good.
The public release was still 0.10. 0.12 was slow to land. The V8 inside Node lagged the V8 inside Chrome, so ES6 features browsers already had were missing.
Joyent owned the project. Contributors wanted a technical committee they could see, and faster releases.
Fedor Indutny and others forked Node in December. The fork was called io.js. The goal was newer V8, ES6, and open governance. The first io.js release followed in January 2015.
2015
In February the Node.js Foundation launched under the Linux Foundation. That was the governance answer: Node would not live or die on one company’s roadmap.
io.js merged back. The combined code shipped as Node 4.0.0 on 8 September 2015. io.js had already used 1, 2 and 3, so 4 was the next SemVer major.
Node 4 also started Long Term Support. Even-numbered majors get LTS (years of patches). Odd-numbered majors stay Current for about six months, then stop. New majors land every April and October. That even/odd cadence is what you still see through Node 26.
npm introduced private modules the same year, so companies could host packages without publishing them to the public registry.
2016
On 22 March, Azer Koçulu unpublished 273 packages from npm. One of them was left-pad, an 11-line function that pads a string.
The trigger was a name dispute. Kik wanted the kik package. After it escalated, npm gave the name to Kik. Azer took his packages down.
Builds broke across the ecosystem. Babel depended on left-pad. The hole lasted a couple of hours. npm republished left-pad 0.0.3, then changed unpublish so you cannot yank a package other people already depend on.
Yarn launched the same year, from Facebook, as another client for the npm registry. Lockfiles and faster installs were the pitch.
Node 6 shipped as the next LTS after 4.
2017
Node 8 became the LTS line. It bundled npm 5, which wrote a package-lock.json by default. util.promisify() turned callback APIs into promises. The WHATWG URL parser landed. N-API showed up as experimental: native addons that would not break on every new Node.
HTTP/2 arrived as experimental in Node 8.4, behind --expose-http2.
Node 9 was the odd Current release that sat next to 8.
2018
Node 10 (LTS) and Node 11 (Current).
N-API became stable in 10. Native modules could target one ABI and survive upgrades.
worker_threads landed as experimental in Node 10.5, behind --experimental-worker. The event loop is one thread. CPU-heavy work still blocked it. Workers gave you extra JS threads in the same process, with a message channel between them.
ES modules were experimental too. You used .mjs and --experimental-modules. CommonJS (require) stayed the default.
Ryan Dahl announced Deno at JSConf EU. Same person, a new runtime: TypeScript first, no node_modules, a permission prompt before the process can touch the disk or the network. It was a critique of Node’s old choices, not a drop-in replacement.
2019
Node 12 (LTS) and Node 13 (Current).
On 12 March the Node.js Foundation and the JS Foundation merged into the OpenJS Foundation. Node, webpack, ESLint, jQuery and a pile of other projects sat under the same umbrella. The Node.js Foundation URL above still redirects into that org.
From Node 11.7 you could require('worker_threads') without --experimental-worker. They were marked stable in 12.
ES modules were unflagged in Node 13.2.0. You could use import / export without --experimental-modules. The feature was still marked experimental in the docs. The warning went away in 14.
2020
Node 14 (LTS) and Node 15 (Current).
Optional chaining (?.) and nullish coalescing (??) arrived with V8 8.1, so they showed up in Node when that V8 did.
GitHub (owned by Microsoft) acquired npm in March. The registry stayed public. The company behind it did not.
Deno 1.0 shipped in May. A second JS server runtime was no longer a talk. It was a download.
2021
Node 16 LTS (Gallium) and Node 17 Current.
16 was the first LTS with official macOS Apple Silicon binaries. You could run Node on an M1 Mac without Rosetta.
timers/promises became stable. setTimeout as a promise, no wrapper of your own.
2022
Node 18 LTS (Hydrogen) and Node 19 Current.
fetch() landed, built on undici. Same fetch you use in the browser. It was experimental. --no-experimental-fetch turned it off if a polyfill already owned that name.
The built-in test runner showed up as experimental: node:test and node --test. You could run tests without Jest or Mocha if you wanted.
2023
Node 20 LTS (Iron) and Node 21 Current.
The test runner became stable. node --test was a real option, not a preview.
The permission model landed as --experimental-permission. You could deny fs, child processes, and worker creation unless the process opted in. It is the same idea Deno shipped with, bolted onto Node later.
Bun 1.0 shipped in September. Another runtime, Zig under the hood, aiming at Node compatibility and faster installs. Node still had the LTS promise and the npm ecosystem. Deno and Bun were the other runtimes in the conversation.
fetch became stable in Node 21.
2024
Node 22 LTS (Jod) and Node 23 Current.
22 is the line a lot of production apps sat on going into 2026.
require() of ES modules landed in 22, behind --experimental-require-module at first, then on by default later in that line (22.12). A CommonJS file can pull in an ESM package without a full rewrite, as long as that graph has no top-level await.
The WebSocket client is on by default. Watch mode (node --watch) is stable. node --run runs a script from package.json without starting npm. It showed up experimental in 22 and was marked stable in 23.
Node 22.6.0 added --experimental-strip-types. Node can run a .ts file by deleting the type annotations and executing the rest as JavaScript. No type checking. Enums and other syntax that need extra JS still fail unless you transform them.
2025
Node 24 LTS (Krypton) and Node 25 Current.
The permission model dropped the experimental name, so the flag on 24 is --permission.
URLPattern is a global, like in the browser. npm 11 ships with the 24 line.
Type stripping turned on by default in Node 23.6 and in the 22.18 LTS update, so node app.ts worked without a flag. It was marked stable in 24.12 and 25.2. Pass --no-strip-types if you want the old behavior.
2026
- Node 24 is Active LTS until 20 October 2026, then Maintenance LTS
- Node 26 is Current and enters LTS on 28 October 2026, the last release on the old even/odd schedule
- Node 25 reached end of life in June 2026
- From Node.js 27, one major a year, and every release becomes LTS. Alpha runs October to March, Current April to October, then 30 months of LTS
- Node.js 27 Alpha starts 28 October 2026. 27.0.0 ships April 2027, LTS October 2027, EOL April 2030
Want me to talk about your product? You can sponsor this site.
Related posts about node: