{}const=>[]async()letfn</>var
DevelopmentWeb

Moving from Node.js to Bun: real problems, bugs and pitfalls

Decided to try Bun instead of Node.js? Learn about the real pitfalls of migration: package incompatibility, database problems, debugging, and other bugs that you will definitely encounter.

К

Kodik

Author

8 min read

Introduction: What is Bun and why is everyone talking about it?

Bun is a new JavaScript runtime that promises to be much faster than Node.js and Deno. Its developers claim that the installation of packages is 10-20 times faster, and the launch of applications is 3-4 times faster. Sounds tempting, doesn't it? But what happens when you decide to move a real project to Bun?

In this article, I will talk about the real problems that developers face when migrating, and how to solve them.

🔥 100,000+ students already with us

Tired of reading theory?
Time to code!

Kodik — an app where you learn to code through practice. AI mentor, interactive lessons, real projects.

🤖 AI 24/7
🎓 Certificates
💰 Free
🚀 Start learning
Joined today

What does Bun promise?

Before we talk about the problems, let's figure out what Bun offers in general:

  • Speed: Written in Zig, uses JavaScriptCore instead of V8

  • Built-in tools: bundler, transpiler, package manager in one bottle

  • Compatibility: Support for most Node.js APIs is claimed

  • TypeScript out of the box: No need for a separate compiler

  • Web API: Support for modern browser APIs on the server

Sounds perfect. But practice shows otherwise.

Problem #1: Incomplete compatibility with Node.js

What is expected?

Bun developers promise 90%+ compatibility with the Node.js API. In theory, your code should just work.

Reality:

// This code works in Node.js
const fs = require('fs');
fs.watch('./files', { recursive: true }, (event, filename) => {
  console.log(`${filename} changed`);
});

In Bun, the recursive option for fs.watch() does not work on some operating systems. You will receive an error or silent failure.

Solution:

Check the Bun documentation for each API you use. It is often necessary to use alternative libraries:

// Alternative to Bun
import { watch } from 'chokidar';

watch('./files', { 
  ignoreInitial: true 
}).on('all', (event, path) => {
  console.log(`${path} changed`);
});

Problem #2: npm packages with native modules.

What is expected?

Bun must support most npm packages, including those that use native modules.

Reality:

Many popular packages simply do not work:

bun install sharp  # Popular library for working with images

When trying to use:

import sharp from 'sharp';
const image = sharp('input.jpg');
// Error: Cannot find module "sharp"

Problems with other packages:

  • bcrypt — native bindings are not supported

  • node-gyp dependencies — require a complete rebuild

  • sqlite3 — unstable operation

Solution:

Look for alternatives in pure JavaScript:

// Use bcryptjs instead of bcrypt
import bcrypt from 'bcryptjs';
const hash = await bcrypt.hash('password', 10);

// Instead of sharp, you can use Bun.file() + Canvas API
import { createCanvas, loadImage } from 'canvas';

Problem #3: Differences in EventEmitter behavior

What is expected?

EventEmitter should work exactly the same as in Node.js.

Reality:

const EventEmitter = require('events');
const emitter = new EventEmitter();

// In Node.js, it works
emitter.on('event', async () => {
  await someAsyncOperation();
});

emitter.emit('event');
console.log('Event emitted');
// Node.js: "Event emitted" → async operation
// Bun: May fall or be executed in a different order

Error handling in asynchronous event handlers is different, which can lead to unhandled promise rejections.

Solution:

Always wrap asynchronous handlers:

emitter.on('event', (data) => {
  (async () => {
    try {
      await someAsyncOperation(data);
    } catch (error) {
      console.error('Error in event handler:', error);
    }
  })();
});

Problem #4: Differences in working with paths

Reality:

import path from 'path';
import { fileURLToPath } from 'url';

// Node.js + ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Bun behaves differently
console.log(__dirname); // May be undefined or incorrect

Solution:

Use the built-in Bun features:

// The right way for Bun
const currentFile = import.meta.path;
const currentDir = import.meta.dir;

console.log('Current file:', currentFile);
console.log('Current directory:', currentDir);

Problem #5: Environment variables and dotenv

What is expected?

Bun automatically loads .env files, so dotenv is not needed.

Reality:

// In Node.js with dotenv
require('dotenv').config();
console.log(process.env.DATABASE_URL);

// In Bun
console.log(process.env.DATABASE_URL); // May not work

Bun loads .env, but the order of priorities is different, and some values may not be picked up.

Solution:

// Explicitly load the configuration
import { config } from 'dotenv';
config({ path: '.env' });

// Or use the Bun API
const env = Bun.env;
console.log(env.DATABASE_URL);

Problem No. 6: Working with databases

Reality with PostgreSQL:

// Node.js + pg
import pg from 'pg';
const { Pool } = pg;

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});

// Bun may not work reliably
const result = await pool.query('SELECT * FROM users');
// Sometimes it hangs or crashes with timeout

Solution:

Use native SQLite support in Bun or alternative drivers:

// Bun has built-in SQLite support
import { Database } from 'bun:sqlite';

const db = new Database('mydb.sqlite');
const query = db.query('SELECT * FROM users');
const users = query.all();

// For PostgreSQL, use postgres.js
import postgres from 'postgres';
const sql = postgres(process.env.DATABASE_URL);
const users = await sql`SELECT * FROM users`;

