TurboJet
Storage

Unggah file

URL unggah terpresign, konfirmasi, dan sketsa client untuk /api/storage.

Sebelum mengunggah, selesaikan Setup. Unggahan memakai URL terpresign agar file tidak lewat body fungsi serverless Anda.

Alur

sequenceDiagram
  participant Client
  participant Api as ApiStorage
  participant R2 as S3OrR2
  Client->>Api: GET /api/storage/upload
  Api->>Api: Create File PENDING
  Api-->>Client: url plus fileId
  Client->>R2: PUT file to presigned url
  Client->>Api: POST /api/storage/confirm
  Api->>Api: File READY
  1. Client terautentikasi memanggil GET /api/storage/upload dengan nama file, content type, dan size opsional.
  2. API membuat Prisma File berstatus PENDING di users/{userId}/{uuid}.{ext} dan mengembalikan URL PUT terpresign plus fileId.
  3. Client PUT byte file ke URL itu dengan Content-Type yang cocok.
  4. Client memanggil POST /api/storage/confirm dengan { fileId } agar baris menjadi READY.

Route

Method / pathAuthRate limitInput
GET /api/storage/uploadWajibWRITEQuery: filename, contentType, size opsional
POST /api/storage/confirmWajibWRITEJSON: { fileId }

Validasi ada di packages/validations/src/storage/storage.ts:

  • Tipe diizinkan: image/jpeg, image/png, image/gif, image/webp, application/pdf, text/plain
  • Ukuran maks: 10 MB (saat size dikirim)

Router: packages/api/src/modules/storage/router.ts. Service: packages/api/src/modules/storage/service.ts.

Sketsa client

async function uploadFile(file: File) {
	const params = new URLSearchParams({
		filename: file.name,
		contentType: file.type,
		size: String(file.size),
	});

	const prepare = await fetch(`/api/storage/upload?${params}`, {
		credentials: "include",
	});
	if (!prepare.ok) throw new Error("Failed to get upload URL");

	const { url, fileId } = (await prepare.json()) as {
		url: string;
		fileId: string;
	};

	const put = await fetch(url, {
		method: "PUT",
		body: file,
		headers: { "Content-Type": file.type },
	});
	if (!put.ok) throw new Error("Failed to upload file");

	const confirm = await fetch("/api/storage/confirm", {
		method: "POST",
		credentials: "include",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify({ fileId }),
	});
	if (!confirm.ok) throw new Error("Failed to confirm upload");

	return confirm.json();
}

Pakai file input atau dropzone pilihan Anda, lalu panggil uploadFile. Cookie sesi harus dikirim (credentials: "include") agar Better Auth mengautentikasi request.

Memperketat aturan

Ubah tipe yang diizinkan atau batas 10 MB di packages/validations/src/storage/storage.ts. Kepemilikan path sudah ditegakkan dengan menghasilkan users/{userId}/... di server — client tidak memilih object key.

Berikutnya

Pelajari cara mengakses file tersimpan dengan URL ter-sign atau publik.

Di halaman ini