iToverDose/Software· 30 APRIL 2026 · 16:03

Cloudflare Workers migration guide for TypeScript auth libraries

Moving a TypeScript auth library from Node.js to Cloudflare Workers exposes hidden dependencies. Learn which APIs break and how to rewrite them without losing functionality.

DEV Community4 min read0 Comments

Migrating a TypeScript authentication library from Node.js to Cloudflare Workers surfaces hidden incompatibilities. Many popular libraries assume Node.js-specific APIs like crypto.randomBytes, Buffer, or process.env, which don’t exist in the Workers runtime. When KavachOS, an open-source auth library, made the switch earlier this year, the author discovered that nearly every core function required rewriting. The migration wasn’t just technical—it reshaped how the library handles cryptography, environment variables, and database interactions. For developers building edge-native applications, these changes are critical to performance and portability.

Why Cloudflare Workers demand a rewrite

Cloudflare Workers execute JavaScript at the edge, offering sub-5ms cold starts and proximity to users. Unlike traditional serverless platforms that emulate Node.js, Workers rely entirely on Web APIs. This means no Buffer, no process.env, and no Node.js fs module. For an authentication library—often called on every protected request—these absences translate directly to latency and compatibility issues.

Most AI agent frameworks and edge applications now target the same runtime. An auth layer that can’t run on Workers becomes the slowest component in the stack. KavachOS’s migration wasn’t optional; it was a strategic move to keep pace with modern edge architectures.

Replacing Buffer with Web-native types

The most pervasive change was eliminating Buffer, which appears in most cryptographic operations. The library contained around 60 instances of Buffer.from() calls, all requiring replacement. Instead of relying on Node’s built-in type, the code now uses Uint8Array and manual conversion utilities.

Before:

const bytes = Buffer.from(secret, "utf-8");
const hex = bytes.toString("hex");

After:

const bytes = new TextEncoder().encode(secret);
const hex = Array.from(bytes)
  .map((b) => b.toString(16).padStart(2, "0"))
  .join("");

A custom helper handles base64 URL encoding, replacing Node’s Buffer.toString("base64url"):

function bytesToBase64Url(bytes: Uint8Array): string {
  let binary = "";
  for (const b of bytes) binary += String.fromCharCode(b);
  return btoa(binary)
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

This approach is more verbose but aligns with Web API standards. It also reduces bundle size by removing Node-specific dependencies.

Swapping Node crypto for Web Crypto

Node’s crypto module offers synchronous methods like randomBytes() and createHash(), which are unavailable in Workers. The Web Crypto API provides similar functionality but operates asynchronously, forcing a cascade of changes throughout the codebase.

Node-style code:

import { randomBytes, createHash } from "node:crypto";
const token = randomBytes(32).toString("hex");
const hash = createHash("sha256").update(input).digest("hex");

Web Crypto equivalent:

const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
const token = bytesToHex(bytes);

const data = new TextEncoder().encode(input);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hash = bytesToHex(new Uint8Array(hashBuffer));

The async nature of Web Crypto meant updating around 20 internal helpers to use await. For JWT operations, the library switched from jsonwebtoken to jose, which supports Web Crypto and runs across Workers, Bun, and Deno without polyfills.

Adapting to Cloudflare’s D1 database

Cloudflare D1 is SQLite-compatible but exposes a different API than traditional Node libraries. Instead of connecting via a connection string, D1 bindings are injected at runtime:

const kavach = await createKavach({
  database: {
    provider: "d1",
    binding: env.DB, // injected by Workers runtime
  },
});

The migration required adding a d1 provider alongside the existing sqlite option. While schema and migration logic remained shared, prepared statements changed from better-sqlite3’s style:

db.prepare(sql).run(...)

to D1’s method:

db.prepare(sql).bind(...).run()

The migration tool now supports dual paths: Node reads schema files from disk, while Workers bundle the schema as a string at build time.

Removing process.env and embracing explicit config

Workers don’t expose process.env. Instead, secrets and bindings are passed via the env object in the request handler:

export default {
  async fetch(request: Request, env: Env) {
    const kavach = await createKavach({
      secret: env.AUTH_SECRET,
      database: {
        provider: "d1",
        binding: env.DB,
      },
    });
    return kavach.handle(request);
  },
};

This shift improved testability by forcing configuration to be explicit rather than relying on hidden environment variables. It also simplified dependency injection across different environments.

TypeScript 5.8’s stricter ArrayBuffer handling

TypeScript 5.8 tightened type checking for ArrayBuffer and Uint8Array, breaking previously acceptable implicit conversions. Code like:

const bytes: Uint8Array = await crypto.subtle.digest("SHA-256", data);

no longer compiled because crypto.subtle.digest() returns an ArrayBuffer, not a Uint8Array. The fix required explicit conversion:

const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", data));

This change affected about 30 locations, demanding careful refactoring to maintain type safety.

Lessons for future migrations

The rewrite revealed avoidable detours. The author spent two days building a custom AES-GCM helper before discovering Web Crypto already includes it. A thorough review of Web Crypto’s API would have saved significant time. Similarly, a custom bytesToBase64 function was unnecessary—btoa() works for ASCII strings.

For developers considering a similar migration, starting with Web Crypto documentation and leveraging platform-native utilities can streamline the process. Open-source projects like KavachOS offer practical examples, with well-documented commit histories showing each step of the transition.

Cloudflare Workers and other edge runtimes are reshaping how applications handle authentication. Libraries that embrace Web APIs—not Node shims—will define the next generation of edge-native security tools. The migration process is challenging, but the performance gains and cross-platform compatibility make it worthwhile.

AI summary

TypeScript tabanlı bir kimlik doğrulama kütüphanesini Node.js’den Cloudflare Workers’a taşırken karşılaşılan teknik zorluklar ve çözüm yollarını keşfedin.

Comments

00
LEAVE A COMMENT
ID #SCPO2O

0 / 1200 CHARACTERS

Human check

5 + 3 = ?

Will appear after editor review

Moderation · Spam protection active

No approved comments yet. Be first.