EDGEWIRE: NODE.JS TCP LIBRARIES IN CLOUDFLARE WORKERS
Adapt Node-style net.Socket clients to cloudflare:sockets.
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 DBQuick Start
npm install edgewire
# or
bun add edgewireimport { 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.
| Feature | Hyperdrive | edgewire |
|---|---|---|
| Protocols | PostgreSQL only | TCP protocols compatible with cloudflare:sockets and the adapter |
| Node.js libraries | PostgreSQL drivers | Tested or compatible Node-style TCP libraries |
| Setup | Dashboard config | Just code |
| Connection pooling | Managed | Durable Objects + Effect.ts |
| Tls | Built-in | Cloudflare 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 pathTls / 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
tlsmodule. 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.