Backend Development 13 min read

Mastering Node.js C++ Addons: Seamless Data Transfer Between JavaScript and C++

This article explains how to use Node.js C++ Addons and the V8 API to define, convert, and transfer JavaScript primitive and complex types to C++ and back, covering type inheritance, immutability, argument handling, and the role of NAN for version stability.

Taobao Frontend Technology
Taobao Frontend Technology
Taobao Frontend Technology
Mastering Node.js C++ Addons: Seamless Data Transfer Between JavaScript and C++

I love using Node.js, but for compute‑intensive scenarios it falls short, so C++ becomes a natural companion and Node.js offers C/C++ Addons via the V8 API.

JavaScript primitive types (String, Number, Boolean, null, undefined) inherit from

Primitive

, which in turn inherits from

Value

; V8 also provides integer types such as

Int32

and

Uint32

, and other structures like Object, Array, and Map.

All JavaScript values are stored in

Local

objects that represent runtime memory units.

The following snippet declares a

Number

value using the isolate of the V8 virtual machine:

#include <node.h>
#include <v8.h>
using namespace v8;
void Test(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = args.GetIsolate();
    // declare variable
    Local<Number> retval = v8::Number::New(isolate, 1000);
}
void init(Local<Object> exports, Local<Object> module) {
    NODE_SET_METHOD(exports, "getTestValue", Test);
}
NODE_MODULE(returnValue, init)

V8’s type API shows that basic JavaScript types are only declared, not assigned, because:

JavaScript primitives are immutable; variables point to memory cells, and reassignment creates a new cell.

Function arguments are passed by value, so changes in C++ do not affect the original JavaScript values.

Declaring a basic type with

Local<Value>

creates a reference to a memory cell, and immutability prevents reassigning that reference.

Data Flow C++ → JavaScript

The demo below defines common JavaScript types (primitives, Object, Array, Function) in C++ and returns an object to JavaScript:

#include <node.h>
#include <v8.h>
using namespace v8;
void MyFunction(const v8::FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    args.GetReturnValue().Set(String::NewFromUtf8(isolate, "Hello World!"));
}
void Test(const v8::FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = args.GetIsolate();
    Local<Number> retval = v8::Number::New(isolate, 1000);
    Local<String> str = v8::String::NewFromUtf8(isolate, "Hello World!");
    Local<Object> obj = v8::Object::New(isolate);
    obj->Set(v8::String::NewFromUtf8(isolate, "arg1"), str);
    obj->Set(v8::String::NewFromUtf8(isolate, "arg2"), retval);
    Local<FunctionTemplate> tpl = v8::FunctionTemplate::New(isolate, MyFunction);
    Local<Function> fn = tpl->GetFunction();
    fn->SetName(String::NewFromUtf8(isolate, "theFunction"));
    obj->Set(v8::String::NewFromUtf8(isolate, "arg3"), fn);
    Local<Boolean> flag = Boolean::New(isolate, true);
    obj->Set(String::NewFromUtf8(isolate, "arg4"), flag);
    Local<Array> arr = Array::New(isolate);
    arr->Set(0, Number::New(isolate, 1));
    arr->Set(1, Number::New(isolate, 10));
    arr->Set(2, Number::New(isolate, 100));
    arr->Set(3, Number::New(isolate, 1000));
    obj->Set(String::NewFromUtf8(isolate, "arg5"), arr);
    Local<Value> und = Undefined(isolate);
    obj->Set(String::NewFromUtf8(isolate, "arg6"), und);
    Local<Value> nul = Null(isolate);
    obj->Set(String::NewFromUtf8(isolate, "arg7"), nul);
    args.GetReturnValue().Set(obj);
}
void init(Local<Object> exports, Local<Object> module) {
    NODE_SET_METHOD(exports, "getTestValue", Test);
}
NODE_MODULE(returnValue, init)

After compiling with

node-gyp

, the addon can be used as:

