How to call C/C++ code from Node.js

Node.js ❤ C/C++

Q: Why would I need to call C/C++ code? There is half a million NPM modules, why would I need native stuff at all?

A: It you don’t need any C/C++ interop right now, it doesn’t mean you won’t need it in the future. In large apps there is usually a mix of different programming languages and technologies is used, you don’t want to limit yourself to working with a single stack, but instead, choose the right tool for the job.

Consider, for example, a password hashing function that is used during sign in process. It’s takes at least 200ms to calculate a hash string, by design! You don’t want to make it run on the same thread where your Node.js app is running, at least, not in a production environment.

Node.js Addons are dynamically-linked shared objects, written in C++, that can be loaded into Node.js using the require() function, and used just as if they were an ordinary Node.js module. They are used primarily to provide an interface between JavaScript running in Node.js and C/C++ libraries.

Hello World

module.exports.hello = () => 'world';
#include <napi.h>using namespace Napi;String Hello(const CallbackInfo& info) {
return String::New(info.Env(), "world");
}
void Init(Env env, Object exports, Object module) {
exports.Set("hello", Function::New(env, Hello));
}
NODE_API_MODULE(addon, Init)
{
"targets": [
{
"target_name": "native",
"sources": [
"binding.cpp"
],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
],
"dependencies": [
"<!(node -p \"require('node-addon-api').gyp\")"
],
"cflags!": ["-fno-exceptions"],
"cflags_cc!": ["-fno-exceptions"],
"defines": ["NAPI_CPP_EXCEPTIONS"]
}
]
}
{
"name": "app",
"version": "0.0.0",
"private": true,
"gypfile": true,
"dependencies": {
"node-addon-api": "^0.6.3"
}
}
$ node --napi-modules -e \
"console.log(require('./build/Release/native.node').hello())"
world

There is one more useful library called bindings that allows importing Node.js addons without providing the full path-name to the .node file. Install it by running yarn add bindings, then you’ll be able to reference our native module as follows: const native = require('bindings')('native');

Using a native C/C++ library from Node.js

apk add --no-cache make g++ python2 libsodium-dev
{
"targets": [
{
...
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")",
"/usr/include/sodium"
],
...
"libraries": ["/usr/lib/libsodium.so.18"],
...
}
]
}
#include <napi.h>
#include <sodium.h>
using namespace Napi;String Hash(const CallbackInfo& info) {
Env env = info.Env();
std::string password = info[0].As<String>().Utf8Value();
char hash[crypto_pwhash_STRBYTES];
crypto_pwhash_str(
hash,
password.c_str(),
password.size(),
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE);
return String::New(env, hash.c_str(), crypto_pwhash_STRBYTES);
}
void Init(Env env, Object exports, Object module) {
exports.Set("hash", Function::New(env, Hash));
}
NODE_API_MODULE(addon, Init)
$ node --napi-modules -e \
"console.log(require('bindings')('native').hash('Passw0rd'))"
$argon2i$v=19$m=32768,t=4,p=1$/N3vumg47o4EfbdB5FZ5xQ$utzaQCjEKmBTW1g1+50KUOgsRdUmRhNI1TfuxA8X9qU

Summary

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Konstantin Tarkus

Konstantin Tarkus

Bringing the technical edge to early-stage companies with advice on software architecture, best practices, database design, web infrastructure, and DevOps.