C++ Native Addon independent of Node.js version using Napi/node-addon-api and Cmake

Atiq Gauri
5 min readAug 4, 2020

This is a tutorial for c++ Node-addon-api / Napi addon using cmake.
Napi makes it independent of node.js version, means our addon will be compatible with all future version of node.js .
Cmake is a cross-platform code compiling tool for c++ code this makes us independent of complex node-gyp process.

Requirements :-

  1. install Node.js > 10.x

Note:- Remember to check automatically install the necessary tools.
(this will be required to install vs-build tool on windows )

2. install Cmake > 3.15

Overview -

  1. Napi addon require four major part
  • package.json file- to handle dependencies, names, version, installation commands etc.
  • CMakeLists.txt file- commands needed to compile c++ code.
  • A node script - to use native addon.
  • C++ source files

2. Addon will be OS dependent, so if you want to make it cross-platform compatible. Then it will be needed to built(once) on all platforms separately.

Node.js app (package.json)

  1. Make a directory for your project and open terminal/power-shell in it.
  2. Since node.js is installed already we will create a project(package.json) using npm.
npm init

This will ask you package name, version, description, etc.
Enter accordingly or just press enter until it’s done with defaults.

Install npm Packages

  1. install node-addon-api / napi package for node.js addon
npm install node-addon-api --save

2. install cmake.js package for Cmake handling

npm install cmake-js --save-dev

C++ source code

We will be using a simple program which takes two argument and print their sum.
(If you want to run multi-threaded code with filesystem head over to end of article)

  • Now lets use Napi syntex to export this c++ function to node-js.
  1. Create a source directory for your c++ files named src
  2. Create a c++ header file in that directory named as src/example.h and add below code in it.
#include <napi.h>
#include <iostream>
using namespace std;namespace example{
//add number function
int add(int x, int y);
//add function wrapper
Napi::Number addWrapped(const Napi::CallbackInfo& info);
//Export API
Napi::Object Init(Napi::Env env, Napi::Object exports);
NODE_API_MODULE(addon, Init)
}
  • napi.h is a header file for node-addon-api .
  • Then we are declaring c++ function and Its wrapper which will call c++ function for us whenever wrapper will be called from node-js.
  • Last two line export functions name to nodejs and register addon in nodejs.

3. Create a c++ source file in src directory named as src/example.cpp and add below code in it.

#include "example.h"using namespace std;int example::add(int x, int y){
return (x+y);
}
Napi::Number example::addWrapped(const Napi::CallbackInfo& info){
Napi::Env env = info.Env();
//check if arguments are integer only.
if(info.Length()<2 || !info[0].IsNumber() || !info[1].IsNumber()){
Napi::TypeError::New(env, "arg1::Number, arg2::Number expected").ThrowAsJavaScriptException();
}
//convert javascripts datatype to c++
Napi::Number first = info[0].As<Napi::Number>();
Napi::Number second = info[1].As<Napi::Number>();
//run c++ function return value and return it in javascript
Napi::Number returnValue = Napi::Number::New(env, example::add(first.Int32Value(), second.Int32Value()));

return returnValue;
}
Napi::Object example::Init(Napi::Env env, Napi::Object exports)
{
//export Napi function
exports.Set("add", Napi::Function::New(env, example::addWrapped));
return exports;
}
  • Call example.h header file.
  • write definition of c++ function.
  • write definition of wrapper validate arguments and convert them to c++ datatype.
  • wrapper will call c++ function with argument and return result.
  • last function export wrapper function to node-js.

CMake configuration (CMakeLists.txt)

CMakeLists.txt: this file will contain all configuration and command required by cmake to compile c++ code.

  1. Create a file in project directory named as CMakeLists.txt and add below code in it.
cmake_minimum_required(VERSION 3.15)# Name of the project (will be the name of the plugin)
project (addon)
set(CMAKE_CXX_STANDARD 17)
# Don't add this line if you will try_compile with boost.
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Essential include files to build a node addon,
# you should add this line in every CMake.js based project.
include_directories(${CMAKE_JS_INC})
# Declare the location of the source files
file(GLOB SOURCE_FILES "src/*.cpp" "src/*.h")
# This line will tell CMake that we're building a shared library
# from the above source files
# named after the project's name
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC})
# This line will give our library file a .node extension without any "lib" prefix
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
# Essential library files to link to a node addon,
# you should add this line in every CMake.js based project.
target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB})
# Include N-API wrappers
execute_process(COMMAND node -p "require('node-addon-api').include"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE NODE_ADDON_API_DIR
)
string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR})
add_definitions(-DNAPI_VERSION=3)
  • require cmake minimum version.
  • set c++ version (in our case it is c++ 17).
  • then some boilerplate to include source files and libraries.
  • set node-js libraries specially napi.
  • add napi version

Configure package.json for cmake.js

  1. Add install command in Script field like below code
"scripts": {
"install": "cmake-js compile"
},
  • This will trigger Cmake to compile c++ code using CMakeLists.txt file configuration.
  • Optionally you can pass architecture ia32 or x64 like this:
"scripts": {
"preinstall": "npm config set cmake_js_arch ia32",
"install": "cmake-js compile"
},

Node script to call c++ functions

Lastly call c++ function in a node script with argument

  1. Create a index.js file in project directory and add below code in it.
const {add} = require("./build/Release/addon.node");console.log(add(2, 3));
  • Here we are importing addon as a node_module and then call our C++ function form it.

Install and Run

  1. execute both commands from project directory
npm install
node index
  • First command will compile all c++ files one by one and generates a .node file in build folder.
  • Second command will execute node script, which calls c++ function and print output on console.

2. Output should look like this:

atiq@edit02 napi_example % npm install
>cmake-js compile

[
'/usr/local/bin/node',
'/Users/atiq/Desktop/napi_example/node_modules/.bin/cmake-js',
'compile',
'--arch',
'x64'
]
...
...
...
...
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
...
...
...
Scanning dependencies of target addon
[ 50%] Building CXX object CMakeFiles/addon.dir/src/example.cpp.o
[100%] Linking CXX shared library Release/addon.node
[100%] Built target addon
npm WARN test@1.0.0 No description
npm WARN test@1.0.0 No repository field.

audited 139 packages in 2.236s
atiq@edit02 napi_example % node index
5

Code Repository

You can access complete code of this project here.

Support my work

Buy Me A Coffee

Additional Possibilities

--

--