Extending Node.js with native C++ modules

Node.js can not only load the JavaScript libraries, but also be extended with native modules (compiled C/C++ code). While this does not mean that you should wipe out your existing JavaScript modules in favor of good ol’ C++, this knowledge might come in handy in specific use cases.

I’m a JavaScript developer, why would I ever want to mess with C++?

First: direct access to existing (legacy) C/C++ libraries. Instead of calling these as external applications in “execute command” style, get your hands directly on the existing source code and pass the results back to Node.js in a form that is comprehensible for your JavaScript runtime. This way you can also get an access to low-level API of the operating system.

Second: performance. In many situations, a well-written native code might prove faster and more performant than the JavaScript equivalent.

Disclaimer: Some of you might be surprised that I put performance as second, but I did it on purpose — a generally better performance of the native code should not be the reason to re-write every piece of JavaScript code into C/C++ equivalent. There are many cases, where the regular JavaScript code will still be more performant than the “wheel reinvented” C/C++ code written by unexperienced developer.

Beware — going low level might end up as if you wanted to pimp your ride in a custom car shop. Of course you might get this nitro boost, but things can get overdone quickly:

This guy got too enthusiastic about C++ modules in Node.js apps.

Hello, (native) world!

Enough warnings, now you’re ready for your first lines of C++ code that will be compiled into native node module.

For the purpose of this article, I will be using Native Abstractions for Node.js (Nan), which is basically a C++ header file providing a namespace and set of useful macros for easier interaction with V8’s (JavaScript engine used in Node.js) APIs. It also makes your code future-proof (for next versions of Node.js), as V8’s API tends to change dramatically between versions. Nan is a recommended solution in Node.js API docs.

Create the .cpp file (I named it main.cpp) and fill it with following piece of code:

Hello, (native) world!

Note that the methods created in C++ are void, they don’t return any value explicitly. Instead you set the return value on the “bridge” info object (reference of type Nan::FunctionCallbackInfo, “implicitly” passed in a NAN_METHOD macro).

In order to run the code, you still need to compile it. Fear not, you don’t have to call the C++ compiler manually. Node.js has got you covered.

For a good start, initialize Node.js project in the same directory where you have your main.cpp file:

npm init -y

Next, install Nan and node-gyp (you know Nan already; node-gyp is a set of tools for native code compilation):

npm install nan node-gyp --save

Update your package.json file so that it contains following lines (versions of your dependencies might slightly differ, but it’s totally fine!):

The important part here is the "scripts" section:

  • "compile": “node-gyp rebuild” — for C++ code compilation
  • "start": "node main.js" — for our main executable script

Now, create a bindings.gyp file — it’s a sort of configuration file for node-gyp. Note how it uses "main.cpp” (your “hello world” source) filename in the array under "sources":

You will also need these three tools installed on your machine (you should be fine on macOS — they come preinstalled; they are also easy too install on Linux, eg Ubuntu with apt-get):

  • make
  • g++
  • python 2.7

If any of these is missing, node-gyp will report it, so don’t waste your time figuring out whether you have them on your OS or not.

It’s time for your first compilation — npm run compile (remember, it will start node-gyp and compile your C++ sources as configured in bindings.gyp file). If everything went smoothly, you should see similar output:

> node-native-addons-example@1.0.0 compile /Users/marcin/projects/node-native-addons-example
> node-gyp rebuild
CXX(target) Release/obj.target/addon/main.o
SOLINK_MODULE(target) Release/addon.node

Nice, you’ve got your first native module compiled and ready to use. Let’s create a JavaScript file that you will run with Node.js:

Woohoo! If everything went fine, you should see whatever message you returned from your C++ addon function in the console.

That’s sweet, but how about something more useful?

Of course. You would like to see where the native modules really shine.

For this purpose let’s implement a simple function that will check whether given number is a prime number and return boolean (true or false) value. We will use the same algorithm for both C++ and JavaScript version of the function and finally compare the execution times against same, relatively large number.

The algorithm will:

  • first, check if the only argument is a number (if not — throw a TypeError);
  • second, check if the number is less than 2 (if so, return true);
  • third, iterate in range of 2 up to a number (exclusively) and check whether the modulo operation yields zero (if so, break the loop & return false because such number is not prime);
  • return true at the very end — the number “made it” through the loop, so it must be prime.

(for simplicity, I deliberately omitted the checks against non-integers or negative numbers; I’ll leave it as an exercise for you!)

Create an isPrime.js file:

Now, for the main.cpp file, replace it’s contents with following code (note that I removed the Hello function here):

Open your main.js file and swap the existing content with following code:

Run npm run compile to compile your C++ module and npm start to execute the main.js file. We are executing both C++ and JavaScript functions to determine if 654188429 (a pretty large number) is prime, or not.

It takes an average of 1800ms for C++ function and 3100ms for JavaScript on my 2015 Macbook Pro (macOS Sierra 10.12, Intel i5, 8GB RAM), resulting in C++ version of the algorithm being 1.7x faster than it’s JavaScript equivalent.

The repo with complete example is available on my Github.

Conclusion

In this article I wanted to show you that Node.js platform is open for native, low-level extensions. However, the process of writing these requires some preparation and extra tooling. While the native code can indeed be more performant, you should still prefer JavaScript solutions unless there is an urgent need to either do heavy optimisations or have an access to low-level APIs.

What would be your use case for developing Node.js modules with C++?

Reference

Like what you read? Give Marcin Baraniecki a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.