Apidog

All-in-one Collaborative API Development Platform

API Design

API Documentation

API Debugging

API Mocking

API Automated Testing

Getting Started with Bun API

Mark Ponomarev

Mark Ponomarev

Updated on May 4, 2025

Among the most exciting recent dev tools is Bun, an incredibly fast, all-in-one JavaScript toolkit designed to enhance developer productivity and application performance. Bun isn't just another runtime; it's a cohesive ecosystem encompassing a runtime, bundler, test runner, package manager, and more, all within a single native executable. This guide will walk you through the essentials of Bun, focusing on its core concepts and powerful APIs.

💡
Want a great API Testing tool that generates beautiful API Documentation?

Want an integrated, All-in-One platform for your Developer Team to work together with maximum productivity?

Apidog delivers all your demans, and replaces Postman at a much more affordable price!
button

What is Bun?

At its core, Bun is built around a high-performance JavaScript runtime engine. Unlike Node.js, which uses Google's V8 engine, Bun utilizes Apple's JavaScriptCore (JSC), the engine powering Safari. This choice, combined with Bun's implementation in the low-level Zig programming language, contributes significantly to its remarkable speed. Startup times for Bun applications are often measured to be drastically faster than equivalent Node.js applications, sometimes by a factor of four or more. This speed advantage extends beyond just startup; Bun's internal implementations of various APIs are optimized for maximum throughput and minimal memory usage.

But Bun's ambition extends far beyond being just a faster runtime. It aims to be a comprehensive toolkit, addressing common developer needs directly:

  1. Integrated Tooling: The bun command itself acts as the entry point for numerous functionalities. bun run executes scripts defined in package.json, bun install manages dependencies (often much faster than npm or yarn), bun test runs tests using a Jest-compatible API, bun build bundles code for production, and bunx executes packages without explicitly installing them. This integration simplifies workflows by reducing the number of distinct development tools needed.
  2. Native TypeScript and JSX Support: One of Bun's standout features is its out-of-the-box support for TypeScript (.ts, .tsx) and JSX (.jsx) files. Bun includes a built-in, highly optimized transpiler that handles these file types seamlessly during execution or bundling. This eliminates the need for separate compilation steps involving tsc or Babel for many common development scenarios, streamlining the setup process.
  3. Module System Compatibility: Bun embraces modern JavaScript with first-class support for ES Modules (ESM). However, it recognizes the vast existing ecosystem built on CommonJS (CJS). Bun provides robust compatibility for both module systems, allowing developers to use import and require largely interchangeably and leverage the millions of existing CJS packages available on npm.
  4. Web Standard API Adherence: A key design principle is the implementation of standard Web APIs. Functions and objects like fetch, Request, Response, Headers, WebSocket, and the Streams API (ReadableStream, WritableStream) are built into the Bun global scope. This promotes code portability between server-side Bun environments, browser frontends, and edge computing platforms, allowing developers to reuse familiar APIs across different contexts.
  5. Node.js Compatibility: While forging its own path with optimized APIs and Web standards, Bun aims for a high degree of compatibility with the Node.js API surface. Many built-in Node.js modules (node:fs, node:path, node:os, node:events, etc.) and global objects (process, Buffer, __filename, __dirname) are partially or fully implemented. The goal is to allow many existing Node.js projects and npm packages to run on Bun with minimal or no modification, positioning Bun as a potential "drop-in replacement" in numerous cases.

By combining these elements, Bun presents a compelling alternative for JavaScript and TypeScript developers seeking performance, simplicity, and a modern development experience.

Bun — A fast all-in-one JavaScript runtime
Bundle, install, and run JavaScript & TypeScript — all in Bun. Bun is a new JavaScript runtime with a native bundler, transpiler, task runner, and npm client built-in.

How to Install Bun

Getting started with Bun is designed to be quick and straightforward across various platforms. The most common method for macOS, Linux, and Windows Subsystem for Linux (WSL) uses a simple curl command executed in your terminal:

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

This command downloads an installation script and pipes it directly to bash. The script handles the detection of your operating system and architecture, downloads the appropriate Bun executable, and typically installs it to ~/.bun/bin. It also attempts to update your shell's configuration file (like .zshrc, .bashrc, or .bash_profile) to add ~/.bun/bin to your system's PATH, making the bun command globally available. You might need to restart your terminal session or manually source your shell configuration file (e.g., source ~/.zshrc) for the changes to take effect immediately.

