"Node.js bindings for Rust libraries using napi-rs. Use when working on Node.js/JavaScript bindings to Rust code, including creating or modifying napi macros and structs, converting types between Rust and JavaScript, handling errors across the FFI boundary, async/Promise patterns, building native modules, publishing to npm, testing binding code, or debugging binding issues. Tuned for CQLite (Cassandra CQL bindings) with feature parity tracking."
Recursos
1Instal·la
npx skillscat add pmcfadin/cqlite/claude-skills-napi-rs-node-bindings Instal·la des del registre de SkillsCat.
napi-rs Node.js Bindings
Project Structure
my-rust-lib/
├── Cargo.toml # napi dependency
├── package.json # npm package config
├── src/
│ ├── lib.rs # Core Rust library
│ └── node/ # Node binding module
│ ├── mod.rs # Exports
│ ├── types.rs # #[napi] structs
│ └── errors.rs # Error conversions
├── index.js # JS entry point (generated)
├── index.d.ts # TypeScript definitions (generated)
└── __test__/
└── index.spec.ts # TestsCore Workflow
1. Expose Rust Types
use napi::bindgen_prelude::*;
use napi_derive::napi;
#[napi]
pub struct MyType {
inner: RustType, // Private Rust type
}
#[napi]
impl MyType {
#[napi(constructor)]
pub fn new(value: i64) -> Result<Self> {
Ok(Self { inner: RustType::new(value)? })
}
#[napi]
pub fn process(&self) -> Result<String> {
self.inner.process().map_err(|e| e.into())
}
}2. Export Functions
#[napi]
pub fn parse_cql(query: String) -> Result<Statement> {
cqlite::parse(&query).map_err(|e| e.into())
}
// Async function returns Promise
#[napi]
pub async fn parse_file(path: String) -> Result<Vec<Statement>> {
let content = tokio::fs::read_to_string(&path).await?;
cqlite::parse_all(&content).map_err(|e| e.into())
}3. Build & Test
npm run build # Build native module
npm run build:debug # Debug build (faster)
npm test # Run testsReference Guides
Load these as needed based on the task:
| Task | Reference |
|---|---|
| Type mapping between Rust ↔ JavaScript | type-conversions.md |
| Converting Rust errors to JS exceptions | error-handling.md |
| Async patterns and Promises | async-patterns.md |
| Building and publishing to npm | build-publish.md |
| Testing strategies | testing.md |
| Debugging common binding issues | debugging.md |
| CQLite CQL feature parity checklist | cqlite-parity.md |
Quick Reference
Cargo.toml Setup
[package]
name = "cqlite"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
napi = { version = "2", default-features = false, features = ["napi9", "async", "serde-json"] }
napi-derive = "2"
[build-dependencies]
napi-build = "2"
[profile.release]
lto = truepackage.json Setup
CQLite's real config is bindings/node/package.json — read it rather than copying a snippet.
Note it is on @napi-rs/cli v3, whose config key is napi.targets (a flat array), not the v2napi.triples object, and its test runner is jest, not ava:
{
"name": "@cqlite/node",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"napi": {
"binaryName": "cqlite-node",
"targets": [
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-pc-windows-msvc"
]
},
"scripts": {
"build": "napi build --platform --profile release-unwind --features write-support",
"build:debug": "napi build --platform --features write-support",
"test": "node --expose-gc ./node_modules/jest/bin/jest.js"
},
"devDependencies": {
"@napi-rs/cli": "^3.5.1",
"jest": "^29.7.0",
"typescript": "^5.0.0"
},
"engines": { "node": ">= 18" }
}The examples elsewhere in this skill are written against ava; CQLite's own suite uses jest, so
translate import test from 'ava' / t.is(...) to jest's test/expect when working inbindings/node/.
build.rs
extern crate napi_build;
fn main() {
napi_build::setup();
}napi-rs Attribute Quick Reference
| Attribute | Use |
|---|---|
#[napi] |
Expose function/struct/impl to JS |
#[napi(constructor)] |
Class constructor |
#[napi(getter)] |
Property getter |
#[napi(setter)] |
Property setter |
#[napi(factory)] |
Static factory method |
#[napi(js_name = "...")] |
Rename in JavaScript |
#[napi(ts_args_type = "...")] |
Custom TypeScript arg types |
#[napi(ts_return_type = "...")] |
Custom TypeScript return type |
#[napi(object)] |
Plain JS object (no class) |
#[napi(strict)] |
Strict type checking |