diff --git a/CPP_STYLE_GUIDE.md b/CPP_STYLE_GUIDE.md
index 8808405b2c6553..f29c8df3210caa 100644
--- a/CPP_STYLE_GUIDE.md
+++ b/CPP_STYLE_GUIDE.md
@@ -1,5 +1,8 @@
# C++ Style Guide
+See also the [C++ codebase README](src/README.md) for C++ idioms in the Node.js
+codebase not related to stylistic issues.
+
## Table of Contents
* [Guides and References](#guides-and-references)
diff --git a/doc/guides/contributing/pull-requests.md b/doc/guides/contributing/pull-requests.md
index f23c92fa024e63..31e3ba64757c12 100644
--- a/doc/guides/contributing/pull-requests.md
+++ b/doc/guides/contributing/pull-requests.md
@@ -121,7 +121,9 @@ in the API docs will also be checked when running `make lint` (or
use `REPLACEME` for the version number in the documentation YAML.
For contributing C++ code, you may want to look at the
-[C++ Style Guide](../../../CPP_STYLE_GUIDE.md).
+[C++ Style Guide](../../../CPP_STYLE_GUIDE.md), as well as the
+[README of `src/`](../../../src/README.md) for an overview over Node.js
+C++ internals.
### Step 4: Commit
diff --git a/src/README.md b/src/README.md
new file mode 100644
index 00000000000000..48cf99991552e2
--- /dev/null
+++ b/src/README.md
@@ -0,0 +1,896 @@
+# Node.js C++ codebase
+
+Hi! 👋 You’ve found the C++ code backing Node.js. This README aims to help you
+get started working on it and document some idioms you may encounter while
+doing so.
+
+## Coding style
+
+Node.js has a document detailing its [C++ coding style][]
+that can be helpful as a reference for stylistic issues.
+
+## V8 API documentation
+
+A lot of the Node.js codebase is around what the underlying JavaScript engine,
+V8, provides through its API for embedders. Knowledge of this API can also be
+useful when working with native addons for Node.js written in C++, although for
+new projects [N-API][] is typically the better alternative.
+
+V8 does not provide much public API documentation beyond what is
+available in its C++ header files, most importantly `v8.h`, which can be
+accessed online in the following locations:
+
+* On GitHub: [`v8.h` in Node.js master][]
+* On GitHub: [`v8.h` in V8 master][]
+* On the Chromium project’s Code Search application: [`v8.h` in Code Search][]
+
+V8 also provides an [introduction for V8 embedders][],
+which can be useful for understanding some of the concepts it uses in its
+embedder API.
+
+Important concepts when using V8 are the ones of [`Isolate`][]s and
+[JavaScript value handles][].
+
+## libuv API documentation
+
+The other major dependency of Node.js is [libuv][], providing
+the [event loop][] and other operation system abstractions to Node.js.
+
+There is a [reference documentation for the libuv API][].
+
+## Helpful concepts
+
+A number of concepts are involved in putting together Node.js on top of V8 and
+libuv. This section aims to explain some of them and how they work together.
+
+
+### `Isolate`
+
+The `v8::Isolate` class represents a single JavaScript engine instance, in
+particular a set of JavaScript objects that can refer to each other
+(the “heap”).
+
+The `v8::Isolate` is often passed to other V8 API functions, and provides some
+APIs for managing the behaviour of the JavaScript engine or querying about its
+current state or statistics such as memory usage.
+
+V8 APIs are not thread-safe unless explicitly specified. In a typical Node.js
+application, the main thread and any `Worker` threads each have one `Isolate`,
+and JavaScript objects from one `Isolate` cannot refer to objects from
+another `Isolate`.
+
+Garbage collection, as well as other operations that affect the entire heap,
+happen on a per-`Isolate` basis.
+
+Typical ways of accessing the current `Isolate` in the Node.js code are:
+
+* Given a `FunctionCallbackInfo` for a [binding function][],
+ using `args.GetIsolate()`.
+* Given a [`Context`][], using `context->GetIsolate()`.
+* Given a [`Environment`][], using `env->isolate()`.
+
+### V8 JavaScript values
+
+V8 provides classes that mostly correspond to JavaScript types; for example,
+`v8::Value` is a class representing any kind of JavaScript type, with
+subclasses such as `v8::Number` (which in turn has subclasses like `v8::Int32`),
+`v8::Boolean` or `v8::Object`. Most types are represented by subclasses
+of `v8::Object`, e.g. `v8::Uint8Array` or `v8::Date`.
+
+
+### Internal fields
+
+V8 provides the ability to store data in so-called “internal fields” inside
+`v8::Object`s that were created as instances of C++-backed classes. The number
+of fields needs to be defined when creating that class.
+
+Both JavaScript values and `void*` pointers may be stored in such fields.
+In most native Node.js objects, the first internal field is used to store a
+pointer to a [`BaseObject`][] subclass, which then contains all relevant
+information associated with the JavaScript object.
+
+The most typical way of working internal fields are:
+
+* `obj->InternalFieldCount()` to look up the number of internal fields for an
+ object (`0` for regular JavaScript objects).
+* `obj->GetInternalField(i)` to get a JavaScript value from an internal field.
+* `obj->SetInternalField(i, v)` to store a JavaScript value in an
+ internal field.
+* `obj->GetAlignedPointerFromInternalField(i)` to get a `void*` pointer from an
+ internal field.
+* `obj->SetAlignedPointerInInternalField(i, p)` to store a `void*` pointer in an
+ internal field.
+
+[`Context`][]s provide the same feature under the name “embedder data”.
+
+
+### JavaScript value handles
+
+All JavaScript values are accessed through the V8 API through so-called handles,
+of which there are two types: [`Local`][]s and [`Global`][]s.
+
+
+#### `Local` handles
+
+A `v8::Local` handle is a temporary pointer to a JavaScript object, where
+“temporary” usually means that is no longer needed after the current function
+is done executing. `Local` handles can only be allocated on the C++ stack.
+
+Most of the V8 API uses `Local` handles to work with JavaScript values or return
+them from functions.
+
+Whenever a `Local` handle is created, a `v8::HandleScope` or
+`v8::EscapableHandleScope` object must exist on the stack. The `Local` is then
+added to that scope and deleted along with it.
+
+When inside a [binding function][], a `HandleScope` already exists outside of
+it, so there is no need to explicitly create one.
+
+`EscapableHandleScope`s can be used to allow a single `Local` handle to be
+passed to the outer scope. This is useful when a function returns a `Local`.
+
+The following JavaScript and C++ functions are mostly equivalent:
+
+```js
+function getFoo(obj) {
+ return obj.foo;
+}
+```
+
+```c++
+v8::Local GetFoo(v8::Local context,
+ v8::Local obj) {
+ v8::Isolate* isolate = context->GetIsolate();
+ v8::EscapableHandleScope handle_scope(isolate);
+
+ // The 'foo_string' handle cannot be returned from this function because
+ // it is not “escaped” with `.Escape()`.
+ v8::Local foo_string =
+ v8::String::NewFromUtf8(isolate,
+ "foo",
+ v8::NewStringType::kNormal).ToLocalChecked();
+
+ v8::Local return_value;
+ if (obj->Get(context, foo_string).ToLocal(&return_value)) {
+ return handle_scope.Escape(return_value);
+ } else {
+ // There was a JS exception! Handle it somehow.
+ return v8::Local();
+ }
+}
+```
+
+See [exception handling][] for more information about the usage of `.To()`,
+`.ToLocalChecked()`, `v8::Maybe` and `v8::MaybeLocal` usage.
+
+##### Casting local handles
+
+If it is known that a `Local` refers to a more specific type, it can
+be cast to that type using `.As<...>()`:
+
+```c++
+v8::Local some_value;
+// CHECK() is a Node.js utilitity that works similar to assert().
+CHECK(some_value->IsUint8Array());
+v8::Local as_uint8 = some_value.As();
+```
+
+Generally, using `val.As()` is only valid if `val->IsX()` is true, and
+failing to follow that rule may lead to crashes.
+
+##### Detecting handle leaks
+
+If it is expected that no `Local` handles should be created within a given
+scope unless explicitly within a `HandleScope`, a `SealHandleScope` can be used.
+
+For example, there is a `SealHandleScope` around the event loop, forcing
+any functions that are called from the event loop and want to run or access
+JavaScript code to create `HandleScope`s.
+
+
+#### `Global` handles
+
+A `v8::Global` handle (sometimes also referred to by the name of its parent
+class `Persistent`, although use of that is discouraged in Node.js) is a
+reference to a JavaScript object that can remain active as long as the engine
+instance is active.
+
+Global handles can be either strong or weak. Strong global handles are so-called
+“GC roots”, meaning that they will keep the JavaScript object they refer to
+alive even if no other objects refer to them. Weak global handles do not do
+that, and instead optionally call a callback when the object they refer to
+is garbage-collected.
+
+```c++
+v8::Global reference;
+
+void StoreReference(v8::Isolate* isolate, v8::Local obj) {
+ // Create a strong reference to `obj`.
+ reference.Reset(isolate, obj);
+}
+
+// Must be called with a HandleScope around it.
+v8::Local LoadReference(v8::Isolate* isolate) {
+ return reference.Get(isolate);
+}
+```
+
+##### `Eternal` handles
+
+`v8::Eternal` handles are a special kind of handles similar to `v8::Global`
+handles, with the exception that the values they point to are never
+garbage-collected while the JavaScript Engine instance is alive, even if
+the `v8::Eternal` itself is destroyed at some point. This type of handle
+is rarely used.
+
+
+### `Context`
+
+JavaScript allows multiple global objects and sets of built-in JavaScript
+objects (like the `Object` or `Array` functions) to coexist inside the same
+heap. Node.js exposes this ability through the [`vm` module][].
+
+V8 refers to each of these global objects and their associated builtins as a
+`Context`.
+
+Currently, in Node.js there is one main `Context` associated with an
+[`Environment`][] instance, and most Node.js features will only work inside
+that context. (The only exception at the time of writing are
+[`MessagePort`][] objects.) This restriction is not inherent to the design of
+Node.js, and a sufficiently committed person could restructure Node.js to
+provide built-in modules inside of `vm.Context`s.
+
+Often, the `Context` is passed around for [exception handling][].
+Typical ways of accessing the current `Environment` in the Node.js code are:
+
+* Given an [`Isolate`][], using `isolate->GetCurrentContext()`.
+* Given an [`Environment`][], using `env->context()` to get the `Environment`’s
+ main context.
+
+
+### Event loop
+
+The main abstraction for an event loop inside Node.js is the `uv_loop_t` struct.
+Typically, there is one event loop per thread. This includes not only the main
+thread and Workers, but also helper threads that may occasionally be spawned
+in the course of running a Node.js program.
+
+The current event loop can be accessed using `env->event_loop()` given an
+[`Environment`][] instance. The restriction of using a single event loop
+is not inherent to the design of Node.js, and a sufficiently committed person
+could restructure Node.js to provide e.g. the ability to run parts of Node.js
+inside an event loop separate from the active thread’s event loop.
+
+
+### `Environment`
+
+Node.js instances are represented by the `Environment` class.
+
+Currently, every `Environment` class is associated with:
+
+* One [event loop][]
+* One [`Isolate`][]
+* One main [`Context`][]
+
+The `Environment` class contains a large number of different fields for
+different Node.js modules, for example a libuv timer for `setTimeout()` or
+the memory for a `Float64Array` that the `fs` module uses for storing data
+returned from a `fs.stat()` call.
+
+It also provides [cleanup hooks][] and maintains a list of [`BaseObject`][]
+instances.
+
+Typical ways of accessing the current `Environment` in the Node.js code are:
+
+* Given a `FunctionCallbackInfo` for a [binding function][],
+ using `Environment::GetCurrent(args)`.
+* Given a [`BaseObject`][], using `env()` or `self->env()`.
+* Given a [`Context`][], using `Environment::GetCurrent(context)`.
+ This requires that `context` has been associated with the `Environment`
+ instance, e.g. is the main `Context` for the `Environment` or one of its
+ `vm.Context`s.
+* Given an [`Isolate`][], using `Environment::GetCurrent(isolate)`. This looks
+ up the current [`Context`][] and then uses that.
+
+
+### `IsolateData`
+
+Every Node.js instance ([`Environment`][]) is associated with one `IsolateData`
+instance that contains information about or associated with a given
+[`Isolate`][].
+
+#### String table
+
+`IsolateData` contains a list of strings that can be quickly accessed
+inside Node.js code, e.g. given an `Environment` instance `env` the JavaScript
+string “name” can be accessed through `env->name_string()` without actually
+creating a new JavaScript string.
+
+### Platform
+
+Every process that uses V8 has a `v8::Platform` instance that provides some
+functionalities to V8, most importantly the ability to schedule work on
+background threads.
+
+Node.js provides a `NodePlatform` class that implements the `v8::Platform`
+interface and uses libuv for providing background threading abilities.
+
+The platform can be accessed through `isolate_data->platform()` given an
+[`IsolateData`][] instance, although that only works when:
+
+* The current Node.js instance was not started by an embedder; or
+* The current Node.js instance was started by an embedder whose `v8::Platform`
+ implementation also implement’s the `node::MultiIsolatePlatform` interface
+ and who passed this to Node.js.
+
+
+### Binding functions
+
+C++ functions exposed to JS follow a specific signature. The following example
+is from `node_util.cc`:
+
+```c++
+void ArrayBufferViewHasBuffer(const FunctionCallbackInfo& args) {
+ CHECK(args[0]->IsArrayBufferView());
+ args.GetReturnValue().Set(args[0].As()->HasBuffer());
+}
+```
+
+(Namespaces are usually omitted through the use of `using` statements in the
+Node.js source code.)
+
+`args[n]` is a `Local` that represents the n-th argument passed to the
+function. `args.This()` is the `this` value inside this function call.
+`args.Holder()` is equivalent to `args.This()` in all use cases inside of
+Node.js.
+
+`args.GetReturnValue()` is a placeholder for the return value of the function,
+and provides a `.Set()` method that can be called with a boolean, integer,
+floating-point number or a `Local` to set the return value.
+
+Node.js provides various helpers for building JS classes in C++ and/or attaching
+C++ functions to the exports of a built-in module:
+
+```c++
+void Initialize(Local