Private avatars in a Node.js SaaS: which object storage, and how to sign downloads
Use a private bucket with short-lived presigned URLs when an avatar belongs to exactly one user, and reach for a public CDN-backed bucket only when the images are genuinely public and you'd rather pay for cache hits than for signatures. For a Node.js SaaS that is the entire decision, and everything after it is plumbing: which S3-compatible provider you point at, how long a signature should live, and what happens to the stored object on the day a user deletes their account. Avatars are small. That removes half the hard problems. The half that's left is the half I get paged for, because an avatar key is written by an untrusted client, read on nearly every page render, cached in three places you don't control, and referenced from a database row that has its own opinion about which object is current. So the questions I ask a storage vendor aren't about upload throughput. They're about whether a partial write can ever be visible to a reader, what the durability number is actually measuring, and how I reconcile the bucket with my user table after a failed deploy. I've never watched a team lose avatar bytes. I've watched several lose track of which bytes were current, which is the same outage with a friendlier root-cause section. How should a Node.js SaaS store private user avatars in object storage? Three moves, in this order. Create one private bucket for the whole tenant base, write each avatar under a key that carries a random component, and mint a presigned GET at display time instead of persisting any URL. Store the key in your database, on the user row, and nothing else, because keys are stable and signatures expire — a URL you saved last Tuesday is a support ticket waiting to happen. Serving the image then costs you one signing call per render, which you can cache in Redis for slightly less than the signature's own lifetime. That random component does more work than it looks like it does. Overwriting a fixed path like users/8821/avatar.png puts you in a read-modify