Bun is an all-in-one JavaScript runtime built from the ground up to be fast. Written in Zig and using JavaScriptCore (the engine powering Safari) instead of V8, Bun combines a runtime, package manager, bundler, and test runner into a single executable. Where most JavaScript toolchains require you to wire together Node.js, npm or Yarn, esbuild or Webpack, and Jest or Vitest, Bun handles all of it natively — with benchmarks that consistently show 2-5x faster startup times and package installation speeds that leave npm in the dust. Since Bun 1.0's production-stable release and the continued improvements in 1.1+, it has become a serious choice for everything from small scripts to production web services.

## Why Bun Matters for Web Developers

The JavaScript ecosystem has long suffered from **toolchain sprawl** — a typical project might involve five or more separate tools just to go from source code to running server. Each tool has its own configuration format, its own version to manage, and its own surface area for things to go wrong. Bun collapses this complexity. One binary handles the tasks that previously required an entire dependency tree of dev tooling, which means fewer things to install, fewer things to configure, and fewer things to debug when something breaks in CI.

**Raw performance** is the other headline. Bun's startup time is dramatically lower than Node.js because JavaScriptCore initialises faster than V8. For serverless functions, scripts, and CLI tools, this matters enormously — cold starts that took 300ms under Node can drop below 50ms with Bun. For `bun install`, the speed difference is almost embarrassing: a `node_modules` directory that takes 45 seconds to populate with npm often takes under five seconds with Bun, because it uses a global module cache and hard links rather than copying files.

**Native TypeScript support** means there is no compilation step to set up before running `.ts` files. Running `bun run server.ts` just works, with no `ts-node`, no `tsx`, no `esbuild` wrapper required. The same applies to JSX and TSX. Bun parses and transpiles these formats natively, using the same engine that handles JavaScript, which keeps the toolchain coherent and fast.

**Node.js compatibility** is a deliberate design goal rather than an afterthought. Bun implements the Node.js API surface — `fs`, `path`, `http`, `crypto`, `child_process`, `stream`, and most others — so the vast majority of npm packages work without modification. The `bun:` namespace exposes Bun-specific APIs for when you want to reach for native capabilities. This means you can migrate an existing Node.js project incrementally, running it under Bun before gradually adopting Bun-native patterns.

## Step 1: System Requirements

Before installing Bun, confirm your environment meets the following requirements.

**Supported operating systems:**

- macOS (x64 and Apple Silicon, macOS 12+)
- Linux (x64 and ARM64, kernel 5.1+, glibc 2.17+)
- Windows (x64, Windows 10+, experimental as of Bun 1.1)

**For production servers**, Linux is the primary target and receives the most optimisation work. If you are deploying to a VPS or bare metal server, a modern Ubuntu LTS (22.04 or 24.04) or Debian release is an excellent choice.

**Git** should already be installed if you are working with any version-controlled project. No other prerequisites are required — Bun ships as a single self-contained binary and brings its own JavaScript engine.

## Step 2: Install Bun

### macOS and Linux

The official install script downloads the Bun binary, verifies its checksum, and places it in `~/.bun/bin`:

```bash
curl -fsSL https://bun.sh/install | bash
```

After the script completes, add Bun to your shell's PATH by following the instructions printed to your terminal. For bash:

```bash
echo 'export BUN_INSTALL="$HOME/.bun"' >> ~/.bashrc
echo 'export PATH="$BUN_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
```

For zsh, replace `.bashrc` with `.zshrc`.

### Homebrew (macOS)

```bash
brew tap oven-sh/bun
brew install bun
```

### npm (global install, any platform)

If you already have Node.js available and want a quick install without the curl script:

```bash
npm install -g bun
```

### Windows

On Windows, use PowerShell:

```powershell
powershell -c "irm bun.sh/install.ps1 | iex"
```

Note that Windows support is still marked experimental. For production workloads on Windows, running Bun inside WSL2 is the more reliable path.

### Verify the installation

```bash
bun --version
```

### Upgrading Bun

```bash
bun upgrade
```

## Step 3: Project Setup

### Initialising a new project

`bun init` creates a new project with a `package.json`, a basic `tsconfig.json`, and an entry file:

```bash
mkdir my-bun-project
cd my-bun-project
bun init
```

For a blank project with no prompts:

```bash
bun init -y
```

### Package management

Installing all dependencies from an existing `package.json`:

```bash
bun install
```

Adding a production dependency:

```bash
bun add hono
```

Adding a development dependency:

```bash
bun add -d @types/bun
```

Running a script from `package.json`:

```bash
bun run build
bun run dev
```

### The lockfile

Bun generates a `bun.lockb` binary lockfile rather than the text-based `package-lock.json` or `yarn.lock`. The binary format is faster to parse and smaller on disk. Commit `bun.lockb` to version control so that all team members and your CI environment get identical dependency trees.

