Documentation Index
Fetch the complete documentation index at: https://mintlify.com/nodejs/node/llms.txt
Use this file to discover all available pages before exploring further.
Addons are dynamically-linked shared objects written in C++. The require() function can load addons as ordinary Node.js modules. Addons provide an interface between JavaScript and C/C++ libraries.
Implementation Options
There are three options for implementing addons:
- Node-API (recommended) - Provides ABI stability across Node.js versions
- nan (Native Abstractions for Node.js) - Compatibility layer for V8 API changes
- Direct V8, libuv, and Node.js APIs - For advanced use cases requiring low-level access
Unless you need direct access to functionality not exposed by Node-API, use Node-API. Refer to C/C++ addons with Node-API for more information.
Components
When not using Node-API, implementing addons requires knowledge of several components:
The C++ library Node.js uses to provide the JavaScript implementation. V8 provides mechanisms for:
- Creating objects
- Calling functions
- Managing memory and garbage collection
The V8 API is documented in v8.h and available online.
libuv
The C library implementing the Node.js event loop, worker threads, and asynchronous behaviors. It provides:
- Cross-platform abstraction for common system tasks
- File system interactions
- Sockets and networking
- Timers and system events
- Threading abstraction for sophisticated async operations
Internal Node.js Libraries
Node.js exports C++ APIs that addons can use, including the node::ObjectWrap class.
Other Libraries
Node.js statically links libraries including OpenSSL, V8, and zlib, whose symbols are purposefully re-exported for addon use.
Hello World Example
This simple addon is equivalent to the following JavaScript:
module.exports.hello = () => 'world';
C++ implementation (hello.cc):
#include <node.h>
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
void Method(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(String::NewFromUtf8(
isolate, "world").ToLocalChecked());
}
void Initialize(Local<Object> exports) {
NODE_SET_METHOD(exports, "hello", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
} // namespace demo
Building Addons
Once the source code is written, it must be compiled into the binary addon.node file.
Create a binding.gyp file:
{
"targets": [
{
"target_name": "addon",
"sources": [ "hello.cc" ]
}
]
}
Build the addon:
npm install -g node-gyp
node-gyp configure
node-gyp build
Using Addons
Once built, the addon can be loaded from Node.js:
const addon = require('./build/Release/addon');
console.log(addon.hello());
// Prints: 'world'
Context-Aware Addons
Addons may need to be loaded multiple times in multiple contexts. For example, Electron runs multiple instances of Node.js in a single process.
Construct a context-aware addon using NODE_MODULE_INITIALIZER:
using namespace v8;
extern "C" NODE_MODULE_EXPORT void
NODE_MODULE_INITIALIZER(Local<Object> exports,
Local<Value> module,
Local<Context> context) {
/* Perform addon initialization */
}
Or use NODE_MODULE_INIT():
NODE_MODULE_INIT(/* exports, module, context */) {
Isolate* isolate = Isolate::GetCurrent();
// Initialize addon
}
Managing Global State
Context-aware addons require careful management of global static data:
- Define a class to hold per-addon-instance data with a static
DeleteInstance() method
- Heap-allocate an instance in the addon initializer
- Call
node::AddEnvironmentCleanupHook() to ensure cleanup
- Store the instance in a
v8::External
- Pass the
v8::External to all exposed methods
Example:
class AddonData {
public:
explicit AddonData(Isolate* isolate) : call_count(0) {
node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this);
}
int call_count;
static void DeleteInstance(void* data) {
delete static_cast<AddonData*>(data);
}
};
NODE_MODULE_INIT() {
Isolate* isolate = Isolate::GetCurrent();
AddonData* data = new AddonData(isolate);
Local<External> external = External::New(isolate, data);
exports->Set(context,
String::NewFromUtf8(isolate, "method").ToLocalChecked(),
FunctionTemplate::New(isolate, Method, external)
->GetFunction(context).ToLocalChecked()).FromJust();
}
Worker Thread Support
To support Worker threads, addons must:
- Be a Node-API addon, or
- Be declared as context-aware using
NODE_MODULE_INIT()
Addons must clean up resources when threads exit using AddEnvironmentCleanupHook():
void AddEnvironmentCleanupHook(v8::Isolate* isolate,
void (*fun)(void* arg),
void* arg);
Linking to Node.js Libraries
Addons are required to link to V8 and may link to other Node.js dependencies:
- Include headers with
#include <...> statements
node-gyp automatically locates appropriate headers
- When downloading full source, addons have access to all Node.js dependencies
- When downloading only headers, only exported symbols are available
Common Patterns
Function Arguments
Reading arguments passed from JavaScript:
void Add(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 2) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Wrong number of arguments")
.ToLocalChecked()));
return;
}
if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Wrong arguments")
.ToLocalChecked()));
return;
}
double value = args[0].As<Number>()->Value() +
args[1].As<Number>()->Value();
args.GetReturnValue().Set(Number::New(isolate, value));
}
Callbacks
Passing JavaScript functions to C++ and executing them:
void RunCallback(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Function> cb = Local<Function>::Cast(args[0]);
const unsigned argc = 1;
Local<Value> argv[argc] = {
String::NewFromUtf8(isolate, "hello world").ToLocalChecked()
};
cb->Call(context, Null(isolate), argc, argv).ToLocalChecked();
}
Object Factory
Creating and returning objects from C++:
void CreateObject(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Object> obj = Object::New(isolate);
obj->Set(context,
String::NewFromUtf8(isolate, "msg").ToLocalChecked(),
args[0]->ToString(context).ToLocalChecked())
.FromJust();
args.GetReturnValue().Set(obj);
}
Wrapping C++ Objects
Wrap C++ classes for use with JavaScript new operator by inheriting from node::ObjectWrap:
class MyObject : public node::ObjectWrap {
public:
static void Init(v8::Local<v8::Object> exports);
private:
explicit MyObject(double value = 0);
~MyObject();
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
double value_;
};
Node-API Alternative
Node-API is an API for building native addons that is:
- Independent from the underlying JavaScript runtime (V8)
- Maintained as part of Node.js
- ABI stable across Node.js versions
- Allows modules compiled for one version to run on later versions without recompilation
Example using Node-API:
#include <node_api.h>
namespace demo {
napi_value Method(napi_env env, napi_callback_info args) {
napi_value greeting;
napi_status status;
status = napi_create_string_utf8(env, "world", NAPI_AUTO_LENGTH, &greeting);
if (status != napi_ok) return nullptr;
return greeting;
}
napi_value init(napi_env env, napi_value exports) {
napi_status status;
napi_value fn;
status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn);
if (status != napi_ok) return nullptr;
status = napi_set_named_property(env, exports, "hello", fn);
if (status != napi_ok) return nullptr;
return exports;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
} // namespace demo
See C/C++ addons with Node-API for complete documentation.