Skip to main content
Node.js provides C++ APIs for executing JavaScript in a Node.js environment from other C++ software.
Using Node.js as an embedded library is different from writing code executed by Node.js. Breaking changes do not follow typical Node.js deprecation policy and may occur on each semver-major release without prior warning.

Overview

The embedder API allows you to:
  • Create and manage Node.js instances programmatically
  • Execute JavaScript code from C++
  • Create multiple isolated Node.js environments
  • Integrate Node.js into existing C++ applications

Documentation

The complete API documentation is available in:
  • src/node.h in the Node.js source tree
  • V8 embedder API documentation

Example: Running JavaScript Code

The following example shows how to create an application equivalent to node -e <code> that runs JavaScript in a Node.js environment.

Setting Up Per-Process State

Node.js requires per-process state management:
#include <node.h>

int main(int argc, char** argv) {
  argv = uv_setup_args(argc, argv);
  std::vector<std::string> args(argv, argv + argc);
  
  // Parse Node.js CLI options
  std::unique_ptr<node::InitializationResult> result =
      node::InitializeOncePerProcess(args, {
        node::ProcessInitializationFlags::kNoInitializeV8,
        node::ProcessInitializationFlags::kNoInitializeNodeV8Platform
      });

  // Check for errors
  for (const std::string& error : result->errors())
    fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
  if (result->early_return() != 0) {
    return result->exit_code();
  }

  // Create V8 platform
  std::unique_ptr<MultiIsolatePlatform> platform =
      MultiIsolatePlatform::Create(4);
  V8::InitializePlatform(platform.get());
  V8::Initialize();

  // Run Node.js instance
  int ret = RunNodeInstance(
      platform.get(), result->args(), result->exec_args());

  // Cleanup
  V8::Dispose();
  V8::DisposePlatform();
  node::TearDownOncePerProcess();
  
  return ret;
}

Setting Up Per-Instance State

Each Node.js instance requires:
  • One v8::Isolate (JS engine instance)
  • One uv_loop_t (event loop)
  • One or more v8::Contexts
  • One node::IsolateData instance
int RunNodeInstance(MultiIsolatePlatform* platform,
                    const std::vector<std::string>& args,
                    const std::vector<std::string>& exec_args) {
  int exit_code = 0;

  // Setup libuv event loop, v8::Isolate, and Node.js Environment
  std::vector<std::string> errors;
  std::unique_ptr<CommonEnvironmentSetup> setup =
      CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
  
  if (!setup) {
    for (const std::string& err : errors)
      fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str());
    return 1;
  }

  Isolate* isolate = setup->isolate();
  Environment* env = setup->env();

  {
    Locker locker(isolate);
    Isolate::Scope isolate_scope(isolate);
    HandleScope handle_scope(isolate);
    Context::Scope context_scope(setup->context());

    // Load and execute JavaScript
    MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
        env,
        "const publicRequire ="
        "  require('node:module').createRequire(process.cwd() + '/');"
        "globalThis.require = publicRequire;"
        "require('node:vm').runInThisContext(process.argv[1]);");

    if (loadenv_ret.IsEmpty())  // JS exception occurred
      return 1;

    // Run event loop
    exit_code = node::SpinEventLoop(env).FromMaybe(1);

    // Stop the environment
    node::Stop(env);
  }

  return exit_code;
}

Key Concepts

Node.js Instance

A Node.js instance (commonly referred to as node::Environment) represents an isolated JavaScript execution environment. Each Environment has:
  • Exactly one v8::Isolate
  • Exactly one uv_loop_t event loop
  • One main v8::Context (and potentially additional contexts)
  • One node::IsolateData shared across environments using the same isolate

V8 Isolate

An isolate is an independent instance of the V8 JavaScript engine. Requirements:
  • Needs a v8::ArrayBuffer::Allocator
  • Use node::ArrayBufferAllocator::Create() for the default Node.js allocator
  • Must be registered with MultiIsolatePlatform when using workers
Helper function:
node::NewIsolate()  // Creates and sets up a v8::Isolate with Node.js hooks

Environment Lifecycle

Node.js environments can be:
  • Created at startup in the main process
  • Created on separate threads as Worker threads
  • Created and destroyed multiple times during application lifecycle
Important: Native addons may be called:
  • Multiple times from multiple contexts
  • Concurrently from multiple threads
  • Need to manage per-environment state appropriately

Common Use Cases

Embedding in Application

Integrate Node.js into a C++ application:
// Initialize once per process
node::InitializeOncePerProcess(args, flags);

// Create platform
auto platform = MultiIsolatePlatform::Create(4);
V8::InitializePlatform(platform.get());
V8::Initialize();

// Create and run instances as needed
RunNodeInstance(platform.get(), args, exec_args);

// Cleanup
V8::Dispose();
V8::DisposePlatform();
node::TearDownOncePerProcess();

Multiple Environments

Run multiple Node.js environments:
// Create multiple environments sharing one isolate
for (int i = 0; i < num_environments; i++) {
  auto setup = CommonEnvironmentSetup::Create(
      platform, &errors, args, exec_args);
  // Use each environment...
}

Worker Threads

Support for Worker threads requires:
  1. Creating a MultiIsolatePlatform instance
  2. Registering isolates with the platform
  3. Proper cleanup with environment hooks
// Platform supports workers
auto platform = MultiIsolatePlatform::Create(4);

// Isolates registered automatically with node::NewIsolate()
Isolate* isolate = node::NewIsolate(
    allocator.get(),
    &loop,
    platform.get());

Memory Management

ArrayBuffer Allocator

Provide an allocator for V8:
std::unique_ptr<v8::ArrayBuffer::Allocator> allocator =
    node::ArrayBufferAllocator::Create();
Benefits of Node.js allocator:
  • Performance optimizations for Node.js Buffer API
  • Required for tracking ArrayBuffer memory in process.memoryUsage()

Cleanup

Proper cleanup is essential:
// Per-instance cleanup
node::Stop(env);  // Stop event loop

// Per-process cleanup
V8::Dispose();
V8::DisposePlatform();
node::TearDownOncePerProcess();

Advanced Features

Custom Module Loading

Control how modules are loaded:
MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
    env,
    "const publicRequire ="
    "  require('node:module').createRequire(process.cwd() + '/');"
    "globalThis.require = publicRequire;");

Event Loop Control

Manage the event loop:
// Run until no more work
exit_code = node::SpinEventLoop(env).FromMaybe(1);

// Stop from any thread
node::Stop(env);

Process Initialization Flags

Control initialization behavior:
node::ProcessInitializationFlags::kNoInitializeV8
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform
node::ProcessInitializationFlags::kNoDefaultSignalHandling

Error Handling

Handle errors during initialization:
std::vector<std::string> errors;
auto setup = CommonEnvironmentSetup::Create(
    platform, &errors, args, exec_args);

if (!setup) {
  for (const std::string& err : errors)
    fprintf(stderr, "Error: %s\n", err.c_str());
  return 1;
}
Check for JavaScript exceptions:
MaybeLocal<Value> result = node::LoadEnvironment(env, code);
if (result.IsEmpty()) {
  // JavaScript exception occurred
  return 1;
}

Best Practices

  1. Initialize Once: Call InitializeOncePerProcess() only once per process
  2. Platform Management: Create platform before V8 initialization
  3. Proper Cleanup: Always cleanup in reverse order of initialization
  4. Thread Safety: Use proper locking when accessing isolates from multiple threads
  5. Error Checking: Check all initialization results and handle errors
  6. Event Loop: Always run the event loop to completion with SpinEventLoop()

Complete Example

Full example available in the Node.js source tree: