~ / initial commit / Node.js

Issue #11 — "add dependencies"

Node.js

The first commit of Node.js is three files, eight lines, and contains no Node.js. It's a .gitmodules file pointing at two C libraries Ryan Dahl had already written.

runtimejavascript
the commit
repo  nodejs/node
sha  9d7895c567
author  Ryan Dahl
date  2009-02-16
message  "add dependencies"
stats  3 files · +8 lines

If you ran git log --reverse on the Node.js repository and expected to see a file called main.cpp or node.cc or index.js or anything that looked like Node itself, you would be disappointed. The first commit of Node.js contains no Node.js.

Eight lines. Three files. And the commit message isn't even "initial commit". It's "add dependencies," as if Ryan Dahl was halfway through a train of thought and just happened to hit git commit -m at exactly the wrong moment for posterity.

1. What the three files contain

The entire content of Node's first commit can be reproduced in this newsletter. Here it is:

.gitmodules (6 lines):

[submodule "deps/oi"]
	path = deps/oi
	url = git://github.com/ry/liboi.git
[submodule "deps/ebb"]
	path = deps/ebb
	url = git://github.com/ry/libebb.git

deps/ebb (1 line):

Subproject commit 646065359957956c9a0a611aa91ee79fb4928e6d

deps/oi (1 line):

Subproject commit 988d97948634359cf2a6740dd5228c69a27015ae

That's it. That is the entire first commit of Node.js. The repository is born pointing at two submodules, liboi and libebb, and containing no original code of its own. Node did not begin with a main() function. It began with a manifest.

2. What liboi and libebb were

Both are Ryan Dahl's own libraries, which he had written before starting Node. Understanding what they do is understanding what Node fundamentally is.

libebb is a tiny, fast HTTP parser and server in C. Ryan wrote it in 2008 as a general-purpose HTTP server building block. It was the "we need something that parses HTTP requests extremely quickly and gets out of the way" primitive.

liboi is a C library for asynchronous I/O: specifically, a thin abstraction over the operating system's non-blocking socket APIs, with support for TLS. "oi" stands for "output/input" reversed, which is the kind of name you give a library when you are naming it for yourself and don't expect anyone else to use it. It would, eventually, be replaced by libev, and then libev would be wrapped by libuv, and libuv would become the thing you hear about when people talk about Node's event loop. But on day one, Node's I/O loop was liboi.

If you strip Node.js down to its philosophical essence, it is this: "write your server using JavaScript, which runs on V8, which is glued to a non-blocking I/O layer, which parses HTTP with something fast." Every word in that sentence has a file. V8 would be added later. The JavaScript bindings would come later. The HTTP parsing and the non-blocking I/O (the two things Ryan already had working) were added first.

The first commit of Node.js is not Node.js. It is the parts of Node.js that Ryan had already built in C before he decided Node.js was a good idea.

3. "add dependencies"

The commit message deserves its own look. "add dependencies" is not "initial commit" or "first commit" or "hello world" or "Inital Import." It is a continuation message, the kind of thing you write when you are midway through setting up a project and you are committing a step in a larger dance. It has the energy of someone clearing their throat.

Ryan later described Node's early days as a weekend project that he was not yet sure was going to work. He had been frustrated by blocking I/O in every web server he had used: Apache with its thread-per-request model, Rails with its process-per-request model, even nginx, which was non-blocking but whose module system was painful to extend. He wanted a server runtime where non-blocking was the default, where you could not accidentally do blocking I/O because the language itself was organized around callbacks.

He picked JavaScript because JavaScript already forced you to think in callbacks (thanks to setTimeout and the DOM), and because V8 had just been released by Google and was unreasonably fast. He picked liboi and libebb because he had already written them. And then he committed a .gitmodules file and wrote "add dependencies" in the message, as if this were any other Tuesday.

4. What came next

The second commit of Node.js, five days later, has the message "first working version" and adds the actual C++ binding layer, a Makefile, and a node.cc file. That is the commit you might expect to be the first commit. It is the one where the idea becomes real. But the git log says the idea became real on day six, not day one. Day one was about lining up the ingredients.

From there, it took Ryan a few months of late nights to get Node into a state where he could present it publicly. He did the first public talk at JSConf EU in November 2009, nine months after this commit. The talk is one of the most influential technical talks of the 2010s. It is the moment Node.js went from "Ryan Dahl's side project" to "a thing everyone is going to have to reckon with." You can still watch it on YouTube. Ryan is very calm and very precise and spends a lot of time explaining why blocking I/O is bad. The audience is skeptical at first, and then, at the point in the talk where he shows a chat server in twenty lines of code, visibly not skeptical.

All of that grew out of a commit that added two submodule pointers.

5. Nothing begins from nothing

The lesson I keep taking from this commit is that nothing begins from nothing. Every "initial commit" we have covered in this newsletter is, on close inspection, a moment partway through a longer process. Next.js was an API spec for a framework that did not exist. Rails was the extraction of Basecamp. Kubernetes was the externalization of Borg. VS Code was the public release of Monaco. Bitcoin's first git commit was eight months after launch. Go's first git commit was a program from 1972.

Node.js is perhaps the purest expression of this principle. Its first commit is literally pointers to its author's prior work. It begins by saying: here is what I already have. Now watch me build on top of it.

If you are waiting for the moment when you can start your ambitious project from scratch, having first cleared your mind and prepared a blank slate, this commit is a permission slip to stop waiting. You can just start. Commit the things you already have. Write the message as "add dependencies" because that is what you are doing. The project will figure out what it is later.

Ryan Dahl was not thinking big thoughts about ontology on February 16, 2009. He was wiring up his two C libraries so he could start calling into them from V8. Eight lines. Three files. A commit message so mundane that you could easily miss it in the log.

The rest of the story is the story of what he typed on top.

6. Footnotes from the commit log

  • Ryan Dahl's git author name in this commit is simply "Ryan," with no surname. Later commits use "Ryan Dahl." He was committing from a personal machine that did not yet have his full name in its git config.
  • liboi is archived on GitHub under the username ry, Ryan's handle. You can still find it at github.com/ry/liboi, though the last commit is from 2009.
  • libebb is also archived. The HTTP parser code inside it was eventually forked, reworked, and became http_parser: the library used by Node for years before being replaced by llhttp in 2019.
  • Ryan Dahl left Node.js in 2012 to work on other things. He returned to runtimes in 2018 with the release of Deno, which is a deliberate reboot of Node with all the decisions he later regretted unwound. Deno's first commit exists, and is also small, and is also a good candidate for a future issue of this newsletter.