TypeScript SDK

Mengunggah File (Upload)

Dua pola arsitektur unggah biner ke penyimpanan Cloudizero: unggah langsung dari server backend atau unggah langsung dari browser via Presigned URL tanpa membebani egress server Anda.

Pola 1: Unggah Langsung Backend (Node.js / Server Actions)

Gunakan cz.storage.upload() saat memproses buffer, stream file, atau file unduhan langsung di lingkungan server (Node.js, Express, Next.js Server Action, dsb.):

services/media.ts
import { Cloudizero } from "@cloudizero/sdk";
import fs from "fs";

const cz = new Cloudizero({
  apiKey: process.env.CLOUDIZERO_API_KEY!,
});

async function uploadReport() {
  const buffer = fs.readFileSync("./quarterly-report.pdf");

  const asset = await cz.storage.upload({
    file: buffer,
    fileName: "quarterly-report.pdf",
    projectId: "prj_LXAlEW70v24YSc0y",
    folderId: "fld_ARaZjwdPpUqXhDHf", // opsional: Folder ID (atau kosongkan untuk root)
    mimeType: "application/pdf",
  });

  console.log("Storage Key:", asset.storageKey || asset.r2Key);
  console.log("Delivery URL:", asset.url);
}

Pola 2: Unggah Klien Langsung (Browser Direct Upload via Presigned URL)

Untuk aplikasi web (React, Vue, Next.js) dengan file besar, jangan mengekspos API Key rahasia di browser. Gunakan pola tiket presigned: backend Anda membuat URL unggah presigned berdurasi terbatas, dan browser klien mengunggah biner langsung ke Cloudizero Storage Engine:

Keamanan: Lindungi Kunci API Anda
Jangan pernah menaruh CLOUDIZERO_API_KEY di kode sisi klien (NEXT_PUBLIC_*). Kunci rahasia harus selalu berada di server backend Anda.

Langkah 1: Endpoint Backend untuk Tiket Presigned & Penyelesaian

Buat endpoint API di backend Anda untuk menghasilkan tiket unggah presigned dan merekam metadata setelah unggahan selesai:

app/api/upload/presign/route.ts
// app/api/upload/presign/route.ts (Next.js App Router / Express)
import { NextResponse } from "next/server";
import { Cloudizero } from "@cloudizero/sdk";

const cz = new Cloudizero({
  apiKey: process.env.CLOUDIZERO_API_KEY!,
});

export async function POST(req: Request) {
  // 1. Authorize user session from cookies / auth token
  const { fileName, fileSize, mimeType, projectId } = await req.json();

  // 2. Generate secure signed direct-to-storage upload ticket
  const ticket = await cz.storage.createPresignedUpload({
    projectId,
    fileName,
    fileSize,
    mimeType,
  });

  return NextResponse.json(ticket);
}
app/api/upload/complete/route.ts
// app/api/upload/complete/route.ts (Next.js App Router)
import { NextResponse } from "next/server";
import { Cloudizero } from "@cloudizero/sdk";

const cz = new Cloudizero({
  apiKey: process.env.CLOUDIZERO_API_KEY!,
});

export async function POST(req: Request) {
  const body = await req.json();

  // Finalize asset & index metadata in Cloudizero Database
  const asset = await cz.storage.completeUpload({
    fileId: body.fileId,
    projectId: body.projectId,
    fileName: body.fileName,
    mimeType: body.mimeType,
    fileSize: body.fileSize,
    storageKey: body.storageKey,
  });

  return NextResponse.json(asset);
}

Langkah 2: Komponen Klien Browser dengan Bilah Progres (Progress Bar)

Browser meminta tiket presigned, melakukan HTTP PUT langsung ke Cloudizero Storage Engine dengan pelaporan progres bita yang akurat, lalu memberitahu backend untuk menyelesaikan:

components/direct-uploader.tsx
// components/direct-uploader.tsx
"use client";

import { useState } from "react";
import type { PresignedUploadResult, UploadedFile } from "@cloudizero/sdk";

