freshyo/edge_migration.md
2026-03-21 20:59:45 +05:30

13 KiB

Edge Migration Plan: Node.js → Cloudflare Workers

Overview

Migrating the backend from Node.js (Express, CommonJS) to Cloudflare Workers (V8 isolates, ESM, Web Standard APIs).


BLOCKERS (Must fix, won't work at all)

1. Express.js — Complete Replacement Needed

Affected files:

  • apps/backend/index.ts
  • apps/backend/src/main-router.ts
  • apps/backend/src/v1-router.ts
  • All files in apps/backend/src/apis/
  • All files in apps/backend/src/uv-apis/

Problem: Express uses Node.js http module internally — unavailable in Workers.

Solution: Replace with Hono (lightweight, Workers-native, similar middleware pattern) or use Cloudflare's export default { fetch } handler. Switch tRPC from createExpressMiddleware to createFetchMiddleware (Fetch adapter).


2. node-postgres (pg) Driver — TCP Sockets Unavailable

Affected files:

  • apps/backend/src/db/db_index.ts:1drizzle-orm/node-postgres

Problem: node-postgres uses TCP connections to Postgres. Workers have no TCP socket support.

Solution: Replace with drizzle-orm/neon-http (Neon serverless driver over HTTP) or use Cloudflare Hyperdrive (connection pooling proxy) with @neondatabase/serverless or fetch-based driver. Schema stays the same, only the driver changes.


3. Redis Client (redis npm package) — Persistent TCP + Pub/Sub Unavailable

Affected files:

  • apps/backend/src/lib/redis-client.ts (full custom RedisClient class)
  • apps/backend/src/stores/product-store.ts
  • apps/backend/src/stores/slot-store.ts
  • apps/backend/src/stores/banner-store.ts
  • apps/backend/src/stores/product-tag-store.ts
  • apps/backend/src/stores/user-negativity-store.ts
  • apps/backend/src/lib/const-store.ts
  • apps/backend/src/lib/event-queue.ts
  • apps/backend/src/lib/post-order-handler.ts

Problem: The redis npm package uses Node.js net/tls modules. subscribe() / publish() require persistent TCP connections — fundamentally incompatible with Workers.

Solution: Replace with Upstash Redis (HTTP-based Redis, drop-in replacement for get/set/del operations) or Cloudflare KV (different API, eventual consistency). Pub/Sub must be rearchitected (see #17).


4. BullMQ — Depends on Node.js Events + Persistent Redis Connections

Affected files:

  • apps/backend/src/lib/notif-job.ts:1-41Queue and Worker classes

Problem: Queue and Worker classes depend on Node.js EventEmitter and persistent Redis TCP connections.

Solution: Replace with Cloudflare Queues (native message queue service) — different API entirely. Or use Cloudflare Cron Triggers + Upstash Redis for simpler job scheduling.


5. node-cron — No Background Process in Workers

Affected files:

  • apps/backend/src/lib/automatedJobs.ts
  • apps/backend/src/jobs/jobs-index.ts

Problem: Workers are request-driven — no long-running background processes. cron.schedule() is meaningless.

Solution: Replace with Cloudflare Cron Triggers (wrangler.toml crons config) — each cron invocation is a separate Worker invocation. Rewrite cron logic as fetch handler with event.type === 'scheduled'.


6. Multer — Express Middleware, Uses Node.js Streams

Affected files:

  • apps/backend/src/lib/upload-handler.ts — multer config (10MB, memory storage)
  • apps/backend/src/main-router.ts:39 — complaint upload
  • apps/backend/src/uv-apis/uv-router.ts:9 — complaint upload
  • apps/backend/src/uv-apis/auth.router.ts:8-9 — profile image
  • apps/backend/src/apis/admin-apis/apis/product.router.ts:8-9 — product images
  • apps/backend/src/apis/admin-apis/apis/tag.router.ts:8,11 — tag image

Problem: Multer depends on Node.js streams and multipart parsing tied to Express.

Solution: Replace with native Request.formData() in Workers (built into the Fetch API) or use a Workers-compatible multipart parser like @mjackson/multipart-parser.


7. fs Module — No Filesystem in Workers

Affected files:

  • apps/backend/src/lib/signed-url-cache.ts:1,22-25,179,196,198fs.existsSync(), fs.mkdirSync(), fs.writeFileSync(), fs.readFileSync()
  • apps/backend/src/lib/disk-persisted-set.ts:1,13-14,18,28fs.existsSync(), fs.writeFileSync(), fs.readFileSync()

Problem: No filesystem in Workers. fs.readFileSync, fs.writeFileSync, fs.existsSync, fs.mkdirSync all unavailable.

Solution: Replace disk-persisted caches with Cloudflare KV or Durable Objects (for stateful storage).

  • signed-url-cache.ts → KV with TTL
  • disk-persisted-set.ts → KV or D1

8. Static File Serving (express.static, res.sendFile)

Affected files:

  • apps/backend/index.ts:134-173 — fallback UI serving, assets serving

Problem: No filesystem means no express.static() or res.sendFile().

Solution: Deploy fallback UI as a separate Cloudflare Pages project. Serve assets from R2 behind Workers. The fallback UI should not be bundled with the backend.


MAJOR CHANGES (Significant rewrite required)

9. process.env — 47 Usages Across 10+ Files

Affected files:

  • apps/backend/src/lib/env-exporter.ts (27 env vars exported)
  • apps/backend/index.ts
  • apps/backend/src/db/db_index.ts
  • apps/backend/src/middleware/auth.ts
  • apps/backend/src/middleware/auth.middleware.ts
  • apps/backend/src/middleware/staff-auth.ts
  • apps/backend/src/trpc/trpc-index.ts
  • All files importing from env-exporter.ts

Problem: Workers expose env vars via the Env bindings object passed to fetch(request, env), not process.env.

Solution: Every function that reads process.env.* needs refactoring to accept env as a parameter or use a shared context pattern. Create an Env type in wrangler.toml and thread it through the app.


10. process.exit() and Signal Handlers (SIGTERM, SIGINT)

Affected files:

  • apps/backend/src/lib/signed-url-cache.ts:254,260process.exit()
  • apps/backend/src/lib/disk-persisted-set.ts:71-72,76process.exit() and signal handlers
  • apps/backend/src/lib/notif-job.ts:163process.on('SIGTERM', ...)
  • apps/backend/src/db/porter.ts:120,124process.exit()

Problem: Workers don't have process object.

Solution: Remove all signal handlers and process.exit() calls. Graceful shutdown is handled by the platform.


11. Buffer.from() — 12 Usages

Affected files:

  • apps/backend/src/lib/cloud_cache.ts:27,52,77,102,127,152,259,266,273,280,287,298

Problem: Workers don't have Node.js Buffer.

Solution: Replace with new TextEncoder().encode() or Uint8Array.


12. crypto.createHmac() — Node.js Crypto API

Affected files:

  • apps/backend/src/trpc/apis/user-apis/apis/payments.ts:8,72-75 — Razorpay signature verification

Problem: Node.js crypto module unavailable.

Solution: Replace with Web Crypto API (crypto.subtle.importKey + crypto.subtle.sign) — available natively in Workers but different async syntax.


13. bcryptjs — May Need WASM Alternative

Affected files:

  • apps/backend/src/uv-apis/auth.controller.ts:105,242bcrypt.hash()
  • apps/backend/src/trpc/apis/user-apis/apis/auth.ts:118,196,311bcrypt.compare(), bcrypt.hash()

Problem: bcryptjs uses Node.js crypto.randomBytes internally.

Solution: Works in Workers with latest bcryptjs, or replace with @noble/hashes (pure JS, no Node dependencies).


14. jsonwebtoken — Depends on Node.js Crypto

Affected files:

  • apps/backend/index.ts:16,81jwt.verify()
  • apps/backend/src/middleware/auth.ts:2,31jwt.verify()
  • apps/backend/src/middleware/auth.middleware.ts:2,32jwt.verify()
  • apps/backend/src/middleware/staff-auth.ts:2,25jwt.verify()
  • apps/backend/src/uv-apis/auth.controller.ts:3,53jwt.sign()
  • apps/backend/src/trpc/apis/user-apis/apis/auth.ts:4,53jwt.sign()
  • apps/backend/src/trpc/apis/admin-apis/apis/staff-user.ts:38jwt.sign()

Problem: The jsonwebtoken lib uses Node.js crypto internally.

Solution: Replace with jose (JWT library designed for Web Crypto API, Workers-compatible).


15. CommonJS → ESM Module System

Affected files:

  • apps/backend/tsconfig.json:29"module": "commonjs"
  • apps/backend/index.ts:135,136,167__dirname usage

Problem: Workers require ESM. __dirname not available in ESM.

Solution: Change module to "ESNext" or "ES2022", moduleResolution to "bundler". Remove all __dirname usage (static files handled differently, see #8).


16. AWS S3 (@aws-sdk/client-s3) — Replace with R2

Affected files:

  • apps/backend/src/lib/s3-client.ts — S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand, getSignedUrl
  • apps/backend/src/lib/cloud_cache.ts — Buffer.from() for S3 uploads
  • apps/backend/src/lib/s3-client.tsgenerateSignedUrlFromS3Url()

Problem: AWS SDK may have Node.js-specific dependencies.

Solution: R2 is S3-compatible — this is the easiest change. Update the S3Client endpoint to point to your R2 bucket. Or use the native env.R2_BUCKET binding for better performance. @aws-sdk/s3-request-presigner for upload URLs may need R2-specific handling.


MODERATE CHANGES

17. Redis Pub/Sub for Order Notifications

Affected files:

  • apps/backend/src/lib/post-order-handler.ts — subscribes to orders:placed, orders:cancelled channels

Problem: Pub/Sub requires persistent TCP connections.

Solution: Replace with Durable Objects with WebSocket broadcasting, or Cloudflare Queues for async event processing.


18. expo-server-sdk — May Not Work in Workers

Affected files:

  • apps/backend/src/lib/notif-job.ts
  • apps/backend/src/lib/expo-service.ts

Problem: SDK may use Node.js internals.

Solution: Check if the SDK uses Node.js internals; if so, replace with direct HTTP calls to Expo's push API via fetch().


19. razorpay SDK — Check Node.js Dependencies

Affected files:

  • apps/backend/src/lib/payments-utils.tsRazorpayPaymentService.createOrder(), initiateRefund(), fetchRefund()

Problem: The Razorpay Node SDK may use http/https internally.

Solution: Replace with direct fetch() calls to Razorpay's REST API, or check if the SDK works with a polyfill.


20. Error.captureStackTrace — V8-Specific

Affected files:

  • apps/backend/src/lib/api-error.ts:12

Problem: V8-specific API.

Solution: Available in Workers (V8 runtime), so this actually works. Verify during migration.


21. process.uptime() — Used in Health Checks

Affected files:

  • apps/backend/src/main-router.ts:18,26

Problem: No process object in Workers.

Solution: Replace with a custom uptime tracker using Date.now() at module load.


22. @turf/turf — Check Bundle Size

Affected files:

  • apps/backend/src/trpc/apis/common-apis/common-trpc-index.ts:5turf.booleanPointInPolygon()

Problem: Workers have a 1MB compressed bundle limit (free) or 10MB (paid). @turf/turf is a large library.

Solution: Import only @turf/boolean-point-in-polygon to reduce size. Or implement the point-in-polygon check manually using a lightweight algorithm.


Infrastructure Changes

Current Workers Replacement
Express on port 4000 export default { fetch } handler (Hono)
PostgreSQL via pg Hyperdrive + Neon/serverless driver
Redis (redis npm) Upstash Redis (HTTP) or Cloudflare KV
BullMQ Cloudflare Queues
node-cron Cloudflare Cron Triggers
AWS S3 Cloudflare R2
Disk filesystem KV / D1 / R2
Docker deployment wrangler deploy
dotenv/config wrangler.toml [vars] + Secrets

Migration Priority

Phase 1 — Foundation

  1. CommonJS → ESM (#15)
  2. Replace dotenv/config with wrangler env bindings (#9)
  3. Replace jsonwebtoken with jose (#14)
  4. Replace Node.js crypto.createHmac with Web Crypto API (#12)

Phase 2 — Data Layer

  1. Replace node-postgres driver with Neon/HTTP driver (#2)
  2. Replace Redis client with Upstash Redis (#3)
  3. Replace fs cache with KV (#7)
  4. Replace S3 with R2 (#16)

Phase 3 — HTTP Layer

  1. Replace Express with Hono (#1)
  2. Replace Multer with native FormData (#6)
  3. Move static files to Pages/R2 (#8)

Phase 4 — Background Jobs

  1. Replace BullMQ with Cloudflare Queues (#4)
  2. Replace node-cron with Cron Triggers (#5)
  3. Rearchitect pub/sub (#17)

Phase 5 — Polish

  1. Verify expo-server-sdk / razorpay SDK compatibility (#18, #19)
  2. Optimize bundle size (#22)
  3. Remove Dockerfile, update deployment pipeline