Problem No. 7: Testing

Reality:

// Jest configuration does not work directly
// package.json
{
  "scripts": {
    "test": "jest"
  }
}

// bun test runs its own test runner

Bun has a built-in test runner, but it is not fully compatible with Jest.

Solution:

Rewrite the tests under Bun:

// test/example.test.ts
import { expect, test, describe } from 'bun:test';

describe('Math operations', () => {
  test('addition', () => {
    expect(2 + 2).toBe(4);
  });
  
  test('async operation', async () => {
    const result = await fetchData();
    expect(result).toBeDefined();
  });
});

Launch:

bun test

Problem #8: Hot Reload and Watch Mode

Reality:

# Node.js with nodemon
nodemon server.js

# Bun
bun --watch server.ts
# It works, but may not restart when some files are changed

Watch mode in Bun sometimes skips changes or restarts too often.

Solution:

Add explicit patterns:

// bunfig.toml
[watch]
ignore = ["node_modules", "dist", ".git"]
include = ["src/**/*.ts", "src/**/*.js"]

Or use external tools:

npm install -D nodemon
nodemon --exec bun run server.ts

Problem #9: Debugging

Reality:

Node.js has great debugging tools via Chrome DevTools or VS Code. In Bun, it works... differently.

# Node.js
node --inspect-brk server.js

# Bun
bun --inspect server.ts
# Does not always correctly show the call stack

Solution:

Use console debugging and logging:

// Add detailed logging
console.log('Debug point 1:', { variable1, variable2 });

// Use formatting utilities
import util from 'util';
console.log(util.inspect(complexObject, { depth: null, colors: true }));

// Or Bun.inspect()
console.log(Bun.inspect(complexObject));

Problem #10: Bundle size and tree-shaking

What is expected?

Bun should create optimized bundles with automatic tree-shaking.

Reality:

bun build ./src/index.ts --outdir ./dist
# Bundle may be larger than expected

Tree-shaking doesn't always work effectively, especially with CommonJS modules.

Solution:

Use only ES modules and check the result:

// ❌ Bad
const lodash = require('lodash');

// ✅ Good
import { map, filter } from 'lodash-es';

// Assembly configuration
bun build ./src/index.ts \
  --outdir ./dist \
  --minify \
  --splitting \
  --target browser

Practical recommendations for migration

Step 1: Start small

Do not transfer the entire project at once. Create a small test project:

mkdir bun-test && cd bun-test
bun init

Step 2: Check dependencies

Create a list of all npm packages and check their compatibility:

# Set dependencies
bun install

# Run tests
bun test

Step 3: Gradual migration

// Create a transition layer
// adapter.ts
export const runtime = {
  isNode: typeof process !== 'undefined' && !process.versions.bun,
  isBun: typeof process !== 'undefined' && !!process.versions.bun
};

export function getAdapter() {
  if (runtime.isBun) {
    return import('./adapters/bun');
  }
  return import('./adapters/node');
}

Step 4: Testing in a production-like environment

# Dockerfile for Bun
FROM oven/bun:1 as base
WORKDIR /app

COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

COPY . .
RUN bun run build

CMD ["bun", "run", "start"]

When NOT to switch to Bun

Do not go if:

  1. The project uses many native modules - time saved on speed will be lost on searching for alternatives

  2. Stability is critical — Bun is still young, bugs are encountered

  3. The team is not ready for experiments - you will have to deal with new problems

  4. Use specific Node.js features — streams, workers, special APIs

  5. Need support for older versions — Bun does not support legacy code

When to try Bun

Switch if:

  1. Create a new project — no old code baggage

  2. Focus on development speed — quick installation of packages really speeds up the work

  3. Use a modern stack — TypeScript, ES modules, modern libraries

  4. Ready for experiments - you can spend time solving problems

  5. Need a built-in bundler — you don't want to configure webpack/vite

A real example of migrating a simple Express application

Node.js version

// server.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.json({ message: 'Hello from Node.js' });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Bun version (what works)

// server.ts
import { serve } from 'bun';

serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);
    
    if (url.pathname === '/') {
      return new Response(
        JSON.stringify({ message: 'Hello from Bun' }),
        { headers: { 'Content-Type': 'application/json' } }
      );
    }
    
    return new Response('Not Found', { status: 404 });
  },
});

console.log('Server running on port 3000')

Bun is an interesting technology, but it's still raw. Real problems with migration include:

Tips for beginners: Use Bun for new pet projects and experiments. For production applications, it is better to stay on Node.js if you do not have specific reasons to switch.

In the Codex we don't just tell the theory — you you get practical skills through real tasks and projects.

What you will get:

  • Structured courses - from basics to advanced topics

  • Practical tasks — consolidate knowledge with real examples

  • Step-by-step analysis — understand how and why the code works

  • Current technologies - study what is used in the industry

Need support and communication?

Join our active Telegram channel - already more than 2000 like-minded peoplewhich:

  • Discuss technologies and share experiences

  • Help each other with solving problems

  • Share useful materials and findings

  • Growing together as developers

Go to Kodik — start your programming journey with community support and quality materials! 🚀

🎯Stop procrastinating

Liked the article?
Time to practice!

In Kodik, you don't just read — you write code immediately. Theory + practice = real skills.

Instant practice
🧠AI explains code
🏆Certificate

No registration • No card