Node.js native addons let you write performance-critical code in C++ and use it from JavaScript.

Let’s see a basic N-API addon in action. Imagine you have a computationally intensive task, like complex mathematical calculations or image processing, that JavaScript struggles to handle efficiently. You can offload this to a C++ addon.

Here’s a simple C++ function that adds two numbers:

// myaddon.cpp
#include <node_api.h>

napi_value Add(napi_env env, napi_callback_info info) {
  napi_status status;
  size_t argc = 2;
  napi_value args[2];
  status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
  if (status != napi_ok) {
    napi_throw_error(env, nullptr, "Failed to get callback info");
    return nullptr;
  }

  int64_t num1, num2;
  status = napi_get_value_int64(env, args[0], &num1);
  if (status != napi_ok) {
    napi_throw_type_error(env, nullptr, "Argument 1 must be an integer");
    return nullptr;
  }
  status = napi_get_value_int64(env, args[1], &num2);
  if (status != napi_ok) {
    napi_throw_type_error(env, nullptr, "Argument 2 must be an integer");
    return nullptr;
  }

  int64_t sum = num1 + num2;
  napi_value result;
  status = napi_create_int64(env, sum, &result);
  if (status != napi_ok) {
    napi_throw_error(env, nullptr, "Failed to create result value");
    return nullptr;
  }
  return result;
}

napi_value Init(napi_env env, napi_value exports) {
  napi_status status;
  napi_property_descriptor desc = {
    "add",
    nullptr,
    Add,
    nullptr,
    nullptr,
    nullptr,
    napi_default,
    nullptr
  };

  status = napi_define_properties(env, exports, 1, &desc);
  if (status != napi_ok) {
    napi_throw_error(env, nullptr, "Failed to define properties");
  }
  return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

To build this, you’ll need a binding.gyp file:

# binding.gyp
{
  "targets": [
    {
      "target_name": "myaddon",
      "sources": [ "myaddon.cpp" ],
      "include_dirs": [ "<!(node -p \"require('node-addon-api').include\")" ],
      "dependencies": [ "<!(node -p \"require('node-addon-api').gyp\")" ],
      "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
    }
  ]
}

And then compile it using node-gyp:

npm install --save-dev node-gyp node-addon-api
node-gyp configure
node-gyp build

Once built, you can use it in your Node.js application:

// index.js
const myaddon = require('./build/Release/myaddon');

const result = myaddon.add(5, 10);
console.log(`The sum is: ${result}`); // Output: The sum is: 15

This demonstrates the core idea: defining C++ functions that Node.js can call. The napi_env is the environment for the Node.js instance, napi_callback_info contains information about the function call (arguments, this value), and napi_value is the generic type for JavaScript values. napi_get_cb_info retrieves arguments, napi_get_value_int64 converts JavaScript numbers to C++ integers, and napi_create_int64 converts the C++ result back to a JavaScript number. NAPI_MODULE is the entry point that registers your addon with Node.js.

The problem N-API solves is the need for a stable, cross-version ABI for Node.js native modules. Before N-API, addons were tied to specific Node.js versions, requiring recompilation for every Node.js update. N-API provides a C API that is consistent across all Node.js versions, meaning an N-API addon compiled for Node.js 10 will work with Node.js 12, 14, 16, and so on, without modification. This is achieved by abstracting away the internal V8 or JavaScript engine APIs, which are prone to change.

The NAPI_DISABLE_CPP_EXCEPTIONS define is crucial. It tells the compiler to use N-API error handling mechanisms (napi_throw_error, napi_throw_type_error) instead of C++ exceptions, which are not directly compatible with Node.js’s error propagation model. This ensures that errors thrown in your C++ code are correctly translated into JavaScript exceptions.

The node-gyp tool is essential for building native addons. It’s a build tool that uses gyp (Generate Your Projects) to create native build tool configuration files (like Makefile for Unix or vcxproj for Windows) from a binding.gyp file. This allows your C++ code to be compiled and linked against Node.js’s internal libraries, producing a shared object (.node file) that Node.js can load.

A common pitfall is forgetting to handle potential errors during N-API calls. Every N-API function returns a napi_status code. It’s vital to check this status after each call and throw a JavaScript error if it’s not napi_ok. Forgetting this can lead to silent failures or crashes.

When passing data between JavaScript and C++, you must be mindful of type conversions. napi_get_value_int64 is for integers, napi_get_value_double for floating-point numbers, and napi_get_value_string_utf8 for strings. Correspondingly, napi_create_int64, napi_create_double, and napi_create_string_utf8 are used to create JavaScript values from C++ data. Mismatched types will result in napi_invalid_arg errors.

A more advanced concept is managing memory. While N-API handles garbage collection for JavaScript values passed to C++, if your C++ code allocates memory (e.g., using malloc or new), you are responsible for freeing it. For complex data structures, you might use napi_wrap to associate a JavaScript object with your C++ instance, allowing you to manage its lifecycle and cleanup when the JavaScript object is garbage collected.

The next step in understanding native addons is often dealing with asynchronous operations, like I/O or network requests, which require using Node.js’s libuv event loop and N-API’s asynchronous functions like napi_create_async_work.

Want structured learning?

Take the full Nodejs course →