Skip to main content
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:

server.ts
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:

db.ts
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:

add.test.ts
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 HTMLRewriter built on lol-html. Deno has no built-in HTMLRewriter global; use an npm package such as npm:lol-html or an HTML parser like deno-dom.
  • Bundler specifics. bun build features like HTML entrypoints with automatic asset bundling don't map one-to-one onto the experimental deno bundle. For full-featured frontend builds, use Vite or another bundler under Deno; see Bundling.

Keep going Jump to heading

Last updated on

Did you find what you needed?

Edit this page
Privacy policy