> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/oven-sh/bun/llms.txt
> Use this file to discover all available pages before exploring further.

# TCP & UDP

> Low-level TCP and UDP networking with Bun.listen(), Bun.connect(), and Bun.udpSocket() for building database clients, game servers, and real-time applications.

Bun provides low-level TCP and UDP APIs for performance-sensitive networking where HTTP overhead is undesirable — think database clients, game servers, or custom protocols.

<Note>
  These are low-level APIs intended for library authors and advanced use cases. For most applications, prefer `Bun.serve()` (HTTP) or `fetch()`.
</Note>

***

## TCP server with `Bun.listen()`

```typescript theme={null}
Bun.listen({
  hostname: "localhost",
  port: 8080,
  socket: {
    open(socket) {
      console.log("Client connected");
      socket.write("Welcome!\n");
    },
    data(socket, data) {
      console.log("Received:", data.toString());
    },
    close(socket) {
      console.log("Client disconnected");
    },
    drain(socket) {
      // Called when the socket is ready to receive more data after backpressure
    },
    error(socket, error) {
      console.error("Socket error:", error);
    },
  },
});
```

All event handlers are declared once per server and shared across all connections — this avoids garbage-collector pressure from per-socket closures and keeps memory usage flat.

### Attaching data to a socket

Attach contextual data to each socket in the `open` handler. Access it via `socket.data` in any handler.

```typescript theme={null}
type SocketData = {
  sessionId: string;
  connectedAt: number;
};

Bun.listen<SocketData>({
  hostname: "localhost",
  port: 8080,
  socket: {
    open(socket) {
      socket.data = {
        sessionId: crypto.randomUUID(),
        connectedAt: Date.now(),
      };
    },
    data(socket, data) {
      socket.write(`[${socket.data.sessionId}] ack: ${data}`);
    },
  },
});
```

### Stopping a TCP server

```typescript theme={null}
const server = Bun.listen({
  hostname: "localhost",
  port: 8080,
  socket: {
    data(socket, data) { socket.write(data); },
  },
});

// Stop accepting new connections; close active ones immediately
server.stop(true);

// Allow process to exit even while server is still listening
server.unref();
```

***

## TCP echo server (complete example)

<Steps>
  <Step title="Create the server">
    ```typescript server.ts theme={null}
    const server = Bun.listen({
      hostname: "localhost",
      port: 9000,
      socket: {
        open(socket) {
          console.log(`[server] client connected: ${socket.remoteAddress}`);
        },
        data(socket, data) {
          console.log(`[server] received: ${data}`);
          socket.write(data); // echo back
        },
        close(socket) {
          console.log("[server] client disconnected");
        },
        error(socket, error) {
          console.error("[server] error:", error.message);
        },
      },
    });

    console.log(`TCP echo server listening on port ${server.port}`);
    ```
  </Step>

  <Step title="Create the client">
    ```typescript client.ts theme={null}
    const socket = await Bun.connect({
      hostname: "localhost",
      port: 9000,
      socket: {
        open(socket) {
          socket.write("Hello, TCP!");
        },
        data(socket, data) {
          console.log(`[client] echo: ${data}`);
          socket.end();
        },
        close(socket) {
          console.log("[client] connection closed");
        },
        error(socket, error) {
          console.error("[client] error:", error.message);
        },
      },
    });
    ```
  </Step>
</Steps>

***

## TCP client with `Bun.connect()`

`Bun.connect()` supports the same socket handlers as `Bun.listen()` plus three client-specific ones:

```typescript theme={null}
const socket = await Bun.connect({
  hostname: "database.internal",
  port: 5432,
  socket: {
    open(socket) {
      console.log("Connected to DB");
    },
    data(socket, data) {
      console.log("Response:", data.toString("hex"));
    },
    close(socket) {},
    drain(socket) {},
    error(socket, error) {},

    // Client-only handlers
    connectError(socket, error) {
      console.error("Connection failed:", error.message);
    },
    end(socket) {
      console.log("Server closed the connection");
    },
    timeout(socket) {
      console.log("Connection timed out");
    },
  },
});

// Send data
socket.write(Buffer.from([0x00, 0x01, 0x02]));
```

***

## TLS over TCP

### TLS server

```typescript theme={null}
Bun.listen({
  hostname: "localhost",
  port: 8443,
  socket: {
    data(socket, data) {
      socket.write(data); // echo
    },
  },
  tls: {
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
  },
});
```

`key` and `cert` accept a string, `BunFile`, `Buffer`, `TypedArray`, or an array of any of those.

### TLS client

```typescript theme={null}
const socket = await Bun.connect({
  hostname: "secure.example.com",
  port: 443,
  tls: true, // use TLS with default settings
  socket: {
    data(socket, data) {
      console.log(data.toString());
    },
  },
});
```

***

## Hot reloading

Both servers and individual sockets support hot-swapping their handlers at runtime without dropping connections:

