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.tsapps/backend/src/main-router.tsapps/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:1—drizzle-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.tsapps/backend/src/stores/slot-store.tsapps/backend/src/stores/banner-store.tsapps/backend/src/stores/product-tag-store.tsapps/backend/src/stores/user-negativity-store.tsapps/backend/src/lib/const-store.tsapps/backend/src/lib/event-queue.tsapps/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-41—QueueandWorkerclasses
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.tsapps/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 uploadapps/backend/src/uv-apis/uv-router.ts:9— complaint uploadapps/backend/src/uv-apis/auth.router.ts:8-9— profile imageapps/backend/src/apis/admin-apis/apis/product.router.ts:8-9— product imagesapps/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,198—fs.existsSync(),fs.mkdirSync(),fs.writeFileSync(),fs.readFileSync()apps/backend/src/lib/disk-persisted-set.ts:1,13-14,18,28—fs.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 TTLdisk-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.tsapps/backend/src/db/db_index.tsapps/backend/src/middleware/auth.tsapps/backend/src/middleware/auth.middleware.tsapps/backend/src/middleware/staff-auth.tsapps/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,260—process.exit()apps/backend/src/lib/disk-persisted-set.ts:71-72,76—process.exit()and signal handlersapps/backend/src/lib/notif-job.ts:163—process.on('SIGTERM', ...)apps/backend/src/db/porter.ts:120,124—process.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,242—bcrypt.hash()apps/backend/src/trpc/apis/user-apis/apis/auth.ts:118,196,311—bcrypt.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,81—jwt.verify()apps/backend/src/middleware/auth.ts:2,31—jwt.verify()apps/backend/src/middleware/auth.middleware.ts:2,32—jwt.verify()apps/backend/src/middleware/staff-auth.ts:2,25—jwt.verify()apps/backend/src/uv-apis/auth.controller.ts:3,53—jwt.sign()apps/backend/src/trpc/apis/user-apis/apis/auth.ts:4,53—jwt.sign()apps/backend/src/trpc/apis/admin-apis/apis/staff-user.ts:38—jwt.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—__dirnameusage
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, getSignedUrlapps/backend/src/lib/cloud_cache.ts— Buffer.from() for S3 uploadsapps/backend/src/lib/s3-client.ts—generateSignedUrlFromS3Url()
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 toorders:placed,orders:cancelledchannels
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.tsapps/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.ts—RazorpayPaymentService.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:5—turf.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
- CommonJS → ESM (#15)
- Replace
dotenv/configwith wrangler env bindings (#9) - Replace
jsonwebtokenwithjose(#14) - Replace Node.js
crypto.createHmacwith Web Crypto API (#12)
Phase 2 — Data Layer
- Replace
node-postgresdriver with Neon/HTTP driver (#2) - Replace Redis client with Upstash Redis (#3)
- Replace
fscache with KV (#7) - Replace S3 with R2 (#16)
Phase 3 — HTTP Layer
- Replace Express with Hono (#1)
- Replace Multer with native FormData (#6)
- Move static files to Pages/R2 (#8)
Phase 4 — Background Jobs
- Replace BullMQ with Cloudflare Queues (#4)
- Replace node-cron with Cron Triggers (#5)
- Rearchitect pub/sub (#17)
Phase 5 — Polish
- Verify expo-server-sdk / razorpay SDK compatibility (#18, #19)
- Optimize bundle size (#22)
- Remove Dockerfile, update deployment pipeline