### TypeScript configuration

Bun generates a sensible `tsconfig.json` on `bun init`, but here is a solid starting point for a Bun-first project:

```json
{
  "compilerOptions": {
    "lib": ["ESNext"],
    "module": "ESNext",
    "target": "ESNext",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowImportingTsExtensions": true,
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true,
    "noEmit": true,
    "strict": true,
    "skipLibCheck": true,
    "types": ["bun-types"]
  }
}
```

Install the type definitions for Bun's built-in APIs:

```bash
bun add -d bun-types
```

## Step 4: Configure Bun for Your Workflow

### bunfig.toml

`bunfig.toml` is Bun's configuration file, equivalent to `.npmrc` combined with parts of Vite's and Jest's config:

```toml
[install.scopes]
"@mycompany" = { registry = "https://npm.mycompany.internal", token = "$NPM_TOKEN" }

[install]
registry = "https://registry.npmjs.org"
production = false

[test]
coverage = true
coverageReporter = ["text", "lcov"]
coverageDir = "coverage"
preload = ["./tests/setup.ts"]

[bundle]
outdir = "dist"
target = "browser"
```

### Defining scripts

`package.json` scripts work exactly as they do under npm:

```json
{
  "scripts": {
    "dev": "bun run --hot src/index.ts",
    "build": "bun build src/index.ts --outdir dist --target node",
    "start": "bun run dist/index.js",
    "test": "bun test",
    "typecheck": "tsc --noEmit",
    "lint": "eslint src --ext .ts,.tsx"
  }
}
```

### Workspaces

Bun supports npm-style workspaces for monorepos:

```json
{
  "name": "my-monorepo",
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}
```

Running `bun install` from the root installs all workspace packages and symlinks them appropriately. To run a script in a specific workspace:

```bash
bun run --filter "./apps/web" dev
```

## Step 5: Core Features

### Runtime

The simplest use of Bun is as a drop-in replacement for `node`:

```bash
bun server.js
bun server.ts
```

Bun supports ES modules (`import`/`export`) and CommonJS (`require`) in the same project without configuration.

### Package manager

```bash
bun install --frozen-lockfile
bun install --production
bun pm ls --all
```

### Bundler

Bun's bundler produces optimised output for both browsers and Node.js-compatible servers:

```bash
bun build src/index.ts --outdir dist --target browser
bun build src/server.ts --outdir dist --target node
bun build src/index.ts --outdir dist --minify
bun build src/cli.ts --compile --outfile my-tool
```

The `--compile` flag bundles your code and Bun's runtime into a single binary.

You can also use the bundler programmatically:

```typescript
const result = await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  target: "browser",
  minify: true,
  splitting: true,
});

if (!result.success) {
  console.error("Build failed:", result.logs);
  process.exit(1);
}
```

### Test runner

Bun's built-in test runner uses the same API as Jest:

```typescript
import { expect, test, describe, beforeEach, afterEach } from "bun:test";

describe("user authentication", () => {
  test("hashes passwords before storing", async () => {
    const hash = await Bun.password.hash("hunter2");
    expect(hash).not.toBe("hunter2");
    expect(await Bun.password.verify("hunter2", hash)).toBe(true);
  });
});
```

Run all tests:

```bash
bun test
bun test --watch
bun test --coverage
```

---

