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
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
Dependencies
| 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
| Bun | Deno |
|---|---|
bun file.ts | deno file.ts |
bun run <script> | deno task <script> |
bunx <pkg> | dx <pkg> |
Test, build, and toolchain
| 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
Bun.serve to Deno.serve
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
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
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
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
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
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
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
- 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.
Last updated on
© 2018–2025 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/runtime/migrate/migrate_from_bun