Tutorial to Native Node.js Modules with C++. Part 1 — An Introduction to Nan

Let’s say you want to build a module, which is required to solve computationally expensive or memory heavy tasks. Obviously JavaScript is not the best language for this purpose. Imagine another situation, where you wanted to use your favorite C++ library with JavaScript. In both cases Node.js provides you the option to write bindings to any C++ code or library, in order to require it in your JavaScript application.

There are various reasons why you would want to do that. Here are some reasons why I decided to write a npm package with JavaScript bindings to the OpenCV C++ library:

  • Using OpenCV in web applications
  • Building electron apps with OpenCV (I prefer to build nice GUIs with HTML and CSS or React instead of wrapping my head around QT)
  • Unit testing with Mocha and Chai
  • Coding in JavaScript is awesome — most obvious reason ;)

So, you want to get started with developing your own native bindings, but have no clue where to start? No problem! In the next few minutes I am going to teach you how to do so.

Yet another v8 and Nan tutorial?

After reading up on tutorials about developing native node addons, I still had a lot of unanswered questions. In this series of articles I want to focus on stuff, that I had to wrap my head around and had a hard time figuring out, when I was first confronted with v8 and Nan. Hopefully, this will help you to get started easier. As always, the source code of this example can be found in my github repository.

Setting up the project

For this example we are going to use Nan (Native Abstractions for Node.js), which is a collection of header files, providing you helpers and macros, which will make your life of developing node addons easier and allows you to keep compatibility across different node versions. Our package.json looks as follows:

We specify Nan as a dependency and add a node-gyp build as well as node-gyp rebuild script (for a clean rebuild) to it. Node-gyp is the module that allows us to bundle the compiled C++ code into a .node file, which can be required in your JS app. It should come with the latest versions of node.js, atleast on ubuntu. If not, you can simply npm i -g node-gyp. Windows users also have to have the msvc14 (Visual Studio 2015) build tools set up on their system. Luckily there is a npm package available for this purpose: npm i -g windows-build-tools.

Also note that the entry file of our package is bindings.js. In this file we will expose the path to our .node module, which makes requiring it easier:

In order for node-gyp to build our module, we have to give it a binding.gyp file where we specify the bundling instructions:

We will put all our header and source files in a src folder and also add the Nan headers installed in node_modules to the include path. Under sources you have to specify each .cc file that you want to compile into your module. In our simple example we will create a Vector class and expose it in the index.cc. Usually in the index.cc I include and initialize all the classes and cc modules, which all implement an “Init” method, exposing their interface to the module:

Now that we have everything set up to build and expose our C++ module, we can start to implement stuff!

Implementing our first class

Probably the most important thing, when you are just getting started, is to understand how to write classes on the native side and how to use them in your JavaScript app. Let’s say we want to implement a simple Vector class which can do the following:

For now we want to initialize new Vectors with their x, y and z coordinates, we want be able to change their coordinates by assigning new values to them and finally we also want to add a Vector to another one.

Declaring the class

To define the Vector class we will create a header file “Vector.h” with the following contents:

You can see that Vector extends the Nan::ObjectWrap class. This is required to pass instances of this class between your JavaScript app and the native module, as you pass them to the JS side by wrapping them and retrieve them on the native side by unwrapping them. We will discuss object wrapping later on in this tutorial.

Furthermore, our class will have three properties: x, y and z as well as getters and setters for their values, which you can declare with the NAN_GETTER and NAN_SETTER macros. We also declare the methods Init, New and Add. The New function, as the name implies, will be our constructor and the Init function is our “module.exports”, which is the first thing being called when we require our module from our app. It basically exposes our Vector class to our module and is declared with the NAN_MODULE_INIT macro. Methods accessible from JavaScript are declared as NAN_METHOD. Note, we have to declare everything statically, which can be invoked from JavaScript.

The last thing we have to define in our class header is a persistent handle to our constructor. You will mostly deal with local and persistent handles, which determine the lifetime of the handle. In contrast to a persistent handle, a local handle (you will see that one later on a lot) will be garbage collected after leaving the scope it is being declared in. But don’t worry about handles and their scopes for now, you don’t even have to declare HandleScopes in methods accessible from JavaScript since NAN_METHOD kindly does this for us already.