If you encounter permission issues or prefer not to pipe directly to bash, you can download the script first and inspect it before running:

curl -fsSL https://bun.sh/install -o install.sh
# Inspect install.sh if desired
bash install.sh

Other Installation Methods:

  • NPM: While primarily intended as a standalone tool, you can also install Bun globally via npm, which might be convenient in environments where Node.js and npm are already present:
npm install -g bun
  • Docker: Official Bun Docker images are available on Docker Hub, providing isolated environments with Bun pre-installed. These are useful for containerized development and deployment workflows. You can find various images based on different base OS distributions (like Debian, Alpine) and tags corresponding to specific Bun versions.
docker run --rm --init --ulimit memlock=-1:-1 oven/bun:latest bun --version
  • Windows: Native Windows support for Bun is still considered experimental but is actively being developed. The recommended way to use Bun on Windows currently is via WSL. However, direct Windows builds are becoming available, and the installation process might involve downloading a .zip archive and manually adding the executable's location to the system PATH. Check the official Bun documentation for the latest status and instructions for native Windows installation.
  • Homebrew (macOS): If you use Homebrew on macOS, you can install Bun via its tap:
brew tap oven-sh/bun
brew install bun

Verification:

Once installed, open a new terminal window and verify the installation by checking the version:

bun --version

This should output the installed version number, confirming that Bun is ready to use. You can also run bun --help to see a list of available commands and options.

Run Your Bun for the First Time

Let's dive into writing and running a simple program with Bun. One of the most common tasks is creating an HTTP server. Bun provides a built-in, highly optimized API for this purpose: Bun.serve.

Create a new file named server.js (or server.ts, as Bun handles both):

// server.ts

// Bun.serve starts the HTTP server
const server = Bun.serve({
  // Specify the port to listen on.
  // Defaults to process.env.PORT || 3000
  port: 3000,

  // The 'fetch' function is the core request handler.
  // It receives a standard Request object and must return a Response object (or a Promise resolving to one).
  fetch(request: Request): Response {
    // Create a standard Web API Response object
    return new Response("Welcome to Bun!");
  },
});

// Log a message indicating the server is running
console.log(`Listening on http://localhost:${server.port}`);

This code snippet does the following:

  1. It calls Bun.serve, the primary function for creating HTTP servers in Bun.
  2. It passes a configuration object, specifying the port (3000 in this case).
  3. The crucial part is the fetch function. This function is invoked for every incoming HTTP request. It aligns with the Service Worker fetch event handler pattern, accepting a standard Request object.
  4. Inside fetch, we construct and return a standard Response object. Here, we simply return plain text "Welcome to Bun!".
  5. Finally, we log a confirmation message to the console, including the actual port the server is listening on (accessible via server.port).

To run this server, open your terminal in the directory where you saved the file and execute:

bun run server.ts

Or, if you saved it as server.js:

bun run server.js

Bun will execute the script. If you used TypeScript (server.ts), Bun's internal transpiler will handle the conversion to JavaScript on the fly before execution. You'll see the "Listening on http://localhost:3000" message.

Now, open your web browser and navigate to http://localhost:3000. You should see the text "Welcome to Bun!" displayed.

To stop the server, go back to your terminal and press Ctrl + C.

This simple example demonstrates the ease of setting up a basic server and running code (including TypeScript) directly with Bun, showcasing its integrated nature and reliance on Web Standard APIs like Request and Response.

What's the Native TypeScript Support in Bun

One of Bun's most significant advantages, especially for developers already using or wanting to adopt TypeScript, is its first-class, out-of-the-box support. Unlike Node.js, where running TypeScript typically requires pre-compilation using the TypeScript compiler (tsc) or using loaders/registers like ts-node or tsx, Bun handles it natively and transparently.

How it Works:

When you ask Bun to run a .ts or .tsx file (e.g., bun run myscript.ts), it automatically invokes its internal transpiler. This transpiler is written in native code (Zig) and is extremely fast. Its job is to:

  1. Strip Types: Remove TypeScript type annotations, interfaces, enums, etc., as these are not part of standard JavaScript execution.
  2. Transform Syntax: Convert TypeScript-specific syntax (like certain enum usages or older decorator syntax if configured) into equivalent JavaScript.
  3. Handle JSX: Transform JSX syntax (used in .tsx and .jsx files) into standard JavaScript function calls (typically React.createElement or a configured JSX runtime equivalent).

The key benefit is that this happens on-the-fly during the execution (bun run) or bundling (bun build) process. There's no separate, explicit build step required just to run your TypeScript code during development.

