# 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: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.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-41` — `Queue` 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,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 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,260` — `process.exit()` - `apps/backend/src/lib/disk-persisted-set.ts:71-72,76` — `process.exit()` and signal handlers - `apps/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` — `__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.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 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.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 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 5. Replace `node-postgres` driver with Neon/HTTP driver (#2) 6. Replace Redis client with Upstash Redis (#3) 7. Replace `fs` cache with KV (#7) 8. Replace S3 with R2 (#16) ### Phase 3 — HTTP Layer 9. Replace Express with Hono (#1) 10. Replace Multer with native FormData (#6) 11. Move static files to Pages/R2 (#8) ### Phase 4 — Background Jobs 12. Replace BullMQ with Cloudflare Queues (#4) 13. Replace node-cron with Cron Triggers (#5) 14. Rearchitect pub/sub (#17) ### Phase 5 — Polish 15. Verify expo-server-sdk / razorpay SDK compatibility (#18, #19) 16. Optimize bundle size (#22) 17. Remove Dockerfile, update deployment pipeline