*Building with Bun? [DeployHQ](https://www.deployhq.com/signup) connects your Git repo to any server and deploys automatically when you push — SFTP, SSH, or cloud. [Try it free](https://www.deployhq.com/signup).*

---

## Step 6: Advanced Features

### Bun.serve — HTTP server

`Bun.serve()` is Bun's built-in HTTP server, implemented in native code:

```typescript
const server = Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);

    if (url.pathname === "/") {
      return new Response("Hello from Bun!", {
        headers: { "Content-Type": "text/plain" },
      });
    }

    if (url.pathname === "/health") {
      return Response.json({ status: "ok", uptime: process.uptime() });
    }

    return new Response("Not Found", { status: 404 });
  },
  error(err) {
    console.error(err);
    return new Response("Internal Server Error", { status: 500 });
  },
});

console.log(`Listening on http://localhost:${server.port}`);
```

`Bun.serve()` also supports WebSockets natively:

```typescript
Bun.serve({
  port: 3000,
  fetch(req, server) {
    if (server.upgrade(req)) {
      return;
    }
    return new Response("Upgrade required", { status: 426 });
  },
  websocket: {
    open(ws) { console.log("Client connected"); },
    message(ws, message) { ws.send(`Echo: ${message}`); },
    close(ws) { console.log("Client disconnected"); },
  },
});
```

### Built-in SQLite

Bun ships with SQLite as a first-class built-in:

```typescript
import { Database } from "bun:sqlite";

const db = new Database("myapp.sqlite");

db.exec(`
  CREATE TABLE IF NOT EXISTS posts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    body TEXT,
    created_at INTEGER DEFAULT (unixepoch())
  )
`);

const insert = db.prepare(
  "INSERT INTO posts (title, body) VALUES ($title, $body)"
);

insert.run({ $title: "My First Post", $body: "Hello, world!" });

const posts = db
  .query("SELECT * FROM posts ORDER BY created_at DESC LIMIT ?")
  .all(10);

db.close();
```

### Foreign Function Interface (FFI)

Bun can call native code from shared libraries:

```typescript
import { dlopen, FFIType, suffix } from "bun:ffi";

const { symbols } = dlopen(`libm.${suffix}`, {
  sqrt: {
    args: [FFIType.double],
    returns: FFIType.double,
  },
});

console.log(symbols.sqrt(144)); // 12
```

### Hot reloading

```bash
bun run --watch src/index.ts    # restarts on change
bun run --hot src/index.ts      # reloads modules without restart
```

## Step 7: Best Practices

### When to use Bun vs Node.js

**Use Bun when:**

- Starting a new project and want a fast, integrated toolchain
- Startup time matters (CLI tools, serverless functions, scripts)
- You want native TypeScript without a separate compilation step
- You need built-in SQLite without a native addon

**Keep Node.js when:**

- You depend on packages with Node-specific native addons
- You are in a locked-down CI environment where Bun cannot be installed
- Migration overhead is not justified for the team

### Commit the lockfile

Always commit `bun.lockb`. Use `--frozen-lockfile` in CI for deterministic installs.

### Use `Bun.$` for shell scripting

```typescript
import { $ } from "bun";

const output = await $`ls -la`.text();
const dir = "dist";
await $`rm -rf ${dir} && mkdir ${dir}`;
```

## Step 8: Deploy with DeployHQ

### Install Bun on your target server

```bash
curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
bun --version
```

Ensure Bun's binary path is in the system `PATH` for the deploy user:

```bash
export BUN_INSTALL="/home/deploy/.bun"
export PATH="$BUN_INSTALL/bin:$PATH"
```

### Configure your DeployHQ build commands

```bash
bun install --frozen-lockfile
bun run build
```

### Serving with Bun.serve

Create an `ecosystem.config.js`:

```javascript
module.exports = {
  apps: [
    {
      name: "my-bun-app",
      script: "bun",
      args: "run start",
      interpreter: "none",
      env: {
        NODE_ENV: "production",
        PORT: "3000",
      },
    },
  ],
};
```

Post-deployment command:

```bash
pm2 reload ecosystem.config.js --env production
```

Alternatively, use systemd:

```ini
[Unit]
Description=My Bun Application
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/my-bun-app
ExecStart=/home/deploy/.bun/bin/bun run start
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target
```

### Branch environments

- `main` branch -> production server, `NODE_ENV=production`
- `develop` branch -> staging server, `NODE_ENV=staging`

For production, always use `--frozen-lockfile`:

```bash
bun install --frozen-lockfile
NODE_ENV=production bun run build
```

## Step 9: Troubleshooting

**`bun: command not found` after installation**

Source your shell config:

```bash
source ~/.bashrc
```

Verify `~/.bun/bin` is in your PATH.

**`bun install` fails with `lockfile had changes`**

Run `bun install` locally without `--frozen-lockfile`, commit both `package.json` and `bun.lockb`, then push.

**A package fails to install or import**

Some packages with native addons compile specifically for Node.js. Common replacements:

- `better-sqlite3` -> `bun:sqlite` (built-in)
- `bcrypt` -> `Bun.password` (built-in)

**TypeScript errors about Bun globals**

Ensure `bun-types` is installed and in your `tsconfig.json`:

```json
{
  "compilerOptions": {
    "types": ["bun-types"]
  }
}
```

**Port already in use on deployment**

```bash
pm2 restart my-bun-app
# or
lsof -i :3000
```

## Conclusion

Bun represents a significant shift in what a JavaScript runtime can be. By collapsing the runtime, package manager, bundler, and test runner into a single fast binary — with native TypeScript support and strong Node.js compatibility — it removes a layer of toolchain complexity that has been a constant background cost of JavaScript development for years. Whether you adopt it as a full replacement for your Node.js stack or use it incrementally for its package installation speed alone, Bun rewards the switch with faster iteration cycles and simpler project configuration.

For production deployments, the combination of `bun install --frozen-lockfile`, `bun run build`, and a simple process manager like PM2 or systemd gives you a reliable, reproducible deployment pipeline. Pair that with [DeployHQ's](https://www.deployhq.com/signup) automatic Git-triggered deployments and you have a workflow that goes from `git push` to live update with minimal manual intervention.

---

If you have questions about deploying your Bun projects, reach out to us at [support@deployhq.com](mailto:support@deployhq.com) or find us on [Twitter/X](https://x.com/deployhq).