Example:

Consider this TypeScript file (greet.ts):

// greet.ts
interface User {
  name: string;
  id: number;
}

function greetUser(user: User): void {
  console.log(`Hello, ${user.name} (ID: ${user.id})!`);
}

const myUser: User = { name: "Bun Developer", id: 123 };

greetUser(myUser);

// You can even use modern features Bun supports
const message = `Bun version: ${Bun.version}`;
console.log(message);

You can run this directly:

bun run greet.ts

Bun will transpile it internally and execute the resulting JavaScript, producing output like:

Hello, Bun Developer (ID: 123)!
Bun version: 1.x.y

JSX Support:

Similarly, if you have a .tsx file with JSX:

// component.tsx

// Assuming you have JSX configured (Bun defaults often work with React)
function MyComponent({ name }: { name: string }) {
  return <div className="greeting">Hello, {name}!</div>;
}

// NOTE: Running this directly won't render HTML,
// it just shows the transpiled JS structure.
// You'd typically use this within a larger app or framework.
console.log("Simulating component creation (transpiled output structure):");
// The actual output depends on JSX transform settings,
// but it would be JavaScript objects/function calls.

Running bun run component.tsx would execute the file, transpiling the JSX into JavaScript.

Configuration (tsconfig.json):

Bun respects tsconfig.json files for configuration options that affect transpilation. While it doesn't perform full type-checking like tsc (Bun focuses on execution and transpilation speed), it reads tsconfig.json to understand settings like:

  • jsx: ("react-jsx", "react", etc.) How JSX should be transformed.
  • jsxImportSource: The module to import JSX helper functions from (e.g., "react").
  • experimentalDecorators, emitDecoratorMetadata: Support for decorators.
  • paths, baseUrl: Module path mapping for custom import aliases.
  • target, module: While Bun manages execution, these can sometimes influence minor transpilation details.
  • strict, strictNullChecks, etc.: These primarily affect type-checking (which Bun doesn't do during run), but some related JavaScript emit behavior might be influenced.

If no tsconfig.json is found, Bun uses sensible default settings.

This seamless integration makes starting TypeScript projects with Bun incredibly simple and fast, lowering the barrier to entry and accelerating development cycles.

Let's Talk About Bun Specific APIs

While Bun heavily emphasizes compatibility with Web Standard APIs and Node.js, it also introduces its own set of optimized, built-in APIs under the global Bun object. These APIs often provide high-performance alternatives or convenient wrappers for common tasks that leverage Bun's native code capabilities.

Here's a glimpse into some key Bun.* APIs:

  • Bun.serve({...}): As seen in the Quickstart, this is the cornerstone for building highly performant HTTP and WebSocket servers. It offers a streamlined configuration and uses the standard fetch handler signature. (Covered in detail later).
  • Bun.file(path): Creates a BunFile object, which is a lazy reference to a file on disk. It provides highly optimized methods for reading file contents in various formats (.text(), .json(), .arrayBuffer(), .stream()) only when needed. This is often much faster than node:fs equivalents.
  • Bun.write(path, data): The counterpart to Bun.file, used for writing data to files efficiently. It accepts various data types (strings, Blobs, Buffers, other BunFiles) and performs atomic writes by default.
  • Bun.build({...}): Provides programmatic access to Bun's built-in bundler, which is compatible with the esbuild API. Allows bundling JavaScript/TypeScript for browsers or other runtimes directly from within Bun scripts.
  • Bun.spawn({...}) / Bun.spawnSync({...}): Run external commands as child processes, similar to Node.js's child_process. Offers asynchronous streaming APIs and a simpler synchronous version, optimized for low overhead.
  • Bun.Transpiler({...}): Direct programmatic access to Bun's fast internal transpiler for converting TypeScript/JSX to JavaScript without full bundling.
  • Bun.password.hash(...) / Bun.password.verify(...): Secure and easy-to-use functions for hashing and verifying passwords using industry-standard algorithms like Argon2id (recommended) and bcrypt. Avoids the need for external libraries.
  • Bun.env: An object providing access to environment variables, similar to process.env, but potentially offering faster access in some scenarios.
  • Bun.version: A string containing the currently running Bun version.
  • Bun.revision: A string containing the Git commit hash of the currently running Bun build.
  • Bun.sleep(ms) / Bun.sleepSync(ms): Functions to pause execution for a specified duration.
  • Bun.gc(): Manually trigger garbage collection (use sparingly, mainly for debugging/benchmarking).
  • Bun.resolveSync(specifier, parentPath) / Bun.resolve(specifier, parentPath): Programmatically perform Node.js-style module resolution to find the absolute path of a module.

These APIs represent Bun's effort to provide optimized, built-in solutions for common development tasks, reducing reliance on external dependencies and leveraging the speed of its native core.

Web APIs in Bun

A fundamental design choice in Bun is its strong commitment to implementing standard Web APIs. Wherever a standard API exists for a particular task (especially for networking and data handling), Bun prefers to implement that standard rather than inventing a proprietary API or solely relying on Node.js conventions.

This approach offers several significant advantages:

  1. Code Portability: Code written using standard Web APIs can often be reused across different JavaScript environments – the browser, Node.js (which is also increasingly adopting Web standards), Deno, Cloudflare Workers, and Bun – with fewer modifications.
  2. Familiarity: Developers already familiar with browser APIs can leverage that knowledge when working with Bun, reducing the learning curve.
  3. Future-Proofing: Aligning with standards established by bodies like WHATWG and W3C generally leads to more stable and widely supported APIs in the long run.
  4. Performance: Bun's native implementations of these Web APIs are highly optimized for its runtime.

Key Web Standard APIs implemented in Bun include:

Fetch API:

  • fetch(): The global function for making HTTP(S) requests.
  • Request: Represents an HTTP request.
  • Response: Represents an HTTP response.
  • Headers: Represents HTTP headers.

URL API:

  • URL: For parsing and manipulating URLs.
  • URLSearchParams: For working with URL query parameters.

Streams API:

  • ReadableStream: Represents a source of data that can be read asynchronously. Used for request/response bodies, file reading, etc.
  • WritableStream: Represents a destination for data that can be written asynchronously. Used for request bodies, file writing, etc.
  • TransformStream: A duplex stream that transforms data as it passes through (e.g., compression, encoding).

Encoding API:

  • TextEncoder: Encodes strings into Uint8Array (typically UTF-8).
  • TextDecoder: Decodes Uint8Array into strings.

Blob API:

  • Blob: Represents immutable raw data, often used for file-like objects.
  • File: Extends Blob to represent files, including metadata like name and last modified date. (Often created via Bun.file().slice() or from form data).

FormData API:

  • FormData: For building sets of key/value pairs, often used for submitting form data in fetch requests.

WebSocket API:

  • WebSocket: The client-side API for establishing WebSocket connections. (Server-side handling is integrated into Bun.serve).

Timers:

  • setTimeout, setInterval, clearTimeout, clearInterval: Standard functions for scheduling code execution.

Console API:

  • console.log, console.error, console.warn, etc.: Standard logging functions.

Crypto API:

  • crypto.subtle: Provides access to low-level cryptographic primitives (hashing, signing, encryption).
  • crypto.randomUUID(): Generates v4 UUIDs.
  • crypto.getRandomValues(): Generates cryptographically strong random numbers.

Performance API:

  • performance.now(): Provides high-resolution timestamps for performance measurements.

By providing robust, performant implementations of these essential Web APIs, Bun positions itself as a modern runtime well-suited for building web servers, APIs, and other network-centric applications using familiar, standardized interfaces.

Bun HTTP Server, Explained

The primary way to create web servers in Bun is through the Bun.serve API. It's designed for exceptional performance and ease of use, integrating seamlessly with standard Web APIs like Request, Response, and fetch.

Core Concepts:

The Bun.serve function takes a configuration object and returns a Server object. The most critical part of the configuration is the fetch function.

import { type Server } from "bun";

const server: Server = Bun.serve({
  port: 8080, // The port to listen on
  hostname: "0.0.0.0", // The network interface to bind to (0.0.0.0 for all)

  // fetch: The heart of the server - handles incoming requests
  async fetch(req: Request, server: Server): Promise<Response> {
    // req is a standard Web API Request object
    // server is a reference to the Server instance itself

    const url = new URL(req.url);

    // Basic Routing Example
    if (url.pathname === "/") {
      return new Response("Homepage");
    }
    if (url.pathname === "/about") {
      return new Response("About Us page");
    }
    if (url.pathname === "/greet" && req.method === "GET") {
        const name = url.searchParams.get("name") || "World";
        return new Response(`Hello, ${name}!`);
    }
     if (url.pathname === "/data" && req.method === "POST") {
        try {
            const data = await req.json(); // Read request body as JSON
            console.log("Received data:", data);
            return new Response(JSON.stringify({ received: data }), {
               headers: { 'Content-Type': 'application/json' }
            });
        } catch (e) {
            return new Response("Invalid JSON body", { status: 400 });
        }
    }

    // Default 404 Not Found
    return new Response("Page Not Found", { status: 404 });
  },

  // error: Optional handler for errors occurring *outside* the fetch handler
  error(error: Error): Response | Promise<Response> {
    console.error("[Server Error]", error);
    return new Response("Something went wrong!", { status: 500 });
  },

  // development: Set to true for helpful development error pages (default: !process.env.NODE_ENV=production)
   development: true,

   // Other options like 'websocket', 'tls' exist for advanced use cases
});

console.log(`Bun server listening on http://${server.hostname}:${server.port}`);

// You can interact with the server object:
// server.stop() // Stops the server
// server.reload({...}) // Updates server configuration (e.g., fetch handler) dynamically

Key Features:

  • Performance: Bun.serve is built on Bun's custom, highly optimized HTTP server implementation written in Zig. It's capable of handling a very high number of requests per second with low latency and resource consumption compared to many Node.js frameworks.
  • fetch Handler: Using the standard (Request) => Response | Promise<Response> signature makes the core logic familiar to anyone who has worked with Service Workers, Cloudflare Workers, or other modern web frameworks. It encourages the use of standard Request and Response objects.
  • Request Object: The req parameter provides access to standard Request properties and methods: req.url, req.method, req.headers, req.json(), req.text(), req.arrayBuffer(), req.formData(), req.body (a ReadableStream).
  • Response Object: You create and return standard Response objects, allowing you to set the body (string, Buffer, Blob, Stream, etc.), status code, and headers easily.
  • Error Handling: The optional error function provides a centralized place to catch errors that occur before or during the initial processing of a request by the server itself (e.g., failed TLS handshake, malformed request), or errors thrown synchronously outside the fetch handler's try...catch. Errors within the async fetch handler should typically be caught there.
  • Streaming: Both request and response bodies can be streamed using the standard ReadableStream and WritableStream APIs, essential for handling large uploads or downloads efficiently.
  • Dynamic Reloading: The server.reload() method allows updating server options, including the fetch and error handlers, without needing a full server restart, which is useful for hot module replacement (HMR) setups.

Bun.serve provides a powerful yet simple foundation for building web applications and APIs in Bun, prioritizing speed and adherence to web standards.

Bun Fetch Client

Complementing the server API, Bun provides a global fetch function for making outbound HTTP(S) requests. This implementation adheres closely to the WHATWG Fetch standard, making it familiar to web developers and ensuring consistency with the fetch function used within Bun.serve. Bun's native implementation ensures high performance for client-side networking tasks.

Making Requests:

The basic usage involves calling fetch with a URL and optionally a configuration object:

async function makeRequests() {
  const url = "https://httpbin.org"; // A useful service for testing HTTP requests

  // --- Basic GET request ---
  console.log("--- GET Request ---");
  try {
    const getResponse = await fetch(`${url}/get?param1=value1`);

    console.log(`Status: ${getResponse.status} ${getResponse.statusText}`);

    // Check if the request was successful (status 200-299)
    if (!getResponse.ok) {
      throw new Error(`HTTP error! status: ${getResponse.status}`);
    }

    // Access headers
    console.log("Content-Type Header:", getResponse.headers.get('content-type'));

    // Read the response body as JSON
    const getData = await getResponse.json();
    console.log("Response JSON:", getData.args); // httpbin.org/get returns query params in 'args'

  } catch (error) {
    console.error("GET request failed:", error);
  }

  // --- POST request with JSON body ---
  console.log("\n--- POST Request (JSON) ---");
  try {
     const postData = { name: "Bun", type: "Runtime" };
     const postResponse = await fetch(`${url}/post`, {
        method: "POST",
        headers: {
          // Indicate we're sending JSON
          "Content-Type": "application/json",
          "Accept": "application/json", // Indicate we prefer JSON back
          "X-Custom-Header": "BunFetchExample",
        },
        // Body must be stringified for JSON
        body: JSON.stringify(postData),
     });

     if (!postResponse.ok) {
        throw new Error(`HTTP error! status: ${postResponse.status}`);
     }

     const postResult = await postResponse.json();
     console.log("POST Response JSON:", postResult.json); // httpbin.org/post returns posted JSON in 'json'

  } catch (error) {
     console.error("POST request failed:", error);
  }

   // --- Request with Timeout ---
   console.log("\n--- GET Request with Timeout ---");
   try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 2000); // Abort after 2 seconds

      const timeoutResponse = await fetch(`${url}/delay/5`, { // This endpoint waits 5 seconds
         signal: controller.signal // Pass the AbortSignal
      });

      clearTimeout(timeoutId); // Clear timeout if fetch completes faster
      console.log("Timeout request succeeded (unexpected for /delay/5)");

   } catch (error: any) {
      // AbortError is thrown when the signal aborts
      if (error.name === 'AbortError') {
         console.log("Fetch aborted due to timeout, as expected.");
      } else {
         console.error("Timeout request failed:", error);
      }
   }
}