export function DirectUploader({ projectId }: { projectId: string }) {
  const [progress, setProgress] = useState<number>(0);
  const [uploading, setUploading] = useState(false);
  const [cdnUrl, setCdnUrl] = useState<string | null>(null);

  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    setUploading(true);
    setProgress(0);

    try {
      // Step 1: Request signed upload ticket from your backend
      const presignRes = await fetch("/api/upload/presign", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          fileName: file.name,
          fileSize: file.size,
          mimeType: file.type || "application/octet-stream",
          projectId,
        }),
      });
      const ticket: PresignedUploadResult = await presignRes.json();

      // Step 2: Stream binary directly to Cloudizero Storage Engine (Zero-Egress)
      await new Promise<void>((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open("PUT", ticket.uploadUrl);
        xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");

        xhr.upload.onprogress = (evt) => {
          if (evt.lengthComputable) {
            setProgress(Math.round((evt.loaded / evt.total) * 100));
          }
        };

        xhr.onload = () => (xhr.status >= 200 && xhr.status < 300 ? resolve() : reject(new Error("PUT upload failed")));
        xhr.onerror = () => reject(new Error("Network error during direct upload"));
        xhr.send(file);
      });

      // Step 3: Notify backend to verify asset and register metadata
      const completeRes = await fetch("/api/upload/complete", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          fileId: ticket.fileId,
          projectId,
          fileName: ticket.finalFileName || file.name,
          mimeType: file.type || "application/octet-stream",
          fileSize: file.size,
          storageKey: ticket.storageKey,
        }),
      });
      const recorded: UploadedFile = await completeRes.json();
      setCdnUrl(recorded.url || null);
    } catch (err) {
      console.error("Direct upload failed:", err);
    } finally {
      setUploading(false);
    }
  };

  return (
    <div className="space-y-4">
      <input type="file" onChange={handleFileChange} disabled={uploading} />
      {uploading && (
        <div className="space-y-1">
          <p className="text-sm font-medium">Uploading: {progress}%</p>
          <div className="w-full bg-muted rounded-full h-2">
            <div className="bg-blue-600 h-2 rounded-full transition-all" style={{ width: `${progress}%` }} />
          </div>
        </div>
      )}
      {cdnUrl && (
        <a href={cdnUrl} target="_blank" rel="noreferrer" className="text-blue-600 text-sm font-medium">
          View uploaded media on CDN &rarr;
        </a>
      )}
    </div>
  );
}

Signature Metode & Parameter (`UploadOptions`)

PropertyTypeDefaultDescription
file*requiredFile | Blob | Buffer | Uint8Array—Muatan data biner. Menerima File/Blob browser asli atau Buffer Node.js.
fileName*requiredstring—Nama file asli beserta ekstensinya (mis. 'banner.webp').
projectId*requiredstring—ID proyek tujuan penyimpanan (Prefixed ID mis. 'prj_...').
folderIdstring | nullnull (root)ID folder virtual target tempat file disimpan.
mimeTypestringapplication/octet-streamTipe MIME untuk header Content-Type penyimpanan.
widthnumber | nullnullLebar gambar dalam piksel (metadata relasional opsional).
heightnumber | nullnullTinggi gambar dalam piksel (metadata relasional opsional).
onProgress({ loaded, total, percent }) => voidundefinedCallback progres unggah waktu-nyata (lingkungan browser).
autoExtractbooleanundefined (mengikuti setelan akun)Menentukan apakah dokumen PDF multi-halaman langsung diekstrak ke WebP saat diunggah.

Nilai Pengembalian (`UploadedFile`)

Promise mengembalikan objek UploadedFile saat pengunggahan dan pengindeksan database selesai:

PropertyTypeDefaultDescription
idstring—Pengidentifikasi unik basis data (Prefixed ID mis. 'fil_...').
namestring—Nama file yang tersimpan di katalog.
storageKeystring—Jalur key objek persis yang tersimpan di penyimpanan Cloudizero.
r2Keystring—Alias kompatibilitas mundur untuk storageKey.
sizenumber—Ukuran dalam bita (bytes).
mimeTypestring—Tipe media MIME.
urlstring | undefined—Tautan CDN publik resolusi penuh jika cdnUrl dikonfigurasi.