Traditionally, implementing file uploads followed a simple but flawed path: you'd send a multi-part form from the frontend to your server, and the server would then pass that data to storage.
In 2026, that's a bottleneck you don't need. Modern web applications have shifted to Direct Client-Side Uploads.
But wait — doesn't uploading from the client mean exposing database credentials or storage API keys? Isn't that a security nightmare?
The answer is no. The modern approach uses Temporary Signed URLs. These are short-lived, cryptographically signed links that grant just enough permission to upload one specific file. When configured correctly, they are incredibly secure and virtually impossible to hijack.
In this edition of G3 Labs, we're breaking down why you should switch to client-side uploads and exactly how to implement them.
Why bother? The 3 Big Wins
Before we dive into the How, let's look at the Why:
- Zero "Double-Hopping": In the traditional way, a file is uploaded twice (User → Server, then Server → Cloud). This creates two failure points. If the connection blinks, the whole process restarts.
- Unblock Your Backend: Large files (like 4K videos) can hang your Node.js event loop or saturate your server's RAM. Direct upload keeps your backend lean and responsive.
- Lower Infrastructure Costs: You pay for bandwidth. By bypassing the server, you cut your data transfer costs in half and reduce the CPU load on your instances.
How it Works: The Architecture
To help you visualize the flow, here is the sequence of events (get used to sequence diagrams at G3 Labs, 'm going to use these alot :)
Sequence Diagram of client-side file upload
The Step-by-Step:
- Metadata Exchange: When a user selects a file, the frontend sends the metadata (filename, type, size) to your backend.
- The Signature: Your backend uses a private key (stored safely in environment variables) to generate a Signed URL.
- The Direct Push: The frontend receives the URL and performs a PUT or POST request directly to the Cloud Storage (S3, Cloudinary, etc.).
- The Handshake: Once the upload hits 100%, the frontend sends the unique "File Key" back to your backend to save it in your database alongside your other form data.
"But what about Image Optimization?"
You might wonder: "If the file goes straight to the cloud, how do I resize it or generate thumbnails?"
Modern apps handle this asynchronously. As soon as the file lands in storage, it triggers a background worker (like an AWS Lambda or a Webhook). This worker performs the optimization while the user continues browsing.
YouTube is the perfect example. When you upload a video, it's sent raw from your browser. Once the upload bar hits 100%, you see that "Processing" status. That's the backend working its magic in the background while the frontend is already "done".
The Reality Check: When to Stick with Server-Side
While client-side is the king of scalability, there are specific scenarios where the "Traditional Approach" is actually the better choice. At G3 Labs, we don't believe in "one size fits all".
1. Immediate, High-Stakes Validation If you need to inspect the content of a file before it even touches your storage (e.g., checking for malware, verifying specific EXIF data, or deep-parsing a CSV), doing it on your server gives you immediate control to reject the request before the transfer is "complete".
2. Handling Small Files & Metadata Complexity If your app only deals with tiny files (like 20KB profile icons) and involves complex database logic that must happen at the exact same time as the upload, a single multipart/form-data request is often simpler to manage than the multi-step "Signed URL" upload.
3. Legacy System Constraints Sometimes, you're working with older on-premise storage systems that don't support CORS or Pre-signed URLs. In those cases, your backend acts as a necessary bridge.
The "Hidden" Infrastructure Wall
Beyond performance, there is a hard technical limit you might hit. Most modern cloud providers and "Edge" platforms now impose strict Request and Response size limits (often as low as 4.5MB to 10MB).
If you try to pipe a 50MB video through a standard Serverless backend, your request will likely be killed before it even finishes. Client-side uploads bypass these "middleman" limits entirely, allowing you to handle files of virtually any size by talking directly to the storage bucket.
Want to see a full, working MERN implementation of this flow? Comment below, and I'll put together a deep-dive code example for the next edition!