await makeRequests();

Key Features and Options:

Standard API: Uses the familiar fetch(url, options) signature.

Methods: Supports all standard HTTP methods (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS). Specified via the method option.

Headers: Headers are set using the headers option, which can be a Headers object or a simple key-value plain object.

Request Body: The body option accepts various types:

  • string: Sent as text (common for JSON, XML, plain text).
  • ArrayBuffer / TypedArray: Sent as binary data.
  • Blob / File: Sent as binary data, often with appropriate Content-Type.
  • ReadableStream: Streams the body content, suitable for large uploads.
  • URLSearchParams: Automatically sets Content-Type to application/x-www-form-urlencoded.
  • FormData: Automatically sets Content-Type to multipart/form-data.

Response Handling: The fetch call returns a Promise that resolves to a Response object.

  • response.ok: Boolean indicating a successful HTTP status (200-299).
  • response.status: The HTTP status code (e.g., 200, 404).
  • response.statusText: The HTTP status message (e.g., "OK", "Not Found").
  • response.headers: A Headers object to access response headers.
  • Body Reading Methods: response.json(), response.text(), response.arrayBuffer(), response.blob(), response.formData(). These return Promises.
  • response.body: A ReadableStream for streaming the response body.

Timeouts & Cancellation: Use AbortController and AbortSignal to implement timeouts or manually cancel requests. Pass the signal to the fetch options.

