Introduction
napi-zig is a toolchain for writing Node.js native addons in Zig. It provides three things:
- A Zig library that turns ordinary
pub fndeclarations into N-API functions, with automatic conversion of arguments, return values, errors, and complex types like structs and enums. - A
build.zighelper that compiles your addon for the current platform during development, and cross-compiles every platform you target during release. - A
napiCLI that scaffolds new projects, bumps versions, and publishes per-platform npm packages with trusted publishing.
The result is an addon you write as a normal Zig module, ship as a single npm install, and distribute as prebuilt binaries for every platform your users run on.
Why Zig?
Zig is a small systems language with no hidden control flow and a build system that already knows how to cross-compile. With N-API, that gives you:
- No GC, no runtime, no surprise allocations.
- Cross-compilation for Linux (glibc and musl), macOS, Windows, and FreeBSD from a single machine.
- Comptime for type conversion, dispatch, and
.d.tsgeneration. - Direct C interop with the N-API headers when you need to drop down.
Example
const std = @import("std");
const napi = @import("napi-zig");
comptime { napi.module(@This()); }
pub fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn greet(env: napi.Env, name: []const u8) ![]const u8 {
return std.fmt.allocPrint(env.allocator(), "Hello, {s}!", .{name});
}import addon from "./my-addon.js";
addon.add(2, 3); // 5
addon.greet("world"); // "Hello, world!"The line comptime { napi.module(@This()); } registers every public declaration in the file. Functions become JS functions, constants become JS properties, nested structs become nested namespaces.
Compared to napi-rs
napi-rs does the same thing for Rust. napi-zig follows the same shape (one source, prebuilt platform binaries, an npm meta-package, OIDC publish) with these differences:
- The Zig build system is already cross-compile aware, so there is no
cargo zigbuildequivalent to install. - Comptime replaces procedural macros. There is no
#[napi]attribute; a function is exported because it ispub. - Errors are values, not panics. Returning
error.Xrejects a promise or throws an exception automatically.
Next steps
- Quick start scaffolds a working addon in one command.
- Manual setup wires it up step by step.
- Functions covers the core concept that explains most of the library.