On this page
Migrate from Bun
Most Bun projects are standard package.json and TypeScript projects, and Deno
runs those directly. Deno reads your package.json, installs the same npm
dependencies, runs TypeScript without a build step, and ships a comparable
built-in toolchain: a test runner, formatter, linter, bundler, and compiler in
one binary.
What needs attention is anything that uses Bun-specific APIs (Bun.serve,
bun:sqlite, Bun.file, the $ shell). Each has a close equivalent in Deno or
in the Node built-ins Deno supports, covered below. Code that imports nothing
from bun or bun:* usually runs unchanged.
Run your project Jump to heading
Install dependencies and run your entrypoint:
cd my-bun-app
deno install
deno run main.ts
deno install reads your existing package.json and resolves the same npm
packages, like bun install. On your first deno install, when there is no
deno.lock yet, Deno seeds it from your existing bun.lock, carrying over the
versions and integrity hashes you already had pinned. bun.lock is Bun's text
lockfile (written since Bun 1.1.39); the legacy binary bun.lockb is not read,
so if that is all you have, run bun install once to produce a bun.lock first
or let Deno resolve your package.json ranges fresh.
Scripts defined in package.json run with
deno task, the equivalent of bun run:
deno task start
The one behavioral difference you will hit immediately: Deno is sandboxed by
default. Where Bun runs your code with full access to the network, file
system, and environment, Deno prompts for each permission the first time the
program needs it. You can grant everything up front with deno run -A main.ts
to match Bun's behavior, then tighten the flags later. See
Security and permissions and the
permission flags reference.
Like Bun, Deno lets you skip the subcommand for files: deno main.ts works the
same as deno run main.ts.
Bun to Deno cheatsheet Jump to heading
Dependencies Jump to heading
| Bun | Deno |
|---|---|
bun install |
deno install |
bun add <pkg> |
deno add <pkg> |
bun remove <pkg> |
deno remove <pkg> |
bun update |
deno update |
bun outdated |
deno outdated |
Run and execute Jump to heading
| Bun | Deno |
|---|---|
bun file.ts |
deno file.ts |
bun run <script> |
deno task <script> |
bunx <pkg> |
dx <pkg> |
Test, build, and toolchain Jump to heading
| Bun | Deno |
|---|---|
bun test |
deno test |
bun build |
deno bundle ¹ |
bun build --compile |
deno compile |
bun upgrade |
deno upgrade |
¹ deno bundle produces a single JavaScript
file from a module graph. It is currently experimental; see the
Bundling guide for the options.
Bun has no built-in formatter or linter, so these are additions rather than
replacements: deno fmt and
deno lint cover what you would otherwise
install Prettier, ESLint, or Biome for, with no extra dependencies.
Bun APIs and their Deno equivalents Jump to heading
Bun.serve to Deno.serve Jump to heading
Both servers take a fetch-style handler: a function that receives a Request
and returns a Response. A Bun.serve call usually translates directly to
Deno.serve:
Deno.serve({ port: 3000 }, (req) => {
return new Response("Hello, World!");
});
The differences: Bun.serve takes a single options object with a fetch
property, while Deno.serve takes the handler as a
direct argument (optionally preceded by an options bag). Bun passes the server
object as the handler's second argument; Deno passes connection info instead.
Bun's routes option has no built-in counterpart in
Deno.serve: use URLPattern or a framework for
routing. See HTTP servers for more.
bun:sqlite to node:sqlite Jump to heading
Deno supports the node:sqlite built-in, which
is also synchronous and covers the same ground:
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync(":memory:");
db.exec("CREATE TABLE people (name TEXT)");
db.prepare("INSERT INTO people VALUES (?)").run("Ada");
const rows = db.prepare("SELECT * FROM people").all();
console.log(rows);
The class names differ (DatabaseSync and StatementSync instead of Database
and Statement), but the prepare/run/get/all flow is the same.
Bun.file to Deno file APIs Jump to heading
Bun.file() returns a lazy file reference with .text() and .json() methods.
In Deno, read the file directly with
Deno.readTextFile, or use
Deno.open when you need a handle for streaming:
const text = await Deno.readTextFile("./data.txt");
const config = JSON.parse(await Deno.readTextFile("./config.json"));
For higher-level helpers (copy, move, walk, exists), use the
@std/fs package. node:fs also works if you prefer
the Node APIs.
bun:test to deno test Jump to heading
bun test runs Jest-style tests imported from bun:test. Deno supports the
node:test built-in, so the describe and
it structure carries over directly; pair it with node:assert for assertions:
import { describe, it } from "node:test";
import assert from "node:assert";
import { add } from "./add.ts";
describe("add", () => {
it("adds two numbers", () => {
assert.strictEqual(add(1, 2), 3);
});
});
deno test
If you want to keep Bun's Jest-style expect assertions, import expect from
@std/expect. Deno's own
Deno.test runner is also available. See
Testing for the full picture.
The $ shell Jump to heading
Bun's $ template-literal shell has a near-identical counterpart:
dax, one of the libraries that inspired it.
import $ from "jsr:@david/dax";
const result = await $`echo hello`.text();
For plain subprocess control without a shell layer, use the built-in
Deno.Command API.
bunfig.toml to deno.json Jump to heading
Runtime and tooling configuration moves from bunfig.toml to deno.json:
tasks, formatter and linter settings, compiler options, and import maps all live
there. Deno reads package.json alongside it, so you can migrate incrementally.
See Configuration.
What has no direct equivalent Jump to heading
A few Bun features have no Deno counterpart, so plan around them:
- Macros. Bun can run functions at bundle time via
with { type: "macro" }imports and inline the results. Deno has no bundle-time macro system; do that work in a build script instead. - HTMLRewriter. Bun ships the Cloudflare Workers-compatible
HTMLRewriterbuilt on lol-html. Deno has no built-inHTMLRewriterglobal; use an npm package such asnpm:lol-htmlor an HTML parser likedeno-dom. - Bundler specifics.
bun buildfeatures like HTML entrypoints with automatic asset bundling don't map one-to-one onto the experimentaldeno bundle. For full-featured frontend builds, use Vite or another bundler under Deno; see Bundling.
Keep going Jump to heading
- Migrate from Node.js. Much of it applies to Bun projects too, including how CommonJS and ES modules are resolved.
- Security and permissions. How the sandbox works and which flags to grant.
- Testing. The test runner, mocking, snapshots, and coverage.
- Dependency management. npm, JSR, and
package.jsonworkflows in detail.