KahWee - Web Development, AI Tools & Tech Trends

Expert takes on AI tools like Claude and Sora, modern web development with React and Vite, and tech trends. By KahWee.

Switching from Node to Bun - Part 2

This is Part 2 covering the migration process and results. Part 1 covered why I switched and the key improvements.

The Migration: Step by Step

Phase 1: Package Management

First, swap npm for Bun's package manager:

// Before (package.json scripts)
"scripts": {
  "start": "ts-node src/index.ts",
  "build": "tsc",
  "dev": "nodemon --watch src --exec ts-node src/index.ts",
}

// After
"scripts": {
  "start": "bun src/index.ts",
  "build": "bun build ./src/index.ts --outdir ./dist --target=bun",
  "dev": "bun --watch src/index.ts",
}

Bun runs TypeScript files directly, which eliminates the separate compilation step and results in faster command execution.

Phase 2: Build Configuration

Bun's native bundler replaced TypeScript's compiler. Bun's sensible defaults eliminated dozens of lines of configuration.

Phase 3: CI Integration

Updated GitHub Actions:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: oven-sh/setup-bun@v1
        with:
          bun-version: 1.2.11
      - run: bun install
      - run: bun run build
      - run: bun run typecheck

This also killed package-lock.json — 4,500+ lines removed — in favor of bun.lock.

Phase 4: Refactoring File Operations

The biggest payoff came from converting Node's fs operations to Bun's native File API.

Using Claude Code for the Migration

Claude Code identified file system operations that could use Bun's native APIs. The approach:

  1. Scan for patterns like fs.readFile and fs.pathExists across the codebase
  2. Build a migration plan for each file based on usage patterns
  3. Generate side-by-side rewrites with explanations of Bun-specific benefits
  4. Apply changes file by file

Claude suggested optimizations I wouldn't have considered, like using arrayBuffer() for binary file operations.

Challenges

Ecosystem maturity: Not all Node packages work with Bun yet.

Documentation gaps: Bun's docs are improving but still lag behind Node's.

Error messages: Bun sometimes gives less informative errors than Node.

Native extensions: Some C/C++ extensions don't work with Bun out of the box.

Warning

Test your critical dependencies against Bun before migrating production workloads. Not all Node packages work yet, and error messages can be less informative.

The benefits outweighed these drawbacks for my use case.

Results: Faster Builds, Cleaner Code

Build time dropped from 37 seconds to 20 seconds — 46% faster. The development cycle feels different when you're not waiting.

Operation Node.js Bun Improvement
Cold start 1.2s 0.3s 75% faster
Build time 37s 20s 46% faster
File operations Moderate Very fast ~3-4x faster
TypeScript execution Requires transpilation Native support No compile step

The codebase is cleaner now too:

Metric Before (Node) After (Bun) Change
Dependencies 20+ 15 25% reduction
Configuration files Multiple complex files Minimal configs Simpler
Boilerplate code Considerable Minimal Cleaner
File system code Complex Direct and simple Easier to maintain

Should You Switch?

Bun fits well for:

  • Static site generators and content-heavy applications
  • TypeScript projects that want to skip compilation
  • Projects bottlenecked by build performance
  • New projects that can fully adopt Bun's ecosystem

For existing projects, migrate gradually. Start with build tooling, then move to runtime code as you validate compatibility. My path:

  1. Package management and simple commands
  2. Build configuration
  3. CI/CD pipelines
  4. File operations refactored to native APIs
  5. Dependency cleanup

Bun is a real step forward for I/O-heavy JavaScript projects. My migration paid for itself within the first week of faster builds.