Redirects: Controlled by the redirect option ('follow', 'manual', 'error'). Defaults to 'follow'.

  • aching: Controlled by the cache option (e.g., 'no-store', 'reload').

Bun's fetch provides a performant and standards-compliant way to interact with HTTP resources, integrating seamlessly into the Bun ecosystem.

Bun WebSockets

WebSockets provide a way to establish persistent, bidirectional communication channels between a client and a server over a single TCP connection. Bun offers excellent support for WebSockets, both for creating WebSocket clients and for handling WebSocket connections on the server-side within Bun.serve.

1. WebSocket Client:

Bun implements the standard browser WebSocket API for creating client connections.

// websocket-client.ts

const wsUrl = "wss://ws.postman-echo.com/raw"; // Public echo server

console.log(`Connecting client to ${wsUrl}...`);

const socket = new WebSocket(wsUrl);

// Event: Connection successfully opened
socket.addEventListener("open", (event) => {
  console.log("[Client] WebSocket connection established!");
  socket.send("Hello from Bun client!");

  // Send binary data after a short delay
  setTimeout(() => {
     const binaryData = new TextEncoder().encode("Bun binary data");
     console.log("[Client] Sending binary:", binaryData);
     socket.send(binaryData);
  }, 500);

   // Close the connection after some time
  setTimeout(() => {
     console.log("[Client] Closing connection...");
     socket.close(1000, "Client done testing"); // 1000 = Normal closure
  }, 2000);
});

