Search

Sign in to launch Copilot/Codex from the palette.

EDGEWIRE: NODE.JS TCP LIBRARIES IN CLOUDFLARE WORKERS

Adapt Node-style net.Socket clients to cloudflare:sockets.

TCP Adapter 2025.12.09
NPM

The Problem

Problem: I needed to connect Cloudflare Workers to a GCP SQL Server instance. Hyperdrive doesn't support MSSQL. The native connect() API works, but requires rewriting all your code to use streams instead of net.Socket.

Implementation: edgewire adapts Node's net.Socket interface to cloudflare:sockets. Tedious and the Kysely example work through the adapter; other libraries depend on which Node socket behavior they require.

Your Code → patchNetModule() → globalThis.net
                                    ↓
                          WorkersNetSocket class
                                    ↓
                          cloudflare:sockets API
                                    ↓
                          TCP connection to your DB

Quick Start

npm install edgewire
# or
bun add edgewire
import { patchNetModule } from 'edgewire';

// One line. Call before importing database drivers.
patchNetModule();

// Import a compatible client after patching net.Socket
import { Connection } from 'tedious';

One setup call. Compatible Node.js TCP libraries can then use the adapter, sometimes without application-level changes.

Why Not Hyperdrive?

Hyperdrive provides managed PostgreSQL connectivity. edgewire explores a lower-level path for other TCP protocols and libraries.

FeatureHyperdriveedgewire
ProtocolsPostgreSQL onlyTCP protocols compatible with cloudflare:sockets and the adapter
Node.js librariesPostgreSQL driversTested or compatible Node-style TCP libraries
SetupDashboard configJust code
Connection poolingManagedDurable Objects + Effect.ts
TlsBuilt-inCloudflare Tunnel

TEDIOUS + SQL SERVER

The original use case. Connect to SQL Server from Workers.

import { patchNetModule } from 'edgewire';
patchNetModule();

import { Connection, Request } from 'tedious';

export default {
  async fetch(request: Request, env: Env) {
    const connection = new Connection({
      server: env.SQL_SERVER_HOST,
      authentication: {
        type: 'default',
        options: {
          userName: env.SQL_SERVER_USER,
          password: env.SQL_SERVER_PASSWORD,
        },
      },
      options: {
        database: env.SQL_SERVER_DB,
        port: 1433,
        encrypt: false,  // Use Cloudflare Tunnel for encryption
        trustServerCertificate: true,
      },
    });

    return new Promise((resolve) => {
      connection.on('connect', (err) => {
        if (err) {
          resolve(new Response(JSON.stringify({ error: err.message }), { status: 500 }));
          return;
        }

        const request = new Request('SELECT * FROM Users WHERE Id = @id', (err, rowCount) => {
          connection.close();
          resolve(new Response(JSON.stringify({ rowCount })));
        });

        request.addParameter('id', 1);
        connection.execSql(request);
      });

      connection.connect();
    });
  },
};

KYSELY + POSTGRESQL

Type-safe queries with Kysely using the adapter shown below.

import { patchNetModule } from 'edgewire';
patchNetModule();

import { Kysely, PostgresDialect } from 'kysely';
import { Pool } from 'pg';

export default {
  async fetch(request: Request, env: Env) {
    const db = new Kysely({
      dialect: new PostgresDialect({
        pool: new Pool({
          host: env.POSTGRES_HOST,
          port: 5432,
          user: env.POSTGRES_USER,
          password: env.POSTGRES_PASSWORD,
          database: env.POSTGRES_DB,
        }),
      }),
    });

    const users = await db.selectFrom('users').selectAll().execute();
    return new Response(JSON.stringify(users));
  },
};

Connection Pooling

Pool state lives in a Durable Object. Effect scopes finalizers for the connection paths shown here; process termination and runtime faults still require defensive cleanup.

import { PooledSocket } from 'edgewire';

export default {
  async fetch(request: Request, env: Env) {
    // Connection pooling via Durable Objects + Effect.ts
    const socket = new PooledSocket(env.CONNECTION_POOL, {
      host: 'db.example.com',
      port: 5432,
    });

    await socket.connect();  // Acquires from pool
    socket.write(data);
    await socket.release();  // Returns to pool on this successful path
  },
};

// Optional config
const socket = new PooledSocket(env.CONNECTION_POOL, options, {
  maxConnections: 20,      // Default: 20
  idleTimeout: 30000,      // Default: 30 seconds
});

Pool Features

  • • Scoped cleanup via Effect.ts finalizers
  • • Cross-request persistence
  • • Automatic idle cleanup (30s)
  • • ~1-2ms overhead vs 50-100ms setup

How It Works

  • • Durable Object holds pool state
  • • Connections persist across requests
  • • Shared across all Worker instances
  • • Evicted after hours of inactivity

Supported Services

Anything that uses net.Socket. The integration test proves compatibility.

// Shown in this post
SQL Server with Tedious
PostgreSQL through Kysely + pg

// Compatibility depends on
- which net.Socket methods the client uses
- TLS behavior
- Workers runtime support
- the remote protocol and network path

Tls / Encryption

edgewire provides raw TCP sockets. For encryption, use Cloudflare Tunnel:

Your Database ← cloudflared tunnel → Cloudflare Network ← Worker
     (encrypted)                                    (encrypted)

Tunnel encrypts the connection from your database to Cloudflare's edge. Worker-to-Cloudflare is already encrypted. No TLS config needed in your code.

Live Demo

Proof of concept deployed at edgewire-proof.coy.workers.dev

{
  "status": "ok",
  "library": "tedious",
  "connectionType": "Connection",
  "message": "SQL Server client instantiated in Cloudflare Workers"
}

Proves the hard part: bundling + polyfill works. Actual TCP connections work the same way.

Limitations

  • Plain TCP only: No Node.js tls module. Use Cloudflare Tunnel for encryption.
  • Workers limits: 30s max request duration, memory varies by plan.
  • Design accordingly: Keep queries fast, use connection pooling.

API Reference

patchNetModule()

Patches globalThis.net to use the TCP adapter. Call before importing database drivers.

PooledSocket

Connection pooling with Durable Objects + Effect.ts resource management. Methods: connect(), release(), destroy(), write().

WorkersNetSocket

Direct socket usage if not using a database driver. Implements Node.js net.Socket interface.