Open-source project
kabiroberai/node-swift avatar
kabiroberai/node-swift

node-swift lets a Swift package be required from JavaScript

Create Node modules in Swift

583 stars27 forksSwiftMIT

At a glance

What is it?
node-swift is a bridge that lets you write a Node module in Swift and load it from JavaScript through Node-API, with Swift package manager and npm packages living side by side in one folder. It suits a macOS team that wants native code or Swift libraries inside an Electron app, and it is a wrapper rather than a general native module toolkit.
Who is it for?
Adopt node-swift when you are on Apple platforms and want to write part of a Node application in Swift, for native macOS APIs or to reuse an existing Swift library. Skip it if you need a portable module, since the alternative wrappers and WebAssembly cover more platforms.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 33 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One module, two package managers, in the same folder

The mechanism is described precisely. A NodeSwift module consists of a SwiftPM package and an NPM package in the same folder, both of which express NodeSwift as a dependency. The Swift package is exposed to JavaScript as a native Node.js module, which can be required by the JS code, and the two sides communicate via Node-API, which is wrapped by the NodeAPI module on the Swift side. That is the whole architecture, and it is a deliberate departure from the way native Node modules are normally built. The usual model is a C or C++ addon compiled with node-gyp against the Node headers, and here the Swift toolchain does the compiling while Node-API does the boundary. The consequence for a user is that the Swift package manager and npm become co-builders of the same artefact rather than the build system being one tool sitting on top of the other.

A Swift class becomes a JavaScript export through a macro

The example is short enough to read in full, and it shows both directions of the API. On the Swift side the module is declared with a macro that takes a list of exports:

swift
import NodeAPI

#NodeModule(exports: [
    "nums": [Double.pi.rounded(.down), Double.pi.rounded(.up)],
    "str": String(repeating: "NodeSwift! ", count: 3),
    "add": try NodeFunction { (a: Double, b: Double) in
        "\(a) + \(b) = \(a + b)"
    },
])

And on the JavaScript side it is an ordinary require:

js
const { nums, str, add } = require("./build/MyModule.node");
console.log(nums); // [ 3, 4 ]
console.log(str); // NodeSwift! NodeSwift! NodeSwift!
console.log(add(5, 10)); // 5.0 + 10.0 = 15.0

The exports are values, a string, and a callable created with NodeFunction from a closure that takes two Doubles. The JavaScript side destructures them exactly as it would from any module. The README's stated design goal is that the APIs feel right at home in idiomatic Swift code, and the example bears that out, because a Swift developer writes a closure and a value list rather than a C export table.

Memory safety is the argument against writing the addon in C

The feature list leads with safety. NodeSwift makes use of Swift's memory safety and automatic reference counting, which means that unlike with the C-based Node-API you never have to think about memory management while writing NodeSwift modules. That is the pitch to a JavaScript team, because the alternative is a C or C++ addon with manual allocation rules. The second claim is simplicity, with progressive disclosure so you can decide whether to use simpler or more advanced NodeSwift APIs to suit your needs. The third is idiomatic style, and the README expands on it: to make a Swift class usable from Node.js you literally declare a class in Swift that conforms to NodeClass, and the project uses Swift features like dynamic member lookup that are designed precisely to make this kind of interop easy. The fourth is versatility, with access to the full set of Node.js APIs in Swift, from JavaScript object manipulation to event loop scheduling. The fifth is cross-platform support across macOS, Linux, Windows and even iOS.

The alternative to WebAssembly is the operating system itself

The Alternatives section makes the comparison with WebAssembly, and the argument is about what the sandbox costs you. WebAssembly is great for performance, but it still runs in a virtual machine, which means it cannot access native Darwin, Win32 or GNU+Linux APIs. NodeSwift runs your Swift code on the bare metal, which the README says should be even faster than WASM, in addition to unlocking access to the operating system native APIs. The reverse case is stated too, that if you want to run Swift code in the browser, WebAssembly might be the right choice since NodeSwift requires a Node.js runtime. That is the honest framing of the choice. The other comparison is against other NAPI wrappers, NAPI, NAN and Neon, each with its own strengths, where NAPI is written in C and thus affords great portability at the cost of memory unsafety. The stated recommendation is that node-swift is a good option if you want to enhance your JS tool on Apple platforms, if you want to bring Node.js code into an existing Swift program, or if you simply prefer Swift to C, C++, Rust and similar languages.

The npm side is a build wrapper around the Swift package