// Event: Message received from server
socket.addEventListener("message", async (event) => {
  // event.data can be string, Blob, or ArrayBuffer
  let messageContent: string | Uint8Array;
  if (typeof event.data === "string") {
    messageContent = event.data;
    console.log(`[Client] Received string: "${messageContent}"`);
  } else if (event.data instanceof Blob) {
    // Bun often receives binary data as Blobs from the WebSocket API
    const arrayBuffer = await event.data.arrayBuffer();
    messageContent = new Uint8Array(arrayBuffer);
    console.log(`[Client] Received binary (Blob):`, messageContent, `(${new TextDecoder().decode(messageContent)})`);
  } else if (event.data instanceof ArrayBuffer) {
     messageContent = new Uint8Array(event.data);
     console.log(`[Client] Received binary (ArrayBuffer):`, messageContent, `(${new TextDecoder().decode(messageContent)})`);
  } else {
    console.log("[Client] Received unknown message type:", event.data);
  }
});

// Event: An error occurred (network issue, etc.)
socket.addEventListener("error", (event) => {
  // The event object often lacks specific details. Check console/network logs.
  console.error("[Client] WebSocket error:", event.type);
});

// Event: Connection closed
socket.addEventListener("close", (event) => {
  console.log(
    `[Client] WebSocket closed. Code: ${event.code}, Reason: "${event.reason}", Clean: ${event.wasClean}`
  );
  // Exit the script cleanly once the socket is closed
  process.exit(0);
});

