TLDR: there’s a sample repo here if you’re lazy to read this post. The sample repo include GitHub Actions sample for CI as well.

The Rust bit

It’s very simple. You write your function in Rust

#[js_function(3)]
fn say_hello(ctx: CallContext) -> Result<JsString> {
  let name = ctx.get::<JsString>(0)?.into_utf8()?;
  let name = name.as_str()?;
  let s = ctx.env.create_string_from_std(format!("Hello, {}!", name))?;
  Ok(s)
}

And then you expose it to Node.js runtime with

#[module_exports]
fn init(mut exports: JsObject) -> Result<()> {
  exports.create_named_method("say_hello", say_hello)?;

  Ok(())
}

Build

You then build it with napi by using this command napi build --platform

And if you want to build for multi-platform, you will have multiple binaries, so you need to load accordingly in Node.js. We use detect-libc library for this.

let parts = [process.platform, process.arch];
if (process.platform === 'linux') {
  const {MUSL, family} = require('detect-libc');
  if (family === MUSL) {
    parts.push('musl');
  } else if (process.arch === 'arm') {
    parts.push('gnueabihf');
  } else {
    parts.push('gnu');
  }
} else if (process.platform === 'win32') {
  parts.push('msvc');
}

module.exports = require(`./node-module-rust-example.${parts.join('-')}.node`);

And that’s it. You can replace test.js with your favorite test runner. The test code there is just dummy code.