The package.json shows what the JavaScript side of the project actually is. The npm package is named node-swift, the version in the manifest is 1.5.2, and the author is Kabir Oberai. The entry points tell the story: bin points at ./lib/cli.js and main points at ./lib/builder.js, so the npm package is a command line tool and a builder rather than a library you import. The build script is tsc, the prepare script runs that build, the test script is node test, and the lint script is eslint. The published files are lib, Sources, vendored and Package.swift, which is the shape of a package that ships a prebuilt Swift module alongside a JavaScript build tool. The only runtime dependency is import-cwd, used to resolve the working directory the CLI operates in, and the dev dependencies are the TypeScript and linting toolchain plus mocha for tests. The repository layout matches: Sources/ and Tests/ hold the Swift code, vendored/ holds the vendored dependencies, src/ holds the TypeScript for the npm side, and example/ is a complete working module with its own Package.swift, index.js and package.json.

The build has to produce a native artefact, so the toolchain is part of the deal

Because a NodeSwift module ends up as a compiled native module, producing it needs a Swift compiler and the platform's native build machinery, not just npm. The repository carries a .node-version file and a Package.resolved, and there is a Package.swift at the root alongside the .swiftpm/ directory that holds the Swift package configuration, so both toolchains are configured in the same checkout. The test plan files are named for the two sides, NodeAPIMacros.xctestplan and NodeJSC.xctestplan, which tells you the Swift side is tested through the standard XCTest mechanism while the JavaScript side has its own harness. The example project is the reference for how the pieces fit together, and the README points at it directly for details. A new contributor should start there, because the relationship between the SwiftPM package, the npm package, the NodeAPI wrapper and the generated .node file is easier to grasp from a working example than from a description.

Where node-swift is the wrong choice

Three situations argue against it. The first is a module that has to run everywhere. The README lists macOS, Linux, Windows and iOS as supported, but the argument it makes for itself over other NAPI wrappers is explicitly about Apple platforms, and the C-based NAPI route is the one with the best portability. A team shipping a native module to every platform should weigh that against the ergonomics of Swift. The second is a project that has already committed to a native module strategy in C, C++ or Rust, where the migration cost of rewriting the addon in Swift is not justified by the memory safety gain because the existing code is already audited. The third is a browser target, which the README answers directly, since node-swift requires a Node.js runtime and WebAssembly is the tool for running Swift in a browser. There is also an overhead that is inherent rather than a limitation of the implementation. Every call across the boundary is a call into native code, and the whole point of a Node module is to be a module you can require, so the interface has to be narrow enough that the boundary cost does not dominate. The realistic alternative for most teams is to keep the native layer small and in the language the platform toolchain already expects, and to use node-swift when the reason to be in Swift is a Swift library you want to reuse rather than a performance target you want to hit.

Editorial conclusion

Adopt node-swift when you are on Apple platforms and want to write part of a Node application in Swift, for native macOS APIs or to reuse an existing Swift library. Skip it if you need a portable module, since the alternative wrappers and WebAssembly cover more platforms. Verify first that your Node version supports the Node-API surface the wrapper uses, and that the Swift package you want to expose can be structured as a Node class or function, because the interop model in this project is declarative.

Frequently asked questions

What is node-swift?

It is a bridge that allows you to write Swift code that talks to Node.js libraries, and vice versa. A NodeSwift module consists of a SwiftPM package and an NPM package in the same folder, and the two sides communicate via Node-API, wrapped by the NodeAPI module on the Swift side.

How do I expose a Swift value or function to JavaScript?

You declare the exports with the NodeModule macro, passing a dictionary of names to values or NodeFunction closures, and the JavaScript side requires the built module and destructures them. The README notes that to make a Swift class usable from Node.js you declare a class in Swift that conforms to NodeClass.

Why choose node-swift over other NAPI wrappers?

The README argues that NAPI, NAN and Neon are each viable, and that NAPI is written in C which affords great portability at the cost of memory unsafety. NodeSwift is a good option if you want to enhance your JS tool on Apple platforms, if you want to bring Node.js code into an existing Swift program, or if you prefer Swift to C, C++ or Rust.

How does node-swift compare with WebAssembly?

The README says WebAssembly still runs in a virtual machine, so it cannot access native Darwin, Win32 or GNU+Linux APIs, while node-swift runs Swift on the bare metal. If you want to run Swift code in the browser, WebAssembly is the right choice, since node-swift requires a Node.js runtime.

Is node-swift still maintained?

The repository is not archived and the last push was on 2026-09-20. The recent tags are v1.4.0 from 2025-07-20, v1.3.0 from 2024-09-29 and v1.2.0 from 2024-06-23, the licence is MIT, and the npm package version in package.json is 1.5.2.

Official sources

  1. Issues
  2. kabiroberai/node-swift on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/kabiroberai-node-swift.svg)](https://hysenlabs.com/projects/kabiroberai-node-swift)

Was this useful?