Runtime APIs
Use Node.js packages with clear boundaries
Run Node.js APIs and npm packages in Bun while identifying the compatibility assumptions your application depends on.
Bun implements many Node.js built-in modules, like path, fs, os, and crypto. You import them with the node: prefix:
import { join } from 'node:path'
const file = join('data', 'notes.json')
console.log(file)
This prints data/notes.json, and the same code runs unchanged in Node.js. The prefix also tells whoever reads the file that path comes from the runtime, not from node_modules. I always use it.
Most npm packages install normally:
bun add zod
Then import them with standard ES module syntax:
import { z } from 'zod'
const Note = z.object({
title: z.string().min(1),
})
console.log(Note.parse({ title: 'Learn Bun' }))
This prints { title: "Learn Bun" }. Zod is plain JavaScript with no runtime-specific code, so there’s nothing to worry about. Packages like this are the majority, and they just work.
Compatibility needs evidence
A successful bun add only proves the package was downloaded. A successful import only proves the file parsed. Neither proves the package works. Run the paths your application uses.
Pay extra attention when a dependency uses:
- a native Node.js add-on
- a recently added Node.js API
- Node.js internals rather than public APIs
- process, stream, or module behavior at the edges
Here’s what a real problem looks like. You install a package, the import works, and one method throws TypeError: undefined is not a function deep inside node_modules. The package called a Node API that Bun hasn’t implemented, or implemented differently.
When that happens, check Bun’s Node.js compatibility page for the module involved. Then write a tiny reproduction, five lines in a repro.ts file that calls only the failing method. If the reproduction fails too, you know it’s the runtime, and you can report it or pick another package. If it passes, the bug is in your code. Either way you learned something before touching the application.
Detect Bun only when you must
You can detect Bun at runtime:
if (process.versions.bun) {
console.log(`Running Bun ${process.versions.bun}`)
}
On Bun this prints something like Running Bun 1.3.11. On Node the property is undefined and nothing prints.
Use checks like this only when behavior must differ. Every if (bun) ... else ... branch is a second code path to test and keep in sync. Shared Web APIs such as Request, Response, fetch, and URL work the same on both runtimes, and building on them gives you a cleaner boundary than a pile of Bun-versus-Node branches.
Lesson completed