Architecture Overview
Node.js is built on top of V8 (JavaScript engine) and libuv (event loop), with a C++ core that bridges JavaScript and native functionality.
V8 Engine JavaScript execution and memory management
libuv Event loop and async I/O
C++ Core Bindings and internal APIs
Core Components
Isolate
The v8::Isolate represents a single JavaScript engine instance with its own heap:
// Accessing the current isolate
Isolate * isolate = Isolate :: GetCurrent ();
// Given a binding function
void MyFunction ( const FunctionCallbackInfo < Value > & args ) {
Isolate * isolate = args . GetIsolate ();
}
// Given an Environment
Isolate * isolate = env -> isolate ();
V8 APIs are not thread-safe unless explicitly specified. In a typical application, the main thread and each Worker thread have one Isolate.
Environment
The Environment class represents a Node.js instance:
// Each Environment is associated with:
// - One event loop (uv_loop_t)
// - One Isolate (v8::Isolate)
// - One principal Realm
// Accessing current Environment
Environment * env = Environment :: GetCurrent (context);
Environment * env = Environment :: GetCurrent (isolate);
Environment * env = Environment :: GetCurrent (args);
Realm
The Realm class is a container for JavaScript objects and functions:
// Each Realm has:
// - A global object
// - A set of intrinsic objects
// - An associated v8::Context
Realm * realm = Realm :: GetCurrent (args);
Realm * realm = Realm :: GetCurrent (context);
File Structure
Node.js C++ files follow a consistent structure:
Key Directories
src/
├── node.h # Main Node.js embedder API
├── node.cc # Core Node.js implementation
├── api/ # Public C++ APIs
├── async_wrap.{h,cc} # Async tracking
├── base_object.{h,cc} # Base class for JS-backed objects
├── env.{h,cc} # Environment implementation
├── node_binding.{h,cc} # Binding system
├── node_buffer.{h,cc} # Buffer implementation
├── node_file.{h,cc} # File system operations
├── node_http_parser.cc # HTTP parser binding
└── ...
JavaScript Value Handles
V8 uses handles to access JavaScript objects safely:
Local Handles
Temporary pointers valid only in current function scope:
void GetFoo ( const FunctionCallbackInfo < Value > & args ) {
Isolate * isolate = args . GetIsolate ();
EscapableHandleScope handle_scope (isolate);
Local < Context > context = isolate -> GetCurrentContext ();
Local < Object > obj = args [ 0 ]. As < Object > ();
Local < String > foo_string =
String :: NewFromUtf8 (isolate, "foo" ). ToLocalChecked ();
Local < Value > return_value;
if ( obj -> Get (context, foo_string). ToLocal ( & return_value)) {
args . GetReturnValue (). Set ( handle_scope . Escape (return_value));
}
}
Local handles (v8::Local<T>) should never be allocated on the heap. Use v8::LocalVector<T> for heap-allocated collections.
Global Handles
Persistent references that survive across function calls:
v8 ::Global < v8 ::Object > reference;
void StoreReference ( Isolate * isolate , Local < Object > obj ) {
// Create a strong reference to obj
reference . Reset (isolate, obj);
}
Local < Object > LoadReference ( Isolate * isolate ) {
// Must be called with a HandleScope
return reference . Get (isolate);
}
Binding Functions
C++ functions exposed to JavaScript follow a specific signature:
void MyBinding ( const FunctionCallbackInfo < Value > & args ) {
// Access arguments
CHECK ( args [ 0 ]-> IsString ());
Local < String > arg0 = args [ 0 ]. As < String > ();
// Access this value
Local < Object > self = args . This ();
// Set return value
args . GetReturnValue (). Set ( 42 );
}
Registering Bindings
void Initialize ( Local < Object > target ,
Local < Value > unused ,
Local < Context > context ,
void* priv ) {
Environment * env = Environment :: GetCurrent (context);
Isolate * isolate = env -> isolate ();
// Add methods to exports
SetMethod (context, target, "myFunction" , MyBinding);
// Create a class
Local < FunctionTemplate > tmpl =
NewFunctionTemplate (isolate, MyClass ::New);
tmpl -> InstanceTemplate ()-> SetInternalFieldCount ( 1 );
// Add prototype methods
SetProtoMethod (isolate, tmpl, "method" , MyClass ::Method);
SetConstructorFunction (context, target, "MyClass" , tmpl);
}
NODE_BINDING_CONTEXT_AWARE_INTERNAL (my_module, Initialize)
BaseObject Pattern
Most C++ objects associated with JavaScript objects inherit from BaseObject:
class MyObject : public BaseObject {
public:
MyObject ( Environment * env , Local < Object > object )
: BaseObject (env, object) {
MakeWeak ();
}
static void New ( const FunctionCallbackInfo < Value > & args ) {
Environment * env = Environment :: GetCurrent (args);
new MyObject (env, args . This ());
}
static void Method ( const FunctionCallbackInfo < Value > & args ) {
MyObject * obj;
ASSIGN_OR_RETURN_UNWRAP ( & obj, args . This ());
// Use obj...
}
private:
// Object state
int counter_ = 0 ;
};
BaseObject provides automatic lifetime management, memory tracking, and integration with Node.js cleanup hooks.
AsyncWrap for Async Tracking
AsyncWrap enables async_hooks tracking:
class MyAsyncResource : public AsyncWrap {
public:
MyAsyncResource ( Environment * env , Local < Object > object )
: AsyncWrap (env, object, AsyncWrap ::PROVIDER_MYTYPE) {
MakeWeak ();
}
void DoAsyncWork () {
// Make callback into JavaScript
HandleScope handle_scope ( env ()-> isolate ());
Context ::Scope context_scope ( env ()-> context ());
Local < Value > argv[] = { /* ... */ };
MakeCallback ( env ()-> oncomplete_string (),
arraysize (argv), argv);
}
};
HandleWrap and ReqWrap
Wrappers for libuv handles and requests:
HandleWrap
class TCPWrap : public HandleWrap {
public:
static void New ( const FunctionCallbackInfo < Value > & args ) {
Environment * env = Environment :: GetCurrent (args);
new TCPWrap (env, args . This ());
}
TCPWrap ( Environment * env , Local < Object > object )
: HandleWrap (env,
object,
reinterpret_cast < uv_handle_t *> ( & handle_),
AsyncWrap ::PROVIDER_TCPWRAP) {
int r = uv_tcp_init ( env -> event_loop (), & handle_);
CHECK_EQ (r, 0 );
}
private:
uv_tcp_t handle_;
};
ReqWrap
class WriteWrap : public ReqWrap < uv_write_t > {
public:
WriteWrap ( Environment * env , Local < Object > object )
: ReqWrap (env, object, AsyncWrap ::PROVIDER_WRITEWRAP) {}
static void New ( const FunctionCallbackInfo < Value > & args ) {
Environment * env = Environment :: GetCurrent (args);
new WriteWrap (env, args . This ());
}
};
Internal Fields
V8 objects can store pointers in internal fields:
// Set up class with internal fields
Local < FunctionTemplate > tmpl =
NewFunctionTemplate (isolate, Constructor);
tmpl -> InstanceTemplate ()-> SetInternalFieldCount ( 1 );
// Store pointer
obj -> SetAlignedPointerInInternalField ( 0 , ptr);
// Retrieve pointer
void * ptr = obj -> GetAlignedPointerFromInternalField ( 0 );
Exception Handling
V8 uses Maybe and MaybeLocal types for error handling:
Maybe < double > SumArray ( Local < Context > context ,
Local < Array > array ) {
double sum = 0 ;
for ( uint32_t i = 0 ; i < array -> Length (); i ++ ) {
Local < Value > entry;
if ( ! array -> Get (context, i). ToLocal ( & entry)) {
// Exception occurred, return empty Maybe
return Nothing < double >();
}
if ( entry -> IsNumber ()) {
sum += entry . As < Number > ()-> Value ();
}
}
return Just (sum);
}
Never use .ToChecked() or .FromJust() unless you’re absolutely certain the operation cannot fail. Incorrect usage can crash Node.js.
Memory Management
Cleanup Hooks
void CleanupHook ( void* arg ) {
MyData * data = static_cast < MyData *> (arg);
delete data;
}
void Initialize ( Local < Object > exports ) {
Environment * env = Environment :: GetCurrent (exports);
MyData * data = new MyData ();
env -> AddCleanupHook (CleanupHook, data);
}
MemoryRetainer
class MyClass : public BaseObject ,
public MemoryRetainer {
public:
void MemoryInfo ( MemoryTracker * tracker ) const override {
tracker -> TrackField ( "buffer" , buffer_);
tracker -> TrackField ( "data" , data_);
}
SET_MEMORY_INFO_NAME ( MyClass )
SET_SELF_SIZE (MyClass)
private:
std ::string buffer_;
std ::vector < int > data_;
};
Avoid Checked Conversions Use .To() instead of .ToChecked() to handle potential exceptions
Reuse Handle Scopes Don’t create unnecessary HandleScope instances
Fast API Calls Use V8 fast API calls for performance-critical paths
Memory Tracking Implement MemoryRetainer for proper heap snapshot support
C++ Addons Build native addons using these internals
Embedding Embed Node.js in C++ applications
V8 Integration Deep dive into V8 engine
Source Code Explore Node.js C++ source