This post is intended to be a little intro guide on how to call C++ code from Bun using FFI.
Repo is at the bottom!
I found some more complicated examples online, but I wanted to make a minimal example that just works out of the box.
Bun's Foreign Function Interface (FFI) lets you call native C/C++ code directly from JavaScript with near-zero overhead. This is perfect when you need performance-critical operations or want to leverage existing C++ libraries.
In this little post, we'll build a minimal example that calls C++ functions from Bun.
Why Use FFI?
- Performance: C++ sometimes runs faster than JavaScript for compute-intensive tasks
- Existing Libraries: Tap into unique or required C/C++ libraries
- System-Level Access: Work with low-level APIs not directly available in JavaScript
Project Structure
├── cpp/
│ └── cpp_math.cpp # C++ source code
├── bun/
│ └── index.js # Bun FFI wrapper
├── dist/
│ └── cpp_math.dylib # Compiled shared library
├── build_cpp.sh # Build script for the C++ code
└── build_bun.sh # Build script for the Bun code
Step 1: Write Your C++ Code
First, create a C++ file with functions you want to export.
cpp_math.cpp
#include <stdint.h>
// Export function with C linkage to avoid name mangling
extern "C" {
int32_t add(int32_t a, int32_t b) {
return a + b;
}
int32_t multiply(int32_t a, int32_t b) {
return a * b;
}
}
Important: Use extern "C"
so Bun can find your functions by their simple names (add
, multiply
) instead of mangled C++ names.
Step 2: Compile to a Shared Library
Create a build script that compiles your C++ code into a shared library (.dylib
on macOS, .so
on Linux, .dll
on Windows):
build_cpp.sh
#!/bin/bash
clang++ -shared -fPIC -O3 -o dist/cpp_math.dylib cpp/cpp_math.cpp
Run it:
chmod +x build_cpp.sh
./build_cpp.sh
The flags:
-
-shared
: Create a shared library -
-fPIC
: Generate position-independent code (required for shared libraries) -
-O3
: Optimize for performance? (Co-pilot told me to add this! :D)
Step 3: Call from Bun using FFI
Now use Bun's dlopen
to load and call your C++ functions:
import { dlopen, FFIType, suffix } from "bun:ffi";
// Import the dylib as an embedded asset (for compiled binaries)
import cpp_math from "../dist/cpp_math.dylib" with { type: "file" };
// Find the library (works in dev and when compiled)
const libName = `cpp_math.${suffix}`;
const libPath = cpp_math || [
`${import.meta.dir}/${libName}`,
`${import.meta.dir}/../dist/${libName}`,
].find(p => Bun.file(p).exists());
// Load the library and define function signatures
const cpp_math_lib = dlopen(libPath, {
add: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
multiply: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
}
});
// Call the C++ functions!
console.log("C++ Add:", cpp_math_lib.symbols.add(10, 20)); // 30
console.log("C++ Multiply:", cpp_math_lib.symbols.multiply(10, 20)); // 200
How It Works
-
dlopen(path, symbols)
: Opens the shared library - Function signatures: Tell Bun the argument and return types
-
FFIType
: Maps JavaScript types to C types (i32, f64, ptr, etc.) -
suffix
: Platform-specific extension (dylib/so/dll) -
Import with
type: "file"
: Embeds the library for standalone compilation
Bonus: Compile to a Single Binary
You can bundle everything into a single executable:
build_bun.sh
bun build ./bun/index.js --compile --outfile dist/bun_binary
The import ... with { type: "file" }
statement embeds the .dylib
into the compiled binary, making it truly self-contained!
Available FFI Types
Common types you'll use:
-
FFIType.i32
/FFIType.i64
- Integers (32/64-bit) -
FFIType.u32
/FFIType.u64
- Unsigned integers -
FFIType.f32
/FFIType.f64
- Floats (32/64-bit) -
FFIType.ptr
- Pointers -
FFIType.void
- No return value -
FFIType.cstring
- C string (null-terminated)
When to Use FFI
✅ Good for:
- CPU-intensive calculations (image processing, cryptography, physics)
- Wrapping existing C/C++ libraries
- Hardware/system-level operations
Conclusion
Bun's FFI makes it trivially easy to call C++ code from JavaScript. With just three files (C++ source, build script, and JS wrapper), you can achieve native performance while keeping the convenience of JavaScript.
The ability to compile everything into a single binary makes distribution simple—no need to worry about shipping separate .dylib
files!
Full code: Check out this repo for the complete working example.
https://github.com/calumk/example-repo-bun-ffi-cpp
calumk
/
example-repo-bun-ffi-cpp
Bun's Foreign Function Interface (FFI) lets you call native C/C++ code directly from JavaScript with near-zero overhead. This is perfect when you need performance-critical operations or want to leverage existing C++ libraries.
Bun FFI Experiment
A minimal example of calling C++ functions from Bun using FFI.
Structure
-
cpp/cpp_math.cpp
- C++ source with a simpleadd()
function -
build_cpp.sh
- Compiles C++ to a shared library -
bun/index.js
- Bun script that calls the C++ function via FFI -
build_bun.sh
- Compiles the Bun script into a standalone executable, including the C++ shared library
Usage
- Build the C++ library:
./build_cpp.sh
- Run the Bun script:
bun run bun/index.js
- Build the Bun executable:
./build_bun.sh
Run the Bun executable:
./dist/bun_binary
Expected output:
C++ Add: 10 + 20 = 30
C++ Multiply: 10 * 20 = 200
JS Add: 10 + 20 = 30
JS Multiply: 10 * 20 = 200
Top comments (0)