// Keep alive briefly for events, exit handled in 'close' listener
// setInterval(() => {}, 1000);

Run this with bun run websocket-client.ts. It connects to the echo server, sends messages, receives them back, and then closes.

2. WebSocket Server (Bun.serve Integration):

Handling WebSocket connections on the server is done by adding a websocket property to the Bun.serve configuration object. This property contains handler functions for different WebSocket lifecycle events.

// websocket-server.ts
import { type ServerWebSocket, type WebSocketHandler } from "bun";

console.log("Starting WebSocket server...");

// Define the WebSocket handler object
const wsHandler: WebSocketHandler<{ authToken: string }> = {
  // open(ws): Called when a new WebSocket connection is established.
  // The server.upgrade() call in 'fetch' is necessary first.
  open(ws: ServerWebSocket<{ authToken: string }>) {
    console.log(`[Server] Connection opened. Token: ${ws.data.authToken}`);
    ws.send("Welcome to the Bun WebSocket server!");
    // Subscribe to a pub/sub topic
    ws.subscribe("the-group-chat");
    // Publish a message to the topic (including the new user)
    ws.publish("the-group-chat", `User with token ${ws.data.authToken} joined.`);
  },

  // message(ws, message): Called when a message is received from a client.
  // 'message' can be a string or a Uint8Array (Buffer).
  message(ws: ServerWebSocket<{ authToken: string }>, message: string | Buffer) {
    const messageString = message.toString(); // Handle both string/buffer
    console.log(`[Server] Received from token ${ws.data.authToken}: ${messageString}`);

     // Echo back the message prepended with the token
    // ws.send(`[${ws.data.authToken}] You sent: ${messageString}`);

    // Publish the message to everyone in the chat room (including sender)
    server.publish("the-group-chat", `[${ws.data.authToken}] ${messageString}`);
  },

  // close(ws, code, reason): Called when a WebSocket connection is closed.
  close(ws: ServerWebSocket<{ authToken: string }>, code: number, reason: string) {
    const goodbyeMessage = `[Server] Connection closed. Token: ${ws.data.authToken}, Code: ${code}, Reason: ${reason}`;
    console.log(goodbyeMessage);
     // Unsubscribe and notify others
     ws.unsubscribe("the-group-chat");
     server.publish("the-group-chat", `User with token ${ws.data.authToken} left.`);
  },

  // drain(ws): Optional. Called when the socket's buffered amount decreases,
  // indicating it's ready to receive more data after backpressure.
  // Useful for managing flow control when sending large amounts of data.
  // drain(ws: ServerWebSocket<{ authToken: string }>) {
  //   console.log(`[Server] Drain event for token ${ws.data.authToken}`);
  // },
};

// Create the HTTP server with WebSocket handling
const server = Bun.serve<{ authToken: string }>({ // Pass generic for ws.data type
  port: 8081,

  // The fetch handler needs to handle the HTTP -> WebSocket upgrade request
  fetch(req: Request, server: Server): Response | undefined {
    const url = new URL(req.url);
    // Check if the request is trying to upgrade to WebSocket
    if (url.pathname === "/ws") {
       // Extract some data (e.g., auth token) to pass to WebSocket context
       const token = req.headers.get("sec-websocket-protocol") || "anonymous"; // Example: use protocol header for token
       const success = server.upgrade(req, {
         // The data object is attached to the ws instance (ws.data)
         // available in all websocket handlers for this connection.
         // MUST be JSON-serializable if using across workers/processes later.
         data: {
           authToken: token,
         },
         // headers: new Headers({ 'X-Custom-Response-Header': 'UpgradeValue' }) // Optional: Add headers to the 101 response
      });

      if (success) {
        // server.upgrade() handles the 101 Switching Protocols response.
        // Return nothing from fetch handler after successful upgrade.
        return undefined;
      } else {
         // Upgrade failed (e.g., invalid request headers)
         return new Response("WebSocket upgrade failed", { status: 400 });
      }
    }

    // Handle regular HTTP requests on other paths
    return new Response("Not a WebSocket endpoint. Try /ws", { status: 404 });
  },

  // Attach the WebSocket handler object
  websocket: wsHandler,

  error(error: Error): Response | Promise<Response> {
    console.error("[Server Error]", error);
    return new Response("Server error", { status: 500 });
  },
});

