Access stored files
List, signed download URLs, public URLs, and delete via /api/storage.
After upload, objects stay in a private bucket. Access is controlled in the API: only the owning user can list, sign, or delete their READY files.
List files
GET /api/storage/Requires auth. Returns the current user’s files with status READY.
Signed download URL
GET /api/storage/signed?path=users/{userId}/….pdfUses getSignedUrl from @repo/storage (default expiry 15 minutes). Anyone who has the URL can fetch the object until it expires — keep TTLs short and only return URLs to authorized callers.
const res = await fetch(
`/api/storage/signed?path=${encodeURIComponent(filePath)}`,
{ credentials: "include" },
);
const { url } = await res.json();
window.open(url, "_blank");You can also call getSignedUrl from server code in @repo/storage when building your own handlers:
import { getSignedUrl } from "@repo/storage";
const { url } = await getSignedUrl({
path: filePath,
expiresIn: 60, // seconds
});Public URL
GET /api/storage/public?path=…Requires S3_PUBLIC_URL to be set. Returns a stable public URL (getPublicUrl) instead of a time-limited signature. Use only when the object is intentionally public via r2.dev or a custom CDN domain.
Prefer signed URLs
For private user files, use /api/storage/signed. Leave S3_PUBLIC_URL
unset unless you need public assets.
Delete
DELETE /api/storage/:idDeletes the object in the bucket (deleteObject) and marks the Prisma row DELETED. Only the owner can delete.
Optional redirect proxy
If you want browser-facing paths like /files/... instead of handing signed URLs to the client, you can add a Next.js route that checks auth, calls getSignedUrl, and redirects. That pattern is not shipped in this repo — keep ownership checks in the handler and consider Cache-Control if the content is safe to cache briefly.
Related
- Upload files
- Setup
- API — Hono module conventions