N-API: Next generation Node.js APIs for native modules

This blog post was written by Arunesh Chandra, Sr. Program Manager, Chakra at Microsoft and Michael Dawson, Runtime Technologies Node.js Technical Lead at IBM.

Node.js has a vibrant module ecosystem, which is key to its continued growth and popularity. The ecosystem includes both JavaScript and native addon modules. Existing native modules are written in C/C++ and directly depend on V8 and/or NAN APIs. The result of this dependency is a lack of API/ABI stability guarantees, requiring native addons to be updated or recompiled for every major Node.js release. By some estimates this affects approximately 30% of the module ecosystem via direct or indirect dependencies. This not only adds to the maintenance burden for native module maintainers, but it also presents a major barrier to upgrading Node.js versions in production for module consumers, who have mission critical native code dependencies in their deployments.

The next generation, ABI-stable Node.js API for native modules or N-API aims to solve this problem, by providing an ABI-stable abstraction layer for native APIs in JavaScript VMs. This will allow native module authors to compile their module once per platform and architecture and make it available for any version of Node.js that implements N-API. This holds true even for versions of Node.js that are built with a different VM e.g. Node-ChakraCore.

Today, we are excited to announce that N-API is available in Node.js 8.0 as an experimental feature, making this an important milestone for Node.js’ journey towards achieving full ABI stability. This is the first step in a long journey and we invite more community participation to take it further. Now is the perfect time for for native module maintainers to try out N-API and provide feedback on API gaps, performance, addon publishing workflow etc. Check out this demo video, which shows N-API in action!

Demo code available at: https://github.com/boingoing/napi_demo

What does the API look like?

The core API inside Node.js is available as a collection of C APIs. The following snippet shows an example of the API shape and error handling constructs. All of the ABI stable APIs follow the same pattern, returning a status code indicating success or the error that occurred, and optionally providing an out parameter to return a result.

NAPI_EXTERN napi_status napi_create_array(napi_env env, napi_value* result);

For a non-zero status code, additional details can be obtained using the following API function:

NAPI_EXTERN napi_status napi_get_last_error_info(napi_env e, const napi_extended_error_info** result);

In addition to the status code returned by the API functions, there are APIs to deal with handling JavaScript exceptions thrown from the VM:

NODE_EXTERN napi_status napi_is_exception_pending(napi_env e, bool* result);
NODE_EXTERN napi_status napi_get_and_clear_last_exception(napi_env e, napi_value* result);
NODE_EXTERN napi_status napi_throw(napi_env e, napi_value error);

For a full description of the N-API functions checkout the N-API documentation.

While the C-based API helps to maintain the ABI stability and makes it easy to understand the surface area provided by N-API, in some cases it is simpler to develop with C++ APIs. In order to support these cases, and to make it as easy as possible to transition from NAN, there is an optional C++ wrapper available as a npm module to provide syntactic sugar over the C APIs. While the wrapper is not considered a part of the N-API, it’s designed to be fully inlinable, and doesn’t have any additional link-time dependencies beyond N-API, so module authors can maintain ABI stability while using it. Here’s a comparison of C and C++ usage of N-API:

C API
#define CHECK_STATUS \
 if (status != napi_ok) { \
 napi_throw_error(env, “N-API call failed”); \
return; \
 }
napi_value Shutdown(napi_env env, napi_callback_info info) {
 napi_status status;
 int s;
 int how;
 napi_value ret;
 napi_value args[2];
 size_t argc = 2;
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
 CHECK_STATUS;
status = napi_get_value_int32(env, args[0], &s);
 CHECK_STATUS;
 status = napi_get_value_int32(env, args[1], &how)
 CHECK_STATUS;
status = napi_create_number(env, nn_shutdown(s, how), &ret); 
 CHECK_STATUS;
 status = napi_set_return_value(env, info, ret);
 CHECK_STATUS;
}
C++ Wrapper
Napi::Value Shutdown(const Napi::CallbackInfo& info) {
 int s = info[0]->As<Napi::Number>();
 int how = info[1]->As<Napi::Number>();
 return Napi::Number::New(info.Env(), nn_shutdown(s, how));
}

Current State: Experimental

The current state of N-API in Node.js v8.0 is experimental. However, as reflected in the chart below, N-API provides 100% coverage for V8 APIs used in 5 or more of the top 30 depended-on modules. While there remain some gaps in the coverage, we believe there is a good foundation available for people to try it out. So far, we have successfully ported 5 modules to use N-API, namely Node-Sass, Canvas, LevelDown, Nanomsg and IoTivity. These ports can serve as examples for developers looking start out on their own porting projects.

Support for older Node versions

The value of N-API shines when native modules need to be supported across Node.js versions. We plan to port N-API to older Node.js LTS lines after it stabilizes in Node.js 8.0. In the meantime, the node-addon-api module provides source compatibility with older versions of Node.js. You can check out the instructions on the repo for more details.

How to get started

To start out we recommend going through this N-API documentation, which has detailed descriptions and usage examples of N-API. It will also be helpful to look at these examples which are NAN addon examples ported to use N-API instead. In addition, there are some tools available to help you get started.

  • There is a migration utility that converts your existing NAN-based native addon to N-API. It does not provide a 100% conversion but it allows you to easily jump-start converting your existing projects.
  • If you want to start fresh with a native Node.js addon project with N-API, check out this yeoman generator for N-API modules. This will generate the necessary scaffolding for such a project.
  • After you are done creating or migrating your N-API module we recommend you follow these instructions for publishing it to the npm repository with a @n-api tag. Using the tag will allow you to publish an N-API version without influencing the sequencing of your non-N-API releases.

How to get involved

If you’ve been reading up to this point and have been thinking “this is so great, how can I help out?” We are happy to hear that. There are several ways that you can get involved which include:

  • Helping improve the documentation: Read through the documentation and provide feedback. We’ve tried hard to make it clear and easy to read, but everybody has a different perspective and adding yours will make the documentation clearer to a broader audience.
  • Trying out one of the ported modules: The abi-stable-node repo has the links to the forks containing the above-mentioned ports of the modules that we used to validate N-API. If you have an application that uses one or more of the modules for which we provide N-API-ported versions, try them out and let us know if you run into any problems.
  • Improving test coverage: Helping to fill in test coverage is a nice way to learn the details of some of the N-API functions and to contribute. Start by looking at the code coverage results for the main N-API implementation file (node_api.cc) at coverage.nodejs.org and look for functions that don’t have good coverage. In addition, you can also help by adding test coverage for the C++ wrapper. The N-API team will work to improve overall coverage but this is a great place to jump in and help as well.
  • Porting a module: Do you have a favorite module or do you maintain one or more native modules? Consider porting them to N-API and publishing an experimental version. We need your feedback. Having a more diverse set of modules ported and ready to go is the best way to make sure we have the right API surface. If you need help to make the port happen, let us know as we do want to work with maintainers to help things along.
  • Joining the N-API Working Group: Helping build N-API and/or the C++ wrapper. We’ve made great progress but code always benefits from more eyes and contributors. Join us at our weekly meeting to share your experience with N-API or to just hear more about what the team’s working on.

If you find issues in N-API during any of these activities please open an issue in the nodejs repo and prefix the title with `n-api:’.

This work is an example of great community collaboration. It would not have been possible without the engagement of individual Node.js Collaborators and CTC members including participants from Google, IBM, Intel, Microsoft, nearForm and NodeSource. We hope that the community will find this work valuable and get engaged to take the Node.js ecosystem to new heights and make it an even better platform in the future.