Implementing the class methods

Now that we have declared our class, we can go ahead and implement our methods, which we will add to our Vector.cc file.

Init:

This is the basic pattern I adopted from tutorials to initialize a new class. First we create the FunctionTemplate for our constructor, which we assign to the New method that we declared and assign this handle to our persistent constructor by calling Reset.

Furthermore, we set the class name, the accessors and prototype methods. SetClassName and Nan::SetAccessor expects the property name to be a v8::Local<v8::String>, a JavaScript String. The reason why we have to call ToLocalChecked() on every JS object that we create with Nan::New is, because Nan::New returns a MaybeLocal, which is a wrapper around v8::Local handles. JS objects can be empty, which MaybeLocals allow you to check for by calling IsEmpty() to catch exceptions early. In our case we create the handle ourselves so we can safely call ToLocalChecked() to return us the v8::Local handle. Lastly we expose our class to our module (target comes from the NAN_MODULE_INIT macro).

Constructor:

First thing to mention here is, the info object of the NAN_METHOD macro is basically an object containing information about the arguments passed to that function. Some properties of the info object worth mentioning are:

  • number of arguments of the function call: info.Length()
  • accessing arguments: info[0], info[1], … info[n]
  • set the return value of the function call: info.GetReturnValue().Set(…)
  • is constructor called with new?: info.IsConstructCall()
  • instance of the prototype method call: info.This()
  • the “this” you should use in constructor: info.Holder()

In the constructor we check some conditions such as ensuring that we create a new instance with 3 arguments, each of them being a Number and not a String, an Undefined or whatever. Note, that Nan::ThrowError will cause an error to be thrown, which can be catched on the JS side, but it will not cause the C++ function to exit, thus I return explicitely.

The more interesting stuff happens at line 18 and on, where we create the new instance. We create a new Vector and call Wrap on it with our JS instance from info.Holder(). Recall, that our Vector class extends Nan::ObjectWrap, which provides us with the Wrap method. Afterwards we initialize the field values of our vector and return it to the JS world.

Add:

In the implementation of the Add method you can see how unwrapping is done by calling Nan::ObjectWrap::Unwrap. Remember, we wanted to invoke the method like this: ‘vecSum = vec1.add(vec2)’. In line 3 we unwrap the instance that we called the method on (vec1), which we retrieve from info.This(). Vec2 is passed as argument 0, thus we unwrap it from info[0] in line 9. Note, that you can use a constructor to check, whether a v8::Value actually is an instance of the class before unwrapping it.

In order to create a new instance on the native side, we have to call the constructor with the argument count argc and an array of arguments. Therefore we will call Nan::NewInstance with the constructor function, argc and the sum of both Vector’s x, y, z coordinates as an array of v8::Values (line 22). This will give us a wrapped JS instance of Vector. Afterwards we can return the new JS instance and we are done.

Accessors:

The last part missing is implementing the accessors, which is more or less straight forward. In a NAN_GETTER and NAN_SETTER you can retrieve the target property name from the property object passed as a v8::String, which we convert to a std::string. In our getter handler we simply check which property is targeted and return it’s value.

The setter handler looks pretty much the same. The only difference is, that the value is passed as a v8::Value, which we can check for it’s type before setting the value of the instance.

You can also implement a getter and setter for each property seperately if you would like to. In this case you do not have to check for the property name as it is determined implicitly.

Build and run it!

And we are already done with the implementation. Time to run it:

npm install &&npm start

Running our little js example should give us the expected output:

> node ./index.js
vec1 Vector { z: 0, y: 10, x: 20 }
vec2 Vector { z: 100, y: 0, x: 30 }
vecSum Vector { z: 100, y: 10, x: 50 }

Want to know how to deal with arrays, JSON objects and callbacks? Continue with Part 2.

Want to build an asynchronous node module? Check out Part 3.