Lewati ke konten utama

Penyimpanan Cloud

Upload file langsung ke penyedia penyimpanan cloud tanpa melewati server Anda.

Amazon S3

Instalasi

npm install @samithahansaka/dropup

Penggunaan Dasar

import { useDropup } from '@samithahansaka/dropup';
import { createS3Uploader } from '@samithahansaka/dropup/cloud/s3';

function S3Uploader() {
const { files, actions, getDropProps, getInputProps } = useDropup({
upload: createS3Uploader({
getPresignedUrl: async (file) => {
// Panggil backend Anda untuk mendapatkan presigned URL
const response = await fetch('/api/s3/presign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
return response.json();
},
}),

onUploadComplete: (file) => {
console.log('Terupload ke S3:', file.uploadedUrl);
},
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Lepas file untuk upload ke S3</p>
</div>
);
}

Backend: Generate Presigned URL

// Contoh Node.js / Express
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});

app.post('/api/s3/presign', async (req, res) => {
const { filename, contentType } = req.body;
const key = `uploads/${Date.now()}-${filename}`;

const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key,
ContentType: contentType,
});

const url = await getSignedUrl(s3, command, { expiresIn: 3600 });

res.json({
url,
fields: {}, // Untuk PUT sederhana, tidak perlu field tambahan
});
});

S3 dengan POST (Multipart Form)

Untuk kebijakan POST S3:

createS3Uploader({
getPresignedUrl: async (file) => {
const response = await fetch('/api/s3/presign-post', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});

const { url, fields } = await response.json();

return {
url, // URL bucket S3
fields, // Field kebijakan untuk disertakan dalam form
};
},
});

Google Cloud Storage

import { createGCSUploader } from '@samithahansaka/dropup/cloud/gcs';

function GCSUploader() {
const { files, getDropProps, getInputProps } = useDropup({
upload: createGCSUploader({
getSignedUrl: async (file) => {
const response = await fetch('/api/gcs/sign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
return response.json();
},
}),
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Upload ke Google Cloud Storage</p>
</div>
);
}

Backend: GCS Signed URL

// Contoh Node.js
import { Storage } from '@google-cloud/storage';

const storage = new Storage();
const bucket = storage.bucket(process.env.GCS_BUCKET);

app.post('/api/gcs/sign', async (req, res) => {
const { filename, contentType } = req.body;
const blob = bucket.file(`uploads/${Date.now()}-${filename}`);

const [url] = await blob.getSignedUrl({
version: 'v4',
action: 'write',
expires: Date.now() + 15 * 60 * 1000, // 15 menit
contentType,
});

res.json({ url });
});

Azure Blob Storage

import { createAzureUploader } from '@samithahansaka/dropup/cloud/azure';

function AzureUploader() {
const { files, getDropProps, getInputProps } = useDropup({
upload: createAzureUploader({
getSasUrl: async (file) => {
const response = await fetch('/api/azure/sas', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
return response.json();
},
}),
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Upload ke Azure Blob Storage</p>
</div>
);
}

Backend: Azure SAS URL

// Contoh Node.js
import {
BlobServiceClient,
generateBlobSASQueryParameters,
BlobSASPermissions,
} from '@azure/storage-blob';

const blobServiceClient = BlobServiceClient.fromConnectionString(
process.env.AZURE_STORAGE_CONNECTION_STRING
);

app.post('/api/azure/sas', async (req, res) => {
const { filename, contentType } = req.body;
const containerClient = blobServiceClient.getContainerClient('uploads');
const blobName = `${Date.now()}-${filename}`;
const blobClient = containerClient.getBlockBlobClient(blobName);

const sasToken = generateBlobSASQueryParameters(
{
containerName: 'uploads',
blobName,
permissions: BlobSASPermissions.parse('cw'), // Create, Write
expiresOn: new Date(Date.now() + 15 * 60 * 1000),
},
blobServiceClient.credential
).toString();

res.json({
url: `${blobClient.url}?${sasToken}`,
headers: {
'x-ms-blob-type': 'BlockBlob',
'Content-Type': contentType,
},
});
});

Cloudflare R2

R2 kompatibel dengan S3, jadi gunakan uploader S3:

import { createS3Uploader } from '@samithahansaka/dropup/cloud/s3';

function R2Uploader() {
const { files, getDropProps, getInputProps } = useDropup({
upload: createS3Uploader({
getPresignedUrl: async (file) => {
const response = await fetch('/api/r2/presign', {
method: 'POST',
body: JSON.stringify({ filename: file.name }),
});
return response.json();
},
}),
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Upload ke Cloudflare R2</p>
</div>
);
}

Backend: R2 Presigned URL

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({
region: 'auto',
endpoint: `https://${process.env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});

app.post('/api/r2/presign', async (req, res) => {
const { filename } = req.body;

const command = new PutObjectCommand({
Bucket: process.env.R2_BUCKET,
Key: `uploads/${Date.now()}-${filename}`,
});

const url = await getSignedUrl(s3, command, { expiresIn: 3600 });
res.json({ url });
});

DigitalOcean Spaces

Juga kompatibel dengan S3:

// Sama seperti S3, hanya perlu update konfigurasi endpoint backend Anda
const s3 = new S3Client({
region: 'nyc3',
endpoint: 'https://nyc3.digitaloceanspaces.com',
credentials: {
accessKeyId: process.env.DO_SPACES_KEY,
secretAccessKey: process.env.DO_SPACES_SECRET,
},
});

Penyedia Cloud Kustom

Buat uploader Anda sendiri untuk layanan cloud apa pun:

import { useDropup, type CustomUploader } from '@samithahansaka/dropup';

const customCloudUploader: CustomUploader = async (file, options) => {
// 1. Dapatkan URL upload dari backend Anda
const { uploadUrl, fileUrl } = await fetch('/api/custom-cloud/init', {
method: 'POST',
body: JSON.stringify({ filename: file.name, size: file.size }),
}).then(r => r.json());

// 2. Upload file
const xhr = new XMLHttpRequest();

return new Promise((resolve, reject) => {
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
options.onProgress((e.loaded / e.total) * 100);
}
};

xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({ url: fileUrl });
} else {
reject(new Error('Upload gagal'));
}
};

xhr.onerror = () => reject(new Error('Error jaringan'));

// Tangani pembatalan
options.signal.addEventListener('abort', () => xhr.abort());

xhr.open('PUT', uploadUrl);
xhr.send(file.file);
});
};

function CustomCloudUploader() {
const { files, getDropProps, getInputProps } = useDropup({
upload: customCloudUploader,
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Upload ke cloud kustom</p>
</div>
);
}

Praktik Terbaik Keamanan

  1. Jangan pernah ekspos kredensial di klien - Selalu generate signed URL di backend Anda
  2. Gunakan waktu kedaluwarsa singkat - 5-15 menit biasanya cukup
  3. Validasi tipe file di backend - Jangan hanya mengandalkan validasi sisi klien
  4. Atur kebijakan CORS yang tepat pada penyimpanan cloud Anda
  5. Batasi ukuran file dalam kebijakan presigned URL Anda
  6. Gunakan bucket terpisah untuk upload pengguna vs. aset aplikasi