console.log(`Bun WebSocket server listening on ws://localhost:${server.port}/ws`);

Server Key Concepts:

Upgrade Request: WebSocket connections start as standard HTTP GET requests with specific headers (Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Key, etc.). The fetch handler must detect these requests.

  • server.upgrade(req, { data: ... }): This crucial method, called within the fetch handler, attempts to complete the WebSocket handshake. If successful, it returns true, and Bun takes over the connection using the websocket handlers. If it fails, it returns false. You must return undefined from fetch after a successful upgrade.
  • websocket Handler Object: Contains the open, message, close, and drain functions to manage the WebSocket lifecycle for connected clients.
  • ServerWebSocket<T>: The type representing a server-side WebSocket connection. The generic T defines the type of the ws.data object.
  • ws.data: An object associated with each connection, initialized by the data property in the server.upgrade options. Useful for storing connection-specific state (like user IDs, auth tokens).

Pub/Sub: Bun includes built-in, efficient publish/subscribe capabilities for WebSockets:

  • ws.subscribe("topic-name"): Subscribes a connection to a topic.
  • ws.unsubscribe("topic-name"): Unsubscribes a connection.
  • ws.publish("topic-name", message): Sends a message to all connections subscribed to that topic except the sender ws.
  • server.publish("topic-name", message): Sends a message to all connections subscribed to the topic (useful for system-wide broadcasts).

Backpressure: The ws.send() method returns the number of bytes buffered. If this value grows large, you might be sending faster than the client can receive. The drain event signals when the buffer has shrunk, allowing you to resume sending safely.

Bun's integrated WebSocket support provides a performant and convenient way to build real-time features into applications, complete with built-in pub/sub functionality.


This guide has covered the fundamental aspects of Bun, from its core philosophy and installation to its specific APIs like Bun.serve, Bun.file, Bun.build, and its implementations of crucial Web Standards like fetch and WebSocket. By combining speed, integrated tooling, native TypeScript/JSX support, and a focus on standards, Bun offers a compelling and productive environment for modern JavaScript and TypeScript development.

💡
Want a great API Testing tool that generates beautiful API Documentation?

Want an integrated, All-in-One platform for your Developer Team to work together with maximum productivity?

Apidog delivers all your demans, and replaces Postman at a much more affordable price!
button
How to Run Phi-4 Reasoning (with Free API, Locally with Ollama)Viewpoint

How to Run Phi-4 Reasoning (with Free API, Locally with Ollama)

The field of Artificial Intelligence is rapidly evolving, with large language models (LLMs) often taking center stage. However, a parallel revolution is happening in the realm of Small Language Models (SLMs). Microsoft Research has been a key player in this space, notably with their Phi series. Building on the success of models like Phi-3, Microsoft recently unveiled two new powerhouses: Phi-4-reasoning and Phi-4-reasoning-plus. These models represent a significant leap forward, demonstrating th

Emmanuel Mumba

May 2, 2025

How to Activate Python venv (Beginner's Guide)Viewpoint

How to Activate Python venv (Beginner's Guide)

In the dynamic world of Python development, managing dependencies and project environments is crucial for sanity and success. Imagine working on two different projects: one requires an older version of a popular library like requests, while the other needs the very latest features. Installing both system-wide would inevitably lead to conflicts, breakage, and frustration. This is precisely the problem Python virtual environments are designed to solve. This tutorial will guide you through the fun

Stefania Boiko

May 2, 2025

Qwen2.5-Omni-7B: Small But MightyViewpoint

Qwen2.5-Omni-7B: Small But Mighty

The field of artificial intelligence is rapidly evolving, pushing the boundaries of what machines can perceive, understand, and generate. A significant leap in this evolution is marked by the introduction of the Qwen2.5-Omni-7B model, a flagship end-to-end multimodal model developed by the Qwen team. This model represents a paradigm shift, moving beyond text-centric interactions to embrace a truly omni-modal experience. It seamlessly processes a diverse array of inputs – text, images, audio, and