What "just use S3" involves for user uploads
Amazon S3 and S3-compatible stores are excellent at storing objects. Accepting files from untrusted users in a browser is a separate problem, and you end up building a small product around the bucket. A typical do-it-yourself setup includes:
- Presigned URLs: server code with the cloud SDK to sign PUT requests with the right content type, length and expiry.
- CORS rules on the bucket so browsers may PUT to it from your domains, and debugging them when they fail.
- Multipart uploads for large files: create, sign each part, collect ETags, complete, and abort or clean up the ones users abandon.
- A CDN in front of the bucket, with origin access configured so the bucket itself stays private.
- Upload events: bucket notifications to a queue or function so your app knows when a file arrived.
- Access control for private files: signed GET URLs and the key management behind them.
- IAM policies scoped tightly enough that a leaked credential can't read everything.
None of it is hard on its own, but together it is days of work and a set of moving parts to monitor, and bandwidth is usually billed per gigabyte on top of storage.
The same flow as a hosted API
The Rapidfile Upload API packages that list into a few REST calls. Your server creates an upload with a secret key, the browser uploads to a signed URL, multipart is handled by the SDK, public files get a CDN URL, private files get signed URLs, and a signed webhook tells your backend when a file is ready.
| Need | Rapidfile Upload API |
|---|---|
| Signed browser uploads | POST /v1/uploads returns a URL valid for 1 hour |
| CORS | Open on every /v1 endpoint and upload URL; nothing to configure |
| Large files | Automatic multipart above 95 MB, parallel parts and retries in the SDK |
| Delivery | Permanent links on cdn.rapidfile.cc |
| Private files | acl: "private" + POST /v1/files/:id/signed-url (up to 7 days) |
| Events | file.uploaded / file.deleted webhooks with HMAC-SHA256 signatures |
| Credentials | One secret key per app, several keys for rotation, revocable in the dashboard |
| Price | Fixed monthly plans, no bandwidth fees |
What the code looks like
The server side is a single fetch, no cloud SDK and no signing code:
// app/api/upload/route.ts: runs on your server, so the secret key stays private
import { auth } from '@/lib/auth'; // your own session check
export async function POST(req: Request) {
const user = await auth(req);
if (!user) return Response.json({ error: 'unauthorized' }, { status: 401 });
const { name, size, type } = await req.json();
const res = await fetch('https://api.rapidfile.cc/v1/uploads', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RAPIDFILE_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, size, type, metadata: { userId: user.id } }),
});
return Response.json(await res.json(), { status: res.status });
}// server.js: Express creates the upload, the browser sends the bytes
import express from 'express';
const app = express();
app.use(express.json());
app.post('/api/upload', requireUser, async (req, res) => {
const { name, size, type } = req.body;
const r = await fetch('https://api.rapidfile.cc/v1/uploads', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RAPIDFILE_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, size, type, metadata: { userId: req.user.id } }),
});
res.status(r.status).json(await r.json());
});
app.listen(3000);// In the browser: no SDK, no API key
const picked = document.querySelector('input[type=file]').files[0];
// 1. Ask your own server for an upload URL
const { upload } = await fetch('/api/upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: picked.name, size: picked.size, type: picked.type }),
}).then((r) => r.json());
// 2. Send the bytes straight to Rapidfile (files up to 95 MB use one PUT)
const res = await fetch(upload.url, { method: 'PUT', body: picked });
const { file } = await res.json();
console.log(file.url); // https://cdn.rapidfile.cc/<id>/<name># 1. Create an upload (server side, with your secret key)
curl https://api.rapidfile.cc/v1/uploads \
-H "Authorization: Bearer $RAPIDFILE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"cat.jpg","size":48213,"type":"image/jpeg"}'
# 2. PUT the bytes to upload.url from the response (no key needed)
curl -T cat.jpg "$UPLOAD_URL"
# Or upload from your server in one request (up to 95 MB)
curl -T cat.jpg -H "Content-Type: image/jpeg" \
-H "Authorization: Bearer $RAPIDFILE_SECRET_KEY" \
"https://api.rapidfile.cc/v1/files?name=cat.jpg"When raw object storage is still the right call
Be honest about the trade-off. If you need lifecycle rules, storage classes, cross-region replication, data residency in a specific region, analytics queries over the bucket, or files that are produced and consumed only by your own backend, a general object store gives you control that an upload API intentionally hides. A hosted upload API is the better fit when the files come from your users, are shown back to users, and you would rather ship features than maintain the pipeline. You can also mix both: user uploads in Rapidfile, internal data in your bucket.
Moving existing uploads
New uploads can switch over by replacing your presign endpoint. For files you already have, a one-off script can stream each object
from your bucket and send it with PUT /v1/files?name=… (up to 95 MB per request) or create a multipart upload for larger
ones, then store the new id and URL. Keep the old URLs working until your database points at the new ones.
Frequently asked questions
Is Rapidfile S3-compatible?
No. It is a REST API built for user uploads, not an S3 protocol implementation. Your server calls a few JSON endpoints instead of using an S3 SDK.
Do I need to configure CORS?
No. The API and the signed upload URLs allow cross-origin requests from any site.
How do I know when an upload finished?
The upload request itself returns the ready file, and your webhook receives a signed file.uploaded event.
Are bandwidth fees charged?
No. Plans include storage and a daily number of CDN requests; bandwidth is not billed.
Can I export my files later?
Yes. List files with GET /v1/files and download each one from its URL (or a signed URL for private files).