Workers get a native TCP socket API

Cloudflare has released connect(), a new API for creating outbound TCP sockets from Workers. The API targets the long-standing gap between Workers and the majority of databases and infrastructure that communicate over raw TCP rather than HTTP or WebSockets. It is available to all users today.

TCP underpins most database wire protocols, along with protocols such as SSH, MQTT, SMTP, FTP, and IRC. While Cloudflare's D1 and some managed database providers support HTTP or WebSocket connections from Workers, most relational and document databases require a direct, persistent TCP connection. The new API gives Workers a path to those services without an intermediary.

The pg driver for PostgreSQL is already working on Workers, with additional database driver support planned. Developers can connect to PostgreSQL from a Worker by following Cloudflare's guide, or start with the TCP Socket API documentation.

Socket basics

A TCP socket is the programming interface for a single two-way TCP connection. One application initiates an outbound connection to a peer that is listening for inbound connections. After the three-way handshake completes, data flows in both directions over the connection's readable and writable streams.

The API design challenge for Workers was that no shared standard for raw TCP sockets exists across runtimes. Node.js exposes the net and tls modules; Deno provides Deno.connect(); browsers have no raw socket API, although a WICG proposal exists. Cloudflare consulted maintainers of database drivers, ORMs, and other networking libraries, and intends to feed the design back into the WinterCG standardization effort.

The result is a single connect() function exported from the cloudflare:sockets module, returning a Socket instance. A simple example connects to a Gopher server, one of the early TCP-based internet protocols that remains operational:

import { connect } from 'cloudflare:sockets';

export default {
  async fetch(req: Request) {
    const gopherAddr = "gopher.floodgap.com:70";
    const url = new URL(req.url);

    try {
      const socket = connect(gopherAddr);

      const writer = socket.writable.getWriter()
      const encoder = new TextEncoder();
      const encoded = encoder.encode(url.pathname + "\r\n");
      await writer.write(encoded);

      return new Response(socket.readable, { headers: { "Content-Type": "text/plain" } });
    } catch (error) {
      return new Response("Socket connection failed: " + error, { status: 500 });
    }
  }
};

TLS upgrade in one API

Opportunistic TLS (StartTLS), where a connection starts in cleartext and is later upgraded, remains common in database protocols. Node.js handles this by requiring two separate APIs: net creates the initial socket, and tls produces a new upgraded connection. Deno's Deno.startTls() likewise creates a new connection object.

The Workers API instead exposes TLS as a property of the single socket object. TLS can be required, allowed, or disabled at creation time, and an existing socket can be upgraded by calling startTls():

// Create a new socket without TLS. secureTransport defaults to "off" if not specified.
const socket = connect("address:port", { secureTransport: "off" })

// Create a new socket, then upgrade it to use TLS.
// Once startTls() is called, only the newly created socket can be used.
const socket = connect("address:port", { secureTransport: "starttls" })
const secureSocket = socket.startTls();

// Create a new socket with TLS
const socket = connect("address:port", { secureTransport: "use" })

TLS configuration moves to the platform

Existing runtimes treat TLS as application-level code, with APIs like Node's tls.createSecureContext() exposing dozens of environment-specific options. Managing certificate file paths and .env credentials across development, staging, and production is a common source of friction.

Cloudflare's approach treats TLS configuration and credentials as host infrastructure concern, similar to its existing mTLS support for subrequests. Configuration is managed through Wrangler and exposed to the Worker via a capability binding. Custom TLS credentials are not yet supported but are planned.

Write before the handshake completes

Because connect() returns synchronously, a Worker can begin writing to the socket before the TCP handshake completes. As soon as the connection is established, queued data is ready to transmit, and the platform can use pipelining to reduce per-connection latency.

Drivers connect directly to databases

Serverless databases that speak HTTP or WebSockets already work with Workers. But most databases, including those hosted on mainstream cloud providers, expose proprietary wire protocols that require a TCP socket. Reliable Worker support for those databases depends on the open-source drivers that implement those protocols.

Cloudflare has worked with the maintainers of pg, the popular PostgreSQL driver used by ORMs including Sequelize and knex.js, to add connect() support. A minimal example installs pg, enables Node compatibility in Wrangler, and opens a connection:

wrangler init
npm install --save pg

The node_compat option must be enabled in wrangler.toml:

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2023-05-15"
node_compat = true

With roughly 20 lines of TypeScript, a Worker can connect to PostgreSQL, run a query, and return the results in the HTTP response:

import { Client } from "pg";

export interface Env {
  DB: string;
}

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext
  ): Promise<Response> {
    const client = new Client(env.DB);
    await client.connect();
    const result = await client.query({
      text: "SELECT * from customers",
    });
    console.log(JSON.stringify(result.rows));
    const resp = Response.json(result.rows);
    // Close the database connection, but don't block returning the response
    ctx.waitUntil(client.end());
    return resp;
  },
};

Local testing should use --experimental-local rather than --local, since the former runs the open-source Workers runtime and mirrors production behavior:

wrangler dev --experimental-local

Roadmap: pooling and beyond

MySQL support is next, targeting both the mysql and mysql2 drivers. The larger open problem is connection pooling in serverless. Creating a fresh database connection per request is workable but inefficient, and maintaining per-isolate pools introduces lifecycle and concurrency problems across many isolates and locations. Cloudflare says it is working on simpler pooling approaches for major databases, plus a new strategy for accelerating database reads.

Outbound TCP is only the first half of the socket story. Inbound TCP and UDP support are planned alongside new application protocols built on QUIC, all under the previously announced Socket Workers initiative. Smart Placement, announced separately, is being extended to Workers that open TCP connections so that queries to a distant database originate from the nearest Cloudflare location on the global network. Only the initial connection pays the full round-trip; the API's pipelining behavior means subsequent operations on that connection remain fast.