引言
当 Node.js SaaS 应用只需要处理头像照片时,对象存储看起来很简单。但当产品扩展到发票、导出文件、PDF、合同、用户上传、导入文件、缩略图、生成报表、支持附件、视频片段和租户专属文档时,它就变成了基础设施。
到了这个阶段,问题不再是“如何用 Express 上传文件?”,而是:哪个存储平台能在上传易用性、访问控制、CDN 分发、S3 兼容性、成本可预测性、合规要求与运维可见性之间提供最佳组合?
本指南比较了 2026 年 Node.js SaaS 应用中常见的对象存储和文件上传平台:AWS S3、Cloudflare R2、Backblaze B2、DigitalOcean Spaces、Supabase Storage 和 UploadThing。本文面向正在选择生产基础设施的团队,而非一次性教程项目。
快速对比表
| 平台 | 最佳适用场景 | Node.js 集成方式 | 成本结构 | 主要权衡 |
|---|---|---|---|---|
| AWS S3 | 企业级 AWS 技术栈、成熟的合规要求、长期持久性 | JS SDK v3、S3 签名 URL、IAM | 存储、请求、数据检索、传输、生命周期层级 | 功能强大,但定价与权限可能变得复杂 |
| Cloudflare R2 | 公共资源、SaaS 下载、对流量费用敏感的工作负载 | 兼容 S3 API、Workers、AWS SDK 兼容客户端 | 存储 + A/B 类操作;截至撰文时,直接 R2 出站流量免费 | 某些工作流仍需考虑 Cloudflare 特有模式 |
| Backblaze B2 | 可预测的备份、归档、媒体及活跃对象存储 | 兼容 S3 API 及原生 B2 工具 | 按 TB/月起;免费出站规则与超大规模云平台有显著不同 | 周边云服务少于 AWS |
| DigitalOcean Spaces | 已在使用 DigitalOcean 的小型 SaaS 团队 | 兼容 S3 API,内置 CDN | 简单的月度套餐,包含存储与出站流量 | 企业级存储架构的控制粒度不如 AWS |
| Supabase Storage | 已在使用 Supabase Auth、Postgres 和 RLS 的应用 | Supabase JS 客户端、S3 兼容、REST、TUS | 纳入 Supabase 平台定价与配额;发布前需确认 | 最适合需要集成化应用后端行为,而非仅是原始对象存储的场景 |
| UploadThing | 需要上传 UX 与路由的全栈 TypeScript 应用 | 第一方框架适配器及后端适配器(包括 Express/Fastify) | 开发者平台定价;发布前需确认 | 更偏向上传层,而非通用云对象存储 |
Node.js SaaS 团队应评估的要点
一个生产级文件管道比存储桶涉及更多环节。在对比服务商之前,先明确实际工作负载。
公共资源
对于文档图片、公开下载、市场文件或公共产品媒体,最重要的变量是 CDN 行为、出站流量价格、缓存失效、热对象读取量 和 部署简便性。
用户私有上传
对于文档、合同、支持附件、发票或租户专属导出,关键变量是 访问控制、签名 URL 有效期、恶意软件扫描、文件类型验证、保留策略、审计日志 和 租户隔离。
生成文件
对于报告、账单导出、PDF 发票、AI 生成资源和后台任务输出,关键变量是 写入吞吐量、生命周期清理、对象命名、幂等性,以及队列工作器如何将最终文件状态记录在数据库中。
媒体密集型产品
存储选择还应考虑 CDN、图像优化、视频处理、转换成本,以及派生文件是作为独立对象存储还是动态生成。
AWS S3:默认的企业级对象存储选项
AWS S3 仍然是默认基准,因为它足够成熟、全球可用,并与 IAM、Lambda、CloudFront、EventBridge、Glue、Athena、生命周期策略、复制、对象锁定及众多合规工作流深度集成。如果你的 Node.js SaaS 应用已运行在 AWS 上,S3 往往能最小化平台风险,即使它不是价格最直观的方案。
适用于 JavaScript v3 的 AWS SDK 提供了常见操作的 S3 示例,如列出存储桶、创建存储桶、上传对象、下载对象、复制对象和删除对象。对于 Node.js 应用,常见的生产路径是让后端签发一个短生命周期的签名上传 URL,然后让浏览器或移动客户端直接上传到 S3。后端将元数据存储在 PostgreSQL 或其他应用数据库中。
典型架构:
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",
})
);
}
在生产环境中,除非有特殊原因,不要将大文件通过 Express 的内存缓冲区传输。应使用流式上传、分片上传或直接签名上传。你的 Node.js API 应负责策略决策:哪个租户可以上传,允许哪些文件类型,适用的最大尺寸,以及哪个后台工作流应扫描或转换对象。
其缺点是定价复杂。AWS S3 的定价包含存储类别、请求费用、部分类别的数据检索费用、生命周期转换、复制、加速、对象元数据功能以及数据传输费用。AWS 指出,S3 请求成本取决于请求类型和数量,且 DELETE 和 CANCEL 请求是免费的。其示例还显示,从 S3 存储桶向外传输到互联网可能产生额外的传输费用。在正式发布前,请确认所有区域的定价。
选择 AWS S3 的条件: 你需要深度 AWS 集成、企业级控制、灵活的生命周期、合规选项以及长期的平台成熟度。
Cloudflare R2:应对流量敏感的 SaaS 资源
Cloudflare R2 对需要向用户提供大量文件且希望摆脱出口流量焦虑的 SaaS 团队非常有吸引力。Cloudflare 的 R2 定价页面说明,R2 按存储数据和两类操作收费,并且所有存储类别从 R2 直接出站都不收取出口带宽费。它还列出了 Standard 存储的免费层级,截至撰文时包含每月 10 GB 存储、100 万次 A 类操作和 1000 万次 B 类操作。
对 Node.js 团队而言,R2 的实用优势在于它支持 S3 API。这意味着许多 AWS SDK 模式只需更改端点配置和凭证即可适配。Cloudflare 也提供了在 aws-sdk-js-v3 中使用 R2 的示例。
R2 特别适合:
- 可能产生高额出站流量的客户下载
- 通过 Cloudflare 网络就近向用户提供的公共资源
- 难以预测出口成本的 SaaS 导出、报告和生成文件
- 已在使用 Cloudflare Workers、Pages、DNS、WAF 或 CDN 的产品
需要权衡的是,S3 兼容并不代表所有 AWS 周边服务都以相同方式存在。如果你的架构高度依赖 AWS IAM 语义、S3 事件工作流、Athena、Glue、Macie 或深度的 AWS 账户控制,应仔细对比两者的运维模型。
选择 Cloudflare R2 的条件: 出口流量的可预测性和 Cloudflare 边缘集成比完整的 AWS 生态深度更重要。
Backblaze B2:适合预算敏感型团队的可预测对象存储
Backblaze B2 是希望获得兼容 S3 的存储,同时拥有比超大规模云更简单成本结构的团队的理想选择。Backblaze 的定价页面显示,B2 云存储起价为 $6.95/TB/月,并指出用户每月可获得平均存储量 3 倍的免费出站流量,超出部分截至撰文时按 $0.01/GB 计费。它还强调提供免费的事务请求。
这种定价模式对那些存储大量用户生成内容、备份、媒体或导出文件,但不需要整个 AWS 平台围绕存储桶的 Node.js SaaS 应用非常有用。
B2 的典型使用场景包括:
- 客户备份与恢复文件
- 读取流量适中的媒体库
- 应用生成的归档文件
- 希望获得可预测存储账单的 SaaS 产品
- 作为 CDN 或计算服务后端的二级存储
主要权衡在于生态的广度。AWS 提供了庞大的相邻服务宇宙,而 Backblaze B2 提供的是对象存储、S3 兼容性和成本可预测性,但你可能需要自行组装更多的基础设施。
选择 Backblaze B2 的条件: 存储量和可预测账单至关重要,且团队能够从容地利用其他服务组合认证、CDN、计算、扫描和可观测性。
DigitalOcean Spaces:面向小型 SaaS 团队的简易捆绑式对象存储
对于已将 Node.js 应用部署在 DigitalOcean Droplets、App Platform、托管数据库或 Kubernetes 上的小型 SaaS 团队,DigitalOcean Spaces 是一个务实的选择。该产品兼容 S3 并内置 CDN。DigitalOcean 的定价页面显示,Spaces 对象存储每月 $5,包含 250 GiB 存储和 1 TiB 出站流量,截至撰文时,额外存储和流量单独计价。
主要优势在于简单性。一个小团队可以在一个云账户中管理应用服务器、数据库、对象存储、CDN 和账单关系。在 SaaS 产品早期阶段,这通常比理论上的成本优化更重要。
Spaces 适合:
- MVP 和早期 SaaS 产品
- 图片上传和可下载文件
- 已使用 DigitalOcean 基础设施的应用
- 希望获得简单月度基准开销,而非复杂云账单的团队
其局限性在于,大型或对合规要求严格的系统可能最终需要更精细的控制、多区域策略、对象锁定功能或专项数据治理。但对许多小型 Node.js SaaS 产品而言,Spaces 足以支撑第一个严肃的生产版本。
选择 DigitalOcean Spaces 的条件: 你希望获得可预测的搭建体验,并且已在其他技术栈中使用 DigitalOcean。
Supabase Storage:面向以 Postgres 为核心的 SaaS 应用的集成存储
Supabase Storage 不仅仅是一个存储桶。它是一个更广泛后端平台的一部分,包含 Postgres、Auth、API、Edge Functions、Realtime 和策略驱动的访问控制。Supabase 的存储文档描述了适用于任意大小文件的存储、细粒度访问控制、优化分发、S3 兼容存储、RESTful API 访问、TUS 断点续传、全球 CDN、图像优化以及与行级安全集成。
这使得当你的 Node.js 或全栈 TypeScript 应用已使用 Supabase Auth 和 Postgres 时,它特别有用。你无需构建独立的身份到存储桶权限层,而是可以将存储授权紧密贴合应用记录和策略。
Supabase Storage 适合:
- 已使用 Supabase Auth 和 Postgres 的 SaaS 应用
- 与数据库行关联的用户生成文件
- 需要行级安全对齐的应用
- 受益于内置图像转换和 CDN 行为的项目
- 偏好集成后端平台的小团队
需要权衡的是,你选择的是平台模式,而不仅仅是原始存储商品。如果你的应用未来可能转向多云架构,或需要非常特定的对象存储控制,应在 Node.js 应用中定义抽象层,以确保迁移的可行性。
选择 Supabase Storage 的条件: 产品速度、Postgres 集成、感知认证的策略和内置媒体处理比原始对象存储的可移植性更重要。
UploadThing:面向全栈 TypeScript 应用的上传开发体验
UploadThing 属于另一类别。它并非主要提供与 S3、R2、B2 或 Spaces 相同的通用对象存储桶服务,而是一个面向 TypeScript 应用的、以开发者为中心的文件上传层。其文档将其描述为一种为全栈 TypeScript 应用添加上传功能的方式,并列出了框架支持,以及 Express 和 Fastify 等后端适配器。
这很重要,因为许多 SaaS 团队需要的不仅仅是存储。他们需要上传路由、前端组件、文件验证、回调、认证钩子和更简洁的开发体验。围绕原始 S3 从头构建这些功能对大型团队或许可行,但对小型团队往往更耗时。
UploadThing 适合:
- Next.js、Astro、Remix 或全栈 TypeScript 应用
- 希望获得文件上传产品而非存储原语构建项目的团队
- 需要快速实现上传路由和验证的应用
- 速度比平台抽象更重要的早期 SaaS 项目
需要权衡的是控制力。如果你需要完全掌控每一个存储行为、区域规则、存储桶策略、生命周期策略和 S3 兼容集成,那么更底层的对象存储服务可能更合适。
选择 UploadThing 的条件: 你的主要痛点是上传实现的速度,而非原始存储架构。
Node.js 文件上传的推荐架构
对于大多数生产级 SaaS 应用,请采用以下模式:
- 客户端向你的 Node.js API 请求上传权限。
- Node.js API 对用户进行身份验证,并检查租户级别的授权。
- API 在数据库中创建一条状态为
pending_upload的记录。 - API 返回一个签名上传 URL 或上传令牌。
- 客户端直接上传到存储或托管上传服务。
- Webhook、回调、队列工作器或轮询任务验证上传完成。
- 后台工作器对文件进行扫描、转换、生成缩略图或建立索引。
- 数据库记录被标记为
ready、rejected或quarantined。 - 根据文件的敏感性,使用签名 URL、CDN URL 或授权代理进行下载。
这样做既避免了大流量负载经过 Node.js 服务器,又确保了应用的策略决策权。
发布前需确认的成本因素
云存储账单常常让人意外,因为存储仅仅是其中的一部分。在给出最终建议前,务必确认以下内容:
| 成本因素 | 为何重要 |
|---|---|
| 每 GB/TB/月存储费 | 留存文件的基本开销 |
| 最小对象尺寸 | 小缩略图或事件文件可能变得昂贵 |
| 最短存储时长 | 在归档或不频繁访问层中,提前删除仍可能产生费用 |
| PUT/LIST/GET 请求 | 高密度对象可能产生请求密集型工作负载 |
| 出站流量 | 公开下载和客户导出可能成为主要成本 |
| CDN 行为 | CDN 缓存命中可减少源站请求和出站流量 |
| 图像转换 | 动态调整尺寸可能增加计算或转换费用 |
| 分片上传残留 | 失败上传可能留下未完成的分块而未清理 |
| 生命周期转换 | 在不同层级间移动文件可能产生费用并增加检索限制 |
| 监控和审计日志 | 生产所需,但可能增加存储和摄取成本 |
实用推荐
- 如果 SaaS 产品已运行在 AWS 上,需要企业级控制,或预期与众多 AWS 服务集成,请使用 AWS S3。
- 如果主要关注公共文件分发和出站流量可预测性,尤其已使用 Cloudflare 提供 DNS、CDN、Workers 或安全服务,请使用 Cloudflare R2。
- 如果你存储大量数据,并期望更简单的存储经济学和 S3 兼容性,请使用 Backblaze B2。
- 如果你希望获得简单捆绑的对象存储服务,并且已在 DigitalOcean 上托管 Node.js,请使用 DigitalOcean Spaces。
- 如果应用已围绕 Supabase Auth、Postgres 和行级安全构建,请使用 Supabase Storage。
- 如果核心瓶颈是 TypeScript 应用中干净上传工作流的构建,而非操作原始对象存储,请使用 UploadThing。
结论
对 Node.js SaaS 产品而言,文件存储并非一个小小的工具决策。它影响着基础设施成本、安全性、用户体验、支持工作流、租户隔离和长期可移植性。
最稳妥的选择方式是依据工作负载形态出发。如果需要企业级 AWS 集成,从 S3 开始。如果出口流量是核心顾虑,仔细对比 R2 与 B2。若希望获得简单的初创技术栈,DigitalOcean Spaces 或 Supabase Storage 可能更快上手。如果主要痛点是上传开发体验,UploadThing 可能比原始存储桶节省更多工程时间。
在给出最终推荐前,切勿忽略确认当前定价、区域可用性、请求费用、出站规则和服务商限制。最合适的平台,是那个能真正匹配你的 SaaS 产品文件生命周期的方案。