Use Bun's native TCP API to implement performance sensitive systems like database clients, game servers, or anything that needs to communicate over TCP (instead of HTTP). This is a low-level API intended for library authors and for advanced use cases.
Start a server (Bun.listen()
)
To start a TCP server with Bun.listen
:
Bun.listen({
hostname: "localhost",
port: 8080,
socket: {
data(socket, data) {}, // message received from client
open(socket) {}, // socket opened
close(socket) {}, // socket closed
drain(socket) {}, // socket ready for more data
error(socket, error) {}, // error handler
},
});
An API designed for speed
Contextual data can be attached to a socket in the open
handler.
type SocketData = { sessionId: string };
Bun.listen<SocketData>({
hostname: "localhost",
port: 8080,
socket: {
data(socket, data) {
socket.write(`${socket.data.sessionId}: ack`);
},
open(socket) {
socket.data = { sessionId: "abcd" };
},
},
});
To enable TLS, pass a tls
object containing key
and cert
fields.
Bun.listen({
hostname: "localhost",
port: 8080,
socket: {
data(socket, data) {},
},
tls: {
// can be string, BunFile, TypedArray, Buffer, or array thereof
key: Bun.file("./key.pem"),
cert: Bun.file("./cert.pem"),
},
});
The key
and cert
fields expect the contents of your TLS key and certificate. This can be a string, BunFile
, TypedArray
, or Buffer
.
Bun.listen({
// ...
tls: {
// BunFile
key: Bun.file("./key.pem"),
// Buffer
key: fs.readFileSync("./key.pem"),
// string
key: fs.readFileSync("./key.pem", "utf8"),
// array of above
key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")],
},
});
The result of Bun.listen
is a server that conforms to the TCPSocket
interface.
const server = Bun.listen({
/* config*/
});
// stop listening
// parameter determines whether active connections are closed
server.stop(true);
// let Bun process exit even if server is still listening
server.unref();
Create a connection (Bun.connect()
)
Use Bun.connect
to connect to a TCP server. Specify the server to connect to with hostname
and port
. TCP clients can define the same set of handlers as Bun.listen
, plus a couple client-specific handlers.
// The client
const socket = await Bun.connect({
hostname: "localhost",
port: 8080,
socket: {
data(socket, data) {},
open(socket) {},
close(socket) {},
drain(socket) {},
error(socket, error) {},
// client-specific handlers
connectError(socket, error) {}, // connection failed
end(socket) {}, // connection closed by server
timeout(socket) {}, // connection timed out
},
});
To require TLS, specify tls: true
.
// The client
const socket = await Bun.connect({
// ... config
tls: true,
});
Hot reloading
Both TCP servers and sockets can be hot reloaded with new handlers.
const server = Bun.listen({ /* config */ })
// reloads handlers for all active server-side sockets
server.reload({
socket: {
data(){
// new 'data' handler
}
}
})
const socket = await Bun.connect({ /* config */ })
socket.reload({
data(){
// new 'data' handler
}
})
Buffering
Currently, TCP sockets in Bun do not buffer data. For performance-sensitive code, it's important to consider buffering carefully. For example, this:
socket.write("h");
socket.write("e");
socket.write("l");
socket.write("l");
socket.write("o");
...performs significantly worse than this:
socket.write("hello");
To simplify this for now, consider using Bun's ArrayBufferSink
with the {stream: true}
option:
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) {
// put it back in the sink if the socket is full
sink.write(data.subarray(wrote));
}
});
Corking — Support for corking is planned, but in the meantime backpressure must be managed manually with the drain
handler.