342 lines
13 KiB
Markdown
342 lines
13 KiB
Markdown
# 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
|