<CodeGroup>
  ```typescript Server hot reload theme={null}
  const server = Bun.listen({
    hostname: "localhost",
    port: 8080,
    socket: {
      data(socket, data) {
        socket.write("v1 response");
      },
    },
  });

  server.reload({
    socket: {
      data(socket, data) {
        socket.write("v2 response");
      },
    },
  });
  ```

  ```typescript Socket hot reload theme={null}
  const socket = await Bun.connect({
    hostname: "localhost",
    port: 8080,
    socket: {
      data(socket, data) {
        console.log("v1:", data.toString());
      },
    },
  });

  socket.reload({
    data(socket, data) {
      console.log("v2:", data.toString());
    },
  });
  ```
</CodeGroup>

***

## Buffering TCP writes

TCP sockets in Bun do not automatically buffer writes. Many small `socket.write()` calls perform significantly worse than a single larger one.

Use `ArrayBufferSink` to accumulate data before flushing:

```typescript theme={null}
import { ArrayBufferSink } from "bun";

const sink = new ArrayBufferSink();
sink.start({
  stream: true,
  highWaterMark: 1024,
});

sink.write("h");
sink.write("e");
sink.write("l");
sink.write("l");
sink.write("o");

queueMicrotask(() => {
  const data = sink.flush();
  const wrote = socket.write(data);

  if (wrote < data.byteLength) {
    // Re-queue the unsent bytes; drain() will be called when ready
    sink.write(data.subarray(wrote));
  }
});
```

***

## UDP with `Bun.udpSocket()`

UDP is connectionless and lower latency than TCP. It is suited for real-time applications like voice chat, gaming, or DNS clients where occasional packet loss is acceptable.

### Create a UDP socket

```typescript theme={null}
// Port assigned by the OS
const socket = await Bun.udpSocket({});
console.log(socket.port);

// Specific port
const socket2 = await Bun.udpSocket({ port: 41234 });
```

### Send a datagram

```typescript theme={null}
// send(data, port, address)
socket.send("Hello, world!", 41234, "127.0.0.1");
```

<Note>
  `send()` requires a resolved IP address. It does not perform DNS lookups in order to keep latency as low as possible.
</Note>

### Receive datagrams

```typescript theme={null}
const server = await Bun.udpSocket({
  socket: {
    data(socket, buf, port, addr) {
      console.log(`Message from ${addr}:${port}: ${buf.toString()}`);
      socket.send("Pong!", port, addr);
    },
  },
});

const client = await Bun.udpSocket({});
client.send("Ping!", server.port, "127.0.0.1");
```

### Connected UDP socket

Connecting a UDP socket binds it to a single peer — all sends go to that peer and only packets from that peer are received. This can also improve performance on some operating systems.

```typescript theme={null}
const server = await Bun.udpSocket({
  socket: {
    data(socket, buf, port, addr) {
      console.log(`${addr}:${port}: ${buf}`);
    },
  },
});

const client = await Bun.udpSocket({
  connect: {
    port: server.port,
    hostname: "127.0.0.1",
  },
});

// No need to specify destination — it was set in `connect`
client.send("Hello from connected socket");
```

### Batch sends with `sendMany()`

Amortize the system-call cost when sending many datagrams at once:

<CodeGroup>
  ```typescript Unconnected socket theme={null}
  const socket = await Bun.udpSocket({});

  // Each group of three elements: [data, port, address]
  socket.sendMany([
    "Hello", 41234, "127.0.0.1",
    "World", 41235, "127.0.0.1",
  ]);
  ```

  ```typescript Connected socket theme={null}
  const socket = await Bun.udpSocket({
    connect: { port: 41234, hostname: "127.0.0.1" },
  });

  // Each element is sent to the connected peer
  socket.sendMany(["foo", "bar", "baz"]);
  ```
</CodeGroup>

`sendMany()` returns the number of packets successfully sent.

### Backpressure

When the OS packet buffer is full, `send()` returns `false` and `sendMany()` returns fewer packets than requested. The `drain` handler fires when the socket is writable again:

```typescript theme={null}
const socket = await Bun.udpSocket({
  socket: {
    drain(socket) {
      // Resume sending
      socket.send("Resumed!", 41234, "127.0.0.1");
    },
  },
});
```

### Socket options

```typescript theme={null}
const socket = await Bun.udpSocket({});

// Allow sending to broadcast addresses
socket.setBroadcast(true);

// Set IP time-to-live for outgoing packets
socket.setTTL(64);
```

### Multicast

```typescript theme={null}
const socket = await Bun.udpSocket({});

// Join a multicast group
socket.addMembership("224.0.0.1");

// Join on a specific interface
socket.addMembership("224.0.0.1", "192.168.1.100");

// Set TTL for multicast packets (network hops)
socket.setMulticastTTL(4);

// Control loopback of multicast packets to local socket
socket.setMulticastLoopback(true);

// Specify outgoing multicast interface
socket.setMulticastInterface("192.168.1.100");

// Leave a group
socket.dropMembership("224.0.0.1");
```

Source-specific multicast (SSM):

```typescript theme={null}
socket.addSourceSpecificMembership("10.0.0.1", "232.0.0.1");
socket.dropSourceSpecificMembership("10.0.0.1", "232.0.0.1");
```
