Does Express Work With Cloudflare R2?
Express works seamlessly with Cloudflare R2 via the AWS SDK—treat R2 as an S3-compatible backend and build file upload/download features directly into your Express app.
Quick Facts
How Express Works With Cloudflare R2
Express has no native R2 support, but Cloudflare R2 is S3-compatible, so you use the official AWS SDK for JavaScript (@aws-sdk/client-s3) with custom endpoint configuration. Your Express routes handle HTTP requests and delegate file operations to the SDK—uploads write directly to R2, downloads stream from R2 through your Express response. The developer experience is straightforward: initialize an S3 client pointing to your R2 bucket's custom domain, then use familiar S3 methods (PutObject, GetObject) in your route handlers. This approach scales well because R2 egress is free, making it cost-effective for image-heavy applications. You'll typically handle multipart uploads, signed URLs for direct downloads, and deletion operations all within Express middleware or route handlers.
Best Use Cases
Quick Setup
npm install express @aws-sdk/client-s3 dotenvimport express from 'express';
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const app = express();
const s3Client = new S3Client({
region: 'auto',
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY,
secretAccessKey: process.env.R2_SECRET_KEY,
},
forcePathStyle: true,
});
app.post('/upload', async (req, res) => {
const filename = `uploads/${Date.now()}-file.txt`;
const uploadCmd = new PutObjectCommand({
Bucket: process.env.R2_BUCKET,
Key: filename,
Body: req,
});
await s3Client.send(uploadCmd);
res.json({ url: `${process.env.R2_PUBLIC_URL}/${filename}` });
});
app.get('/download/:key', async (req, res) => {
const url = await getSignedUrl(s3Client,
new GetObjectCommand({ Bucket: process.env.R2_BUCKET, Key: req.params.key }),
{ expiresIn: 3600 }
);
res.redirect(url);
});
app.listen(3000);Known Issues & Gotchas
Endpoint URL misconfiguration—using Cloudflare's dashboard URL instead of the S3-compatible endpoint
Fix: Use the format: https://<account-id>.r2.cloudflarestorage.com (or your custom domain) and set forcePathStyle: true in SDK config
Large file uploads timing out or hitting memory limits in Express
Fix: Use multipart upload via @aws-sdk/lib-storage or stream the request body directly to R2 instead of buffering
CORS errors when serving R2 content directly to browsers
Fix: Configure R2 bucket CORS settings or proxy downloads through Express routes
Credential exposure if access keys are hardcoded
Fix: Use environment variables and consider Cloudflare's API tokens or temporary credentials via STS
Alternatives
- •Next.js with Vercel Blob—tighter integration but vendor-locked to Vercel infrastructure
- •Django with boto3 and AWS S3—if you prefer Python backends with similar S3-compatible storage
- •MinIO self-hosted + Express—for on-premise S3-compatible storage without cloud dependency
Resources
Related Compatibility Guides
Explore more compatibility guides