A cross-runtime sockets API takes a step closer to standard

Cloudflare and Vercel engineers have published a draft specification for a cross-runtime TCP sockets API, along with a Node.js-compatible implementation that developers can use today. The work, conducted under the Web-interoperable Runtimes Community Group (WinterCG), aims to provide a common connect() interface across JavaScript environments — including serverless platforms where Node.js built-ins like node:net and node:tls aren't available.

The new implementation serves two audiences:

  1. Library maintainers who currently rely on node:net and node:tls can add support for runtimes lacking those APIs without writing runtime-specific code.
  2. Framework authors can expose connect() in local development, smoothing the path for applications that target runtimes providing the API natively.

Why a new sockets standard was needed

JavaScript runtimes have historically lacked a shared API for creating and managing TCP or UDP sockets. Node.js offers node:net and node:tls, but those interfaces date back more than a decade and remain callback-based, with configuration patterns that don't map well to serverless or browser environments. That fragmentation has forced library maintainers to juggle multiple code paths and left application developers guessing which libraries will work on which platforms.

The connect() API draws on existing socket interfaces and prior proposals, incorporating feedback from the JavaScript community — including contributors to Node.js. The pg library (node-postgres) is already using the API.

The proposed API surface

The current draft specification defines the following core interface:

dictionary SocketAddress {
  DOMString hostname;
  unsigned short port;
};

typedef (DOMString or SocketAddress) AnySocketAddress;

enum SecureTransportKind { "off", "on", "starttls" };

[Exposed=*]
dictionary SocketOptions {
  SecureTransportKind secureTransport = "off";
  boolean allowHalfOpen = false;
};

[Exposed=*]
interface Connect {
  Socket connect(AnySocketAddress address, optional SocketOptions opts);
};

interface Socket {
  readonly attribute ReadableStream readable;
  readonly attribute WritableStream writable;

  readonly attribute Promise<undefined> closed;
  Promise<undefined> close();

  Socket startTls();
};

The API is Promise-based and leans on existing web standards wherever possible. The read and write ends of a socket are exposed as ReadableStream and WritableStream, respectively, so data can be piped directly to or from any other stream-compatible code.

The entry point is connect(), which accepts either a hostname:port string or an object with separate hostname and port fields. It returns a Socket object representing the connection.

Connections can be established in plaintext, TLS, or a special "starttls" mode. In the latter case, the socket begins in plaintext and can be upgraded to TLS by calling startTls() on the same Socket object — no need to create a new socket or switch to a different API set.

For example, upgrading a socket via the startTLS pattern looks like this:

import { connect } from "@arrowood.dev/socket"

const options = { secureTransport: "starttls" };
const socket = connect("address:port", options);
const secureSocket = socket.startTls();
// The socket is immediately writable
// Relies on web standard WritableStream
const writer = secureSocket.writable.getWriter();
const encoder = new TextEncoder();
const encoded = encoder.encode("hello");
await writer.write(encoded);

The equivalent code using node:net and node:tls is:

import net from 'node:net'
import tls from 'node:tls'

const socket = new net.Socket(HOST, PORT);
socket.once('connect', () => {
  const options = { socket };
  const secureSocket = tls.connect(options, () => {
    // The socket can only be written to once the
    // connection is established.
    // Polymorphic API, uses Node.js streams
    secureSocket.write('hello');
  }
})

Using the Node.js implementation

The published Node.js implementation lets library maintainers write code against connect() and ship it across runtimes without maintaining runtime-specific branches. To use it, install the package as a dependency:

npm install --save @arrowood.dev/socket

Then import it in your library or application:

import { connect } from "@arrowood.dev/socket"

Next steps

The draft specification is open for community review, with feedback sought from developers who maintain libraries or work directly with node:net or node:tls. After incorporating that feedback, engineers from Cloudflare, Vercel and others plan to pursue adding connect() directly to Node.js as a built-in API.