const returnValue = require('./build/Release/returnValue.node');
console.log(returnValue.getTestValue());

Data Flow JavaScript → C++

The following example shows how to validate argument count and types, convert JavaScript values to V8 types, and invoke a JavaScript function from C++:

#include <node.h>
#include <v8.h>
#include <iostream>
using namespace v8;
using namespace std;
void GetArgument(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    if (args.Length() < 2) {
        isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments")));
        return;
    }
    if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
        isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "arguments must be number")));
    }
    // ... type checks for Object, Boolean, Array, String, Function, Null, Undefined omitted for brevity
    Local<Number> value1 = Local<Number>::Cast(args[0]);
    Local<Number> value2 = Local<Number>::Cast(args[1]);
    double sum = value1->NumberValue() + value2->NumberValue();
    Local<String> str = Local<String>::Cast(args[2]);
    String::Utf8Value utfStr(str);
    cout << string(*utfStr) << endl;
    Local<Array> input_array = Local<Array>::Cast(args[3]);
    printf("%d, %f %f
", input_array->Length(), input_array->Get(0)->NumberValue(), input_array->Get(1)->NumberValue());
    Local<Object> obj = Local<Object>::Cast(args[4]);
    Local<Value> a = obj->Get(String::NewFromUtf8(isolate, "a"));
    Local<Value> b = obj->Get(String::NewFromUtf8(isolate, "b"));
    Local<Array> c = Local<Array>::Cast(obj->Get(String::NewFromUtf8(isolate, "c")));
    cout << a->NumberValue() << "   " << b->NumberValue() << endl;
    printf("%d, %f %f
", c->Length(), c->Get(0)->NumberValue(), c->Get(1)->NumberValue());
    Local<String> cString = Local<String>::Cast(c->Get(2));
    String::Utf8Value utfC(cString);
    cout << string(*utfC) << endl;
    Local<Object> d = Local<Object>::Cast(obj->Get(String::NewFromUtf8(isolate, "d")));
    Local<String> dString1 = Local<String>::Cast(d->Get(String::NewFromUtf8(isolate, "m")));
    String::Utf8Value utfD1(dString1);
    cout << string(*utfD1) << endl;
    Local<String> dString2 = Local<String>::Cast(d->Get(String::NewFromUtf8(isolate, "n")));
    String::Utf8Value utfD2(dString2);
    cout << string(*utfD2) << endl;
    Local<Boolean> flagTrue = Local<Boolean>::Cast(args[5]);
    cout << "Flag: " << flagTrue->BooleanValue() << endl;
    Local<Function> cb = Local<Function>::Cast(args[8]);
    const unsigned argc = 2;
    Local<Value> argv[2] = { a, b };
    cb->Call(Null(isolate), argc, argv);
    args.GetReturnValue().Set(sum);
}
void Init(Local<Object> exports, Local<Object> module) {
    NODE_SET_METHOD(module, "exports", GetArgument);
}
NODE_MODULE(argumentss, Init)

After compiling, it can be invoked as:

const getArguments = require('./build/Release/arguments');
console.log(getArguments(2, 3, 'Hello Arguments', [1,2,3], {a:10,b:100,c:[23,22,"我是33"],d:{m:'我是22',n:'我是23'}}, true, null, undefined, function myFunction(...args){
    console.log('I am Function!');
    console.log(...args);
    console.log('I am Function!');
}));

NAN

Because V8’s API changes across Node.js versions, the NAN library provides a stable wrapper; after including its header you can use helpers such as:

v8::Local<v8::Primitive> Nan::Undefined();
v8::Local<v8::Primitive> Nan::Null();

Reference Materials

Type conversions from JavaScript to C++ in V8

node addon

v8 types documentation

node-gyp

gyp user documentation

nan

Node.jsV8Data ConversionNANnode-gypC++ Addons
Taobao Frontend Technology
Written by

Taobao Frontend Technology

The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.