Introduction
Object storage looks simple when a Node.js SaaS app only needs profile pictures. It becomes infrastructure when the product grows into invoices, exports, PDFs, contracts, user uploads, import files, thumbnails, generated reports, support attachments, video clips, and tenant-specific documents.
At that point, the question is no longer “how do I upload a file with Express?” The question becomes: which storage platform gives the right combination of upload ergonomics, access control, CDN delivery, S3 compatibility, cost predictability, compliance posture, and operational visibility?
This guide compares common object storage and file upload platforms for Node.js SaaS applications in 2026: AWS S3, Cloudflare R2, Backblaze B2, DigitalOcean Spaces, Supabase Storage, and UploadThing. It is written for teams choosing production infrastructure, not for a one-off tutorial project.
Quick Comparison Table
| Platform | Best Fit | Node.js Integration Model | Cost Shape | Main Tradeoff |
|---|---|---|---|---|
| AWS S3 | Enterprise AWS stacks, mature compliance, long-term durability | AWS SDK for JavaScript v3, S3 signed URLs, IAM | Storage, requests, retrievals, transfer, lifecycle classes | Powerful but pricing and permissions can become complex |
| Cloudflare R2 | Public assets, SaaS downloads, egress-sensitive workloads | S3-compatible API, Workers, AWS SDK-compatible clients | Storage plus Class A/Class B operations; direct R2 egress is free at time of writing | Some workflows still need Cloudflare-specific thinking |
| Backblaze B2 | Predictable backup, archive, media, and active object storage | S3-compatible API and native B2 tooling | Starts from TB/month pricing; free egress rules differ materially from hyperscalers | Fewer adjacent cloud services than AWS |
| DigitalOcean Spaces | Small SaaS teams already using DigitalOcean | S3-compatible API, CDN included | Simple monthly bundle with included storage and outbound transfer | Less granular than AWS for enterprise storage architecture |
| Supabase Storage | Apps already using Supabase Auth, Postgres, and RLS | Supabase JavaScript client, S3 compatibility, REST, TUS | Part of Supabase platform pricing and quotas; confirm before publishing | Best when you want integrated app backend behavior, not only raw object storage |
| UploadThing | Full-stack TypeScript apps that want upload UX and routing | First-party framework adapters and backend adapters including Express/Fastify | Developer platform pricing; confirm before publishing | More of an upload layer than a generic cloud object store |
What a Node.js SaaS Team Should Evaluate
A production file pipeline has more moving parts than the storage bucket. Before comparing providers, define the actual workload.
Public Assets
For documentation images, open downloads, marketing files, or public product media, the most important variables are CDN behavior, egress price, cache invalidation, hot object read volume, and deployment simplicity.
Private User Uploads
For documents, contracts, support attachments, invoices, or tenant-specific exports, the key variables are access control, signed URL expiry, malware scanning, file type validation, retention policy, audit logging, and per-tenant isolation.
Generated Files
For reports, billing exports, PDF invoices, AI-generated assets, and background job outputs, the key variables are write throughput, lifecycle cleanup, object naming, idempotency, and how the queue worker records final file state in the database.
Media-Heavy Products
The storage choice should also include CDN, image optimization, video processing, transformation costs, and whether derivatives are stored as separate objects or generated dynamically.
AWS S3: The Default Enterprise Object Storage Choice
AWS S3 is still the default baseline because it is mature, globally available, deeply integrated with IAM, Lambda, CloudFront, EventBridge, Glue, Athena, lifecycle policies, replication, object lock, and many compliance workflows. If your Node.js SaaS app already runs on AWS, S3 often minimizes platform risk even when it is not the simplest price story.
The AWS SDK for JavaScript v3 includes S3 examples for common actions such as listing buckets, creating buckets, uploading objects, downloading objects, copying objects, and deleting objects. For Node.js apps, the usual production path is to let the backend issue a short-lived signed upload URL, then let the browser or mobile client upload directly to S3. The backend stores metadata in PostgreSQL or another application database.
A typical architecture:
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({ region: process.env.AWS_REGION });
export async function putObjectExample() {
await s3.send(
new PutObjectCommand({
Bucket: process.env.UPLOAD_BUCKET,
Key: "tenants/acme/uploads/example.pdf",
Body: Buffer.from("example"),
ContentType: "application/pdf",
})
);
}
For production, do not route large files through Express memory buffers unless you have a specific reason. Use streaming, multipart upload, or direct signed uploads. Your Node.js API should own policy decisions: which tenant can upload, which file types are allowed, what maximum size applies, and which background workflow should scan or transform the object.
The drawback is pricing complexity. AWS S3 pricing includes storage classes, request charges, data retrieval charges for some classes, lifecycle transitions, replication, acceleration, object metadata features, and data transfer. AWS notes that S3 request costs depend on request type and quantity, and that DELETE and CANCEL requests are free. It also shows examples where internet transfer out from an S3 bucket can add separate transfer charges. Confirm all regional pricing before publishing.
Choose AWS S3 when you need deep AWS integration, enterprise controls, lifecycle flexibility, compliance options, and long-term platform maturity.
Cloudflare R2: Strong for Egress-Sensitive SaaS Assets
Cloudflare R2 is often attractive for SaaS teams that serve many files to users and want to reduce egress anxiety. Cloudflare’s R2 pricing page states that R2 charges for stored data and two classes of operations, and that there are no egress bandwidth charges for any storage class when egressing directly from R2. It also lists a free tier for Standard storage, including 10 GB-month storage, 1 million Class A operations, and 10 million Class B operations per month at the time of writing.
For Node.js teams, R2’s practical advantage is that it supports the S3 API. That means many AWS SDK patterns can be adapted by changing endpoint configuration and credentials. Cloudflare also provides examples for using R2 with aws-sdk-js-v3.
R2 is particularly appealing for:
- Customer downloads that may have high outbound traffic
- Public assets served near users through Cloudflare
- SaaS exports, reports, and generated files where egress costs are hard to forecast
- Products already using Cloudflare Workers, Pages, DNS, WAF, or CDN
The tradeoff is that S3 compatibility does not mean every AWS-adjacent service exists in the same way. If your architecture depends heavily on AWS IAM semantics, S3 event workflows, Athena, Glue, Macie, or deep AWS account controls, you should compare the operational model carefully.
Choose Cloudflare R2 when egress predictability and Cloudflare edge integration matter more than full AWS ecosystem depth.
Backblaze B2: Predictable Object Storage for Budget-Aware Teams
Backblaze B2 is a strong option for teams that want S3-compatible storage with a simpler cost story than hyperscale cloud storage. Backblaze’s pricing page lists B2 Cloud Storage as starting at $6.95/TB/month and states that users get free egress up to 3x average monthly storage, with egress beyond that listed at $0.01/GB at the time of writing. It also highlights free transactions.
This pricing model can be useful for Node.js SaaS apps that store large volumes of user-generated content, backups, media, or exported files but do not need the entire AWS platform around the bucket.
Good B2 use cases include:
- Customer backups and recovery files
- Media libraries with moderate read traffic
- App-generated archives
- SaaS products that want predictable storage bills
- Secondary storage behind a CDN or compute provider
The main tradeoff is ecosystem breadth. AWS gives you a large universe of adjacent services. Backblaze B2 gives you object storage, S3 compatibility, and cost predictability, but you may assemble more of the surrounding infrastructure yourself.
Choose Backblaze B2 when storage volume and predictable billing matter, and when your team is comfortable assembling auth, CDN, compute, scanning, and observability from other services.
DigitalOcean Spaces: Simple Bundled Object Storage for Small SaaS Teams
DigitalOcean Spaces is a practical choice for small SaaS teams already deploying Node.js apps on DigitalOcean Droplets, App Platform, Managed Databases, or Kubernetes. The product is S3-compatible and includes a built-in CDN. DigitalOcean’s pricing page lists Spaces Object Storage at $5 per month, including 250 GiB storage and 1 TiB outbound transfer, with additional storage and transfer prices shown separately at the time of writing.
The main advantage is simplicity. A small team can keep the app server, database, object storage, CDN, and billing relationship in one cloud account. This is often more important than theoretical price optimization during the first stage of a SaaS product.
DigitalOcean Spaces fits:
- MVPs and early SaaS products
- Image uploads and downloadable files
- Apps already using DigitalOcean infrastructure
- Teams that want a simple monthly baseline rather than a complex cloud bill
The limitation is that large or compliance-heavy systems may eventually need more granular controls, multi-region strategy, object lock features, or specialized data governance. For many small Node.js SaaS products, however, Spaces is enough for the first serious production version.
Choose DigitalOcean Spaces when you want predictable setup and already use DigitalOcean for the rest of the stack.
Supabase Storage: Integrated Storage for Postgres-First SaaS Apps
Supabase Storage is not just a bucket. It is part of a broader backend platform with Postgres, Auth, APIs, Edge Functions, Realtime, and policy-driven access control. Supabase’s Storage documentation describes storage for files of any size, fine-grained access controls, optimized delivery, S3-compatible storage, RESTful API access, TUS resumable uploads, Global CDN, image optimization, and row-level security integration.
This makes it especially useful when your Node.js or full-stack TypeScript app already uses Supabase Auth and Postgres. Instead of building a separate identity-to-bucket permission layer, you can keep storage authorization closer to application records and policies.
Supabase Storage is a good fit for:
- SaaS apps already using Supabase Auth and Postgres
- User-generated files tied to database rows
- Apps needing row-level security alignment
- Projects that benefit from built-in image transformation and CDN behavior
- Small teams that prefer an integrated backend platform
The tradeoff is that you are choosing a platform model, not just a raw storage commodity. If your app may later move to a multi-cloud architecture or needs very specific object storage controls, define an abstraction layer in your Node.js app so migration remains possible.
Choose Supabase Storage when product speed, Postgres integration, auth-aware policies, and built-in media handling matter more than raw object storage portability.
UploadThing: Upload Developer Experience for Full-Stack TypeScript Apps
UploadThing sits in a different category. It is not mainly a generic object storage bucket provider in the same way S3, R2, B2, or Spaces are. It is a developer-focused file upload layer for TypeScript applications. The documentation describes it as a way to add file uploads to full-stack TypeScript apps and lists framework support plus backend adapters such as Express and Fastify.
That matters because many SaaS teams do not only need storage. They need upload routes, frontend components, file validation, callbacks, auth hooks, and a cleaner developer experience. Building this from scratch around raw S3 can be correct for large teams, but it is often slower for small teams.
UploadThing is a good fit for:
- Next.js, Astro, Remix, or full-stack TypeScript apps
- Teams that want a file upload product rather than a storage primitives project
- Apps that need fast implementation of upload routes and validation
- Early SaaS projects where speed matters more than platform abstraction
The tradeoff is control. If you need to fully own every storage behavior, region rule, bucket policy, lifecycle policy, and S3-compatible integration, a lower-level object storage service may be more appropriate.
Choose UploadThing when your primary pain is upload implementation speed, not raw storage architecture.
Recommended Architecture for Node.js File Uploads
For most production SaaS apps, use this pattern:
- The client asks your Node.js API for upload permission.
- The Node.js API authenticates the user and checks tenant-level authorization.
- The API creates a database record with status such as
pending_upload. - The API returns a signed upload URL or upload token.
- The client uploads directly to storage or to a managed upload service.
- A webhook, callback, queue worker, or polling task verifies upload completion.
- A background worker scans, transforms, thumbnails, or indexes the file.
- The database record is marked
ready,rejected, orquarantined. - Downloads use signed URLs, CDN URLs, or an authorization proxy depending on file sensitivity.
This avoids routing large payloads through the Node.js server while keeping policy decisions in your application.
Cost Factors to Confirm Before Publishing
Cloud storage bills are often surprising because storage is only one component. Confirm the following before publishing a final recommendation:
| Cost Factor | Why It Matters |
|---|---|
| Storage per GB/TB/month | Baseline cost for retained files |
| Minimum object size | Small thumbnails or event files can become expensive |
| Minimum storage duration | Early deletes can still be billed in archive or infrequent tiers |
| PUT/LIST/GET requests | High-cardinality objects can create request-heavy workloads |
| Egress | Public downloads and customer exports can dominate cost |
| CDN behavior | CDN cache hits may reduce origin requests and egress exposure |
| Image transformations | Dynamic resizing can add compute or transformation charges |
| Multipart upload leftovers | Failed uploads may leave incomplete parts without cleanup |
| Lifecycle transitions | Moving files between tiers may cost money and add retrieval limits |
| Monitoring and audit logs | Required for production, but may add storage and ingest costs |
Practical Recommendations
- Use AWS S3 if your SaaS product already runs on AWS, needs enterprise controls, or expects to integrate with many AWS services.
- Use Cloudflare R2 if your main concern is public file delivery and egress predictability, especially if you already use Cloudflare for DNS, CDN, Workers, or security.
- Use Backblaze B2 if you store large volumes and want simpler storage economics with S3 compatibility.
- Use DigitalOcean Spaces if you want a simple bundled object storage service and already use DigitalOcean for Node.js hosting.
- Use Supabase Storage if your app is already built around Supabase Auth, Postgres, and row-level security.
- Use UploadThing if your bottleneck is building a clean upload workflow in a TypeScript app rather than operating raw object storage.
Conclusion
For a Node.js SaaS product, file storage is not a small utility decision. It affects infrastructure cost, security, user experience, support workflows, tenant isolation, and long-term portability.
The safest way to choose is to start from workload shape. If you need enterprise AWS integration, start with S3. If egress is the central concern, compare R2 and B2 carefully. If you want a simple startup stack, DigitalOcean Spaces or Supabase Storage may be faster. If your main problem is the upload developer experience, UploadThing may save more engineering time than a raw bucket.
Do not publish a final recommendation without confirming current pricing, region availability, request charges, egress rules, and provider limits. The best platform is the one that matches the file lifecycle your SaaS product actually has.