Skip to main content
  1. Posts/

Buntime Funtime

·1331 words·7 mins
Photograph By Cesar Carlevarino Aragon
Blog JavaScript Web Development
Table of Contents

The All-in-One Promise
#

The JavaScript ecosystem has a tooling problem. To run a TypeScript project, you need Node.js for the runtime, npm (or pnpm, or yarn) for packages, webpack or Vite for bundling, ts-node for TypeScript execution, and Jest or Vitest for tests. Each tool has its own config file, its own quirks, and its own version to manage. It works, but it’s a lot of moving parts for what is fundamentally “run my code.”

Bun says: what if one tool did all of that?

What Bun Actually Is
#

Bun is a JavaScript/TypeScript runtime written in Zig, powered by JavaScriptCore (Safari’s engine, not V8). But calling it a runtime undersells it. It’s also a package manager, a bundler, and a test runner — all in one binary.

bun install          # package manager (replaces npm/pnpm)
bun run server.ts    # runtime (replaces node + ts-node)
bun test             # test runner (replaces Jest/Vitest)
bun build ./src      # bundler (replaces webpack/esbuild)

One install. No tsconfig.json needed for basic TypeScript execution. No ts-node. No build step between writing TypeScript and running it.

Batteries Included (And Why That Matters for Security)
#

Here’s something that doesn’t show up in benchmarks but matters for supply chain security : Bun ships with built-in modules for things that traditionally require third-party npm packages.

Neednpm Ecosystem (third-party)Bun Built-in (maintained by Oven)
SQLitebetter-sqlite3bun:sqlite (3-6x faster)
Password hashingbcrypt (C++ addon)Bun.password (bcrypt/argon2)
Test runnerjest / vitestbun:test
HTTP serverexpress / fastifyBun.serve()
WebSocketwsBuilt into Bun.serve()
S3 client@aws-sdk/client-s3Bun.S3
Database clientpg / mysql2Bun.SQL (PostgreSQL, MySQL, SQLite)
File I/Ofs-extraBun.file() / Bun.write()
Cryptocrypto (Node built-in)Web Crypto API (native)

Every third-party package is a dependency you’re trusting. Each one can be compromised, abandoned, or typosquatted. When Bun provides these as built-in modules maintained by the core team, that’s fewer dependencies in your node_modules, fewer potential attack vectors, and fewer packages to audit.

This isn’t theoretical — npm supply chain attacks have hit major packages. The fewer third-party dependencies your project needs, the smaller your attack surface. Bun’s batteries-included approach turns a performance feature into a security feature.

The Benchmarks (And Why They Lie)
#

Every Bun article leads with benchmarks, so let’s get them out of the way:

WhatBunNode.jsDifference
Package install (1,847 deps)~5 seconds~28 minutes (npm)300x+
Cold start8-15ms60-120ms5-8x
HTTP server (synthetic)52,000 req/s13,000 req/s4x
HTTP server (real app with DB)~12,000 req/s~12,000 req/sSame

That last row is the one that matters. In synthetic benchmarks — “how fast can you respond to an empty HTTP request” — Bun crushes Node.js. In real applications where you’re querying a database, running business logic, and serializing responses, the runtime overhead becomes noise. The bottleneck moves from the JavaScript engine to I/O.

Does this mean the speed doesn’t matter? No. Package installs going from 28 minutes to 5 seconds is genuinely transformative for CI/CD pipelines. Cold starts dropping by 5-8x matters for serverless. But if someone tells you “Bun makes my API 4x faster,” ask them what their database latency is.

The Project: BHVR Stack
#

I’ve been building with the BHVR stack — Bun, Hono, Vite, React — in a Turborepo monorepo. Here’s what the project looks like:

my-bhvr/
├── backend/
│   ├── auth/        # Authentication service (Hono + Bun)
│   ├── gateway/     # API gateway (Hono + Zod validation)
│   └── todos/       # Todo service (Hono + Bun)
├── frontend/
│   └── client/      # React + Vite + TanStack
├── packages/
│   ├── types/       # Shared TypeScript types
│   ├── ui/          # Shared UI components
│   └── eslint-config/
├── turbo.json       # Turborepo task orchestration
└── bun.lock         # Bun lockfile (binary)

Bun is both the runtime and the package manager. Each backend service runs with bun --watch run src/server.ts in development — native TypeScript execution with file watching, no build step. The gateway uses Hono (a lightweight web framework designed for edge runtimes) with Zod for request validation.

Hono: The Express Replacement
#

If Bun is the runtime, Hono is the framework. It’s what Express would be if it were designed in 2024 for modern runtimes instead of 2010 for Node.js.

import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";

const app = new Hono();

// Looks familiar if you've used Express
app.get("/api/todos", async (c) => {
  const todos = await getTodos();
  return c.json(todos);
});

// But with built-in Zod validation
app.post(
  "/api/todos",
  zValidator(
    "json",
    z.object({
      title: z.string().min(1),
      completed: z.boolean().default(false),
    }),
  ),
  async (c) => {
    const data = c.req.valid("json"); // typed and validated
    const todo = await createTodo(data);
    return c.json(todo, 201);
  },
);

Hono is tiny (~14KB), works across runtimes (Bun, Node.js, Deno, Cloudflare Workers), and has first-class TypeScript support. The Zod validator middleware means your request body is validated and typed before your handler runs. No more if (!req.body.title) checks scattered through your code.

What Actually Changed
#

The Install Speed Is Real
#

bun install on the monorepo takes about 3 seconds. The equivalent npm install would take close to a minute. In development, this means switching branches with different dependencies is instant. In CI, this means faster pipelines.

TypeScript Without a Build Step
#

This is the one that changes your workflow the most. In Node.js land, running TypeScript means either:

  • tsc to compile → node dist/index.js to run (two steps, rebuild on every change)
  • ts-node or tsx as a wrapper (slower startup, sometimes broken)

With Bun:

bun run src/server.ts    # just works, native TS execution

No tsconfig.json required for basic execution (though you’ll still want one for strict type checking). No build step. Save the file, the watcher restarts, you see the result. The feedback loop tightens significantly.

Turborepo Ties It Together
#

The monorepo uses Turborepo for task orchestration — turbo dev starts all services in parallel, turbo build builds everything in dependency order, turbo test runs all test suites. Combined with Bun workspaces, shared packages (@repo/types, @repo/ui) are resolved locally without publishing.

{
  "workspaces": ["./backend/*", "./frontend/*", "./packages/*"]
}

The gateway imports types from @repo/types and the frontend imports components from @repo/ui — all resolved at the workspace level, all type-checked across boundaries.

The Honest Comparison
#

Aspectnpm + Node.js + ExpressBun + Hono
Install speed30-60s3-5s
TypeScript executionRequires ts-node or build stepNative
Framework weightExpress: ~200 depsHono: ~14KB, minimal deps
Type safetyManual validation or middlewareZod integration built-in
Monorepo supportnpm workspaces (works)Bun workspaces (faster)
Ecosystem maturity15 years of packages~98% npm compatibility
Production track recordBattle-testedGrowing, not yet industry standard
Debugging toolsMature (Node inspector, Chrome DevTools)Good (improving)

When to Use Bun
#

Use Bun if you’re starting a new project, want fast TypeScript execution without build tooling, care about install speed (especially in CI), or are building for edge/serverless where cold start matters.

Stick with Node.js if you’re maintaining a large existing codebase, depend on native C++ addons, need maximum ecosystem stability, or your team doesn’t have bandwidth to learn a new runtime.

The middle ground: use bun install as your package manager with an existing Node.js project. You get the install speed without changing anything else. This is the lowest-risk way to try Bun — swap one command, keep everything else.

The pnpm Question
#

I use pnpm at work. It’s fast, disk-efficient, and strict about dependencies (no phantom deps). For existing Node.js projects, pnpm is the right choice — it solves the package management problem without requiring a runtime change.

Bun is faster at installs, but pnpm is “more correct” — its strict dependency resolution catches problems that Bun’s more permissive approach misses. In a large monorepo with dozens of packages, that strictness matters more than a few seconds of install time.

For new projects where I’m choosing the full stack? Bun. For existing Node.js projects at work? pnpm. Different tools for different contexts — a familiar theme .

Aaron Yong
Author
Aaron Yong
Building things for the web. Writing about development, Linux, cloud, and everything in between.

Related

The Contract
·785 words·4 mins
Photograph By Romain Dancre
Blog Software Engineering Web Development
REST API design patterns that save your future self from debugging nightmares
Building With Hugo
·1204 words·6 mins
Photograph By Nick Morrison
Blog Hugo Web Development
How I built this website with Hugo and the Blowfish theme
Load testing with Grafana K6
·1414 words·7 mins
Photograph By National Cancer Institute
Blog Testing Node.js
Our first foray into load testing our backend APIs