Node.js N-API Addons — Complete Guide to Native C/C++ Extensions
In this tutorial, you will learn about Node.js N. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js N-API addons allow writing native C or C++ modules that integrate directly with the Node.js runtime, providing performance-critical operations with stable ABI across Node versions.
What You'll Learn
By the end of this tutorial, you'll create N-API addons using the C API and node-addon-api C++ wrapper, compile with node-gyp, handle JavaScript values, and expose native functions.
Why N-API Matters
N-API is the only stable ABI for Node.js native addons. Code compiled against N-API works across Node versions without recompilation, unlike the older Nan API.
Real-World Use
A video processing library uses an N-API addon to perform pixel-level operations in C++ at 60fps, calling back into JavaScript for higher-level logic like scene detection and metadata extraction.
N-API Path
flowchart LR
A[Architecture] --> B[N-API Addons]
B --> C[Worker Threads]
C --> D[Performance]
D --> E[Profiling]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Project Setup
N-API addons require node-gyp for compilation. Create a binding.gyp file and a C++ source file.
// binding.gyp
{
"targets": [{
"target_name": "addon",
"sources": ["addon.cc"],
"include_dirs": ["<!(node -e \"require('node-addon-api').include\")"],
"dependencies": ["<!(node -e \"require('node-addon-api').gyp\")"],
"defines": ["NAPI_VERSION=9"]
}]
}
// Build: node-gyp configure && node-gyp build
Basic N-API Module
Use node-addon-api (C++) for a cleaner API. Expose functions that return JavaScript values.
#include <napi.h>
Napi::String Hello(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::String::New(env, "Hello from C++ addon!");
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("hello", Napi::Function::New(env, Hello));
return exports;
}
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
Using the Addon from JavaScript
Load the compiled addon with require and call native functions.
const addon = require("./build/Release/addon");
console.log(addon.hello());
// Hello from C++ addon!
Passing JavaScript Values
Handle numbers, strings, booleans, arrays, and objects in C++.
Napi::Value ProcessArray(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (!info[0].IsArray()) {
Napi::TypeError::New(env, "Expected array").ThrowAsJavaScriptException();
return env.Undefined();
}
Napi::Array arr = info[0].As<Napi::Array>();
uint32_t len = arr.Length();
Napi::Array result = Napi::Array::New(env, len);
for (uint32_t i = 0; i < len; i++) {
Napi::Value val = arr.Get(i);
if (val.IsNumber()) {
result.Set(i, Napi::Number::New(env, val.As<Napi::Number>().DoubleValue() * 2));
}
}
return result;
}
Synchronous vs Asynchronous Operations
Perform expensive operations on the libuv thread pool to avoid blocking the event loop.
class AsyncWorker : public Napi::AsyncWorker {
public:
AsyncWorker(Napi::Function& callback)
: Napi::AsyncWorker(callback) {}
void Execute() override {
// Heavy computation here (runs on thread pool)
result = performExpensiveOperation();
}
void OnOK() override {
Callback().Call({Env().Undefined(), Napi::Number::New(Env(), result)});
}
private:
int result;
};
Common Mistakes
1. Blocking the Event Loop in Native Code
Synchronous C++ operations block the main thread. Use AsyncWorker to run on the libuv thread pool.
2. Memory Leaks in Native Modules
N-API handles are automatically managed. Raw C++ allocations must be freed manually. Use RAII patterns.
3. Incorrect Type Checking
Always check JavaScript value types with IsNumber(), IsString(), etc. before conversion. Incorrect types crash the Process.
4. Ignoring Error Handling
Call ThrowAsJavaScriptException to propagate errors. Native crashes become uncaught exceptions.
5. Forgetting to Rebuild After Node Upgrade
N-API is ABI-stable, but recompile when upgrading the node-addon-api package or major Node versions.
Practice Questions
1. What is the main advantage of N-API over Nan?
N-API provides a stable ABI. Addons compiled against N-API work across Node versions without recompilation.
2. How do you compile an N-API addon?
Use node-gyp: node-gyp configure && node-gyp build. A binding.gyp file specifies sources and dependencies.
3. How do you run native code without blocking the event loop?
Use Napi::AsyncWorker to execute code on the libuv thread pool, then call back into JavaScript.
4. What is the role of binding.gyp?
It is a JSON-like configuration file that tells node-gyp how to compile the native module.
5. Challenge: Create an N-API addon that calculates Fibonacci numbers.
// C++ function calculates fib(n) synchronously
// JavaScript call: const result = addon.fibonacci(40);
FAQ
Mini Project: String Processing Addon
Build a native addon that counts word frequency efficiently.
#include <napi.h>
#include <unordered_map>
#include <sstream>
Napi::Object WordCount(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::string text = info[0].As<Napi::String>().Utf8Value();
std::unordered_map<std::string, int> counts;
std::istringstream stream(text);
std::string word;
while (stream >> word) counts[word]++;
Napi::Object result = Napi::Object::New(env);
for (auto& [word, count] : counts) {
result.Set(Napi::String::New(env, word), Napi::Number::New(env, count));
}
return result;
}
What's Next
Node.js Worker Threads Node.js Performance Node.js Profiling
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro