File Storage
Two storage backends
Section titled “Two storage backends”| Environment | Storage | How |
|---|---|---|
| Local / cPanel | Local disk (backend/uploads/) | Node.js fs.writeFileSync |
| Cloudflare Workers | Cloudflare R2 | globalThis.BUCKET.put() |
The unified interface lives in backend/utils/storage.js and exposes three functions:
saveFile(file, folder) // Returns filename/key stringdeleteFile(filename) // Removes filegetFileUrl(filename) // Returns URL stringHow saveFile works
Section titled “How saveFile works”const saveFile = async (file, folder = 'others') => { const ext = path.extname(file.originalname).toLowerCase(); const uniqueName = Date.now() + '-' + Math.round(Math.random() * 1E9) + ext; const key = `${folder}/${uniqueName}`;
if (globalThis.BUCKET) { // Production: save to R2 await globalThis.BUCKET.put(key, file.buffer, { httpMetadata: { contentType: file.mimetype } }); return key; }
// Development: save to local disk fs.writeFileSync(path.join(__dirname, '..', 'uploads', folder, uniqueName), file.buffer); return key;};Files come in as Buffer via multer’s memory storage (config/multer.js), so file.buffer is always available.
Known issue: getFileUrl is broken in production
Section titled “Known issue: getFileUrl is broken in production”getFileUrl currently returns /uploads/${filename}, which works in local dev (Express serves /uploads/ as static files) but is meaningless in a Cloudflare Worker (no filesystem, no static file serving).
In production, R2 files are inaccessible via getFileUrl. Files are saved successfully to R2 but the URL is never a valid R2 link.
The fix requires choosing one of:
-
R2 with a public bucket + custom domain — Set the R2 bucket to public, configure a custom domain in the Cloudflare dashboard, and update
getFileUrlto returnhttps://uploads.shanvitravel.com.np/${filename}. -
R2 with signed URLs — Generate a time-limited signed URL per request using the R2 API. More secure but adds per-request latency.
-
Cloudflare Images — For image files specifically, use Cloudflare Images which provides automatic resizing, CDN delivery, and a clean URL scheme.
Option 1 is the simplest fix and the right starting point.
Multer configuration
Section titled “Multer configuration”config/multer.js configures multer to use memory storage (files stored as Buffer in RAM, not written to temp disk). This is required because Cloudflare Workers have no writable filesystem.
// config/multer.js (simplified)const multer = require('multer');const storage = multer.memoryStorage();const upload = multer({ storage, limits: { fileSize: 10 * 1024 * 1024 } }); // 10MBFile type validation should be added here — currently any file extension is accepted. For KYC and submission files, acceptable types should be limited to JPEG, PNG, and PDF.
Folder structure in R2 / uploads
Section titled “Folder structure in R2 / uploads”| Folder key | Used for |
|---|---|
kyc/ | KYC registration and PAN files |
others/ | Default — submission passports, receipts, etc. |
vfs/ | VFS tracking task documents |
No explicit folder is passed for submission files, so they land in others/. This makes it harder to clean up orphaned files and harder to set per-folder retention policies. Each feature should pass a meaningful folder argument to saveFile.