Cara Menggunakan External Storage untuk Aplikasi Stateless
Panduan menggunakan cloud storage seperti Cloudflare R2 dan AWS S3 untuk menyimpan file dari aplikasi di getlaunch.dev.
Kenapa Butuh External Storage?
Di getlaunch.dev, container bersifat stateless — file yang ditulis ke filesystem container akan hilang saat container restart atau re-deploy. Untuk menyimpan file secara permanen (gambar, dokumen, video, backup), kamu perlu cloud storage external.
Keuntungannya:
- Persistent — File tidak hilang saat container restart
- Scalable — Storage bisa tumbuh tanpa batas
- CDN-ready — File bisa di-serve langsung melalui CDN untuk akses cepat
- Cost-effective — Hanya bayar untuk storage yang digunakan
Opsi Cloud Storage
Cloudflare R2 (Rekomendasi)
Cloudflare R2 adalah S3-compatible object storage tanpa biaya egress:
- 10GB gratis per bulan
- Tidak ada biaya bandwidth (egress gratis!)
- S3-compatible API
- Global CDN built-in
Setup:
- Daftar di Cloudflare Dashboard
- Buka R2 di sidebar
- Buat bucket baru
- Buat API token di R2 → Manage R2 API Tokens
- Tambahkan credentials ke environment variables di getlaunch.dev:
R2_ACCOUNT_ID=your-account-id
R2_ACCESS_KEY_ID=your-access-key
R2_SECRET_ACCESS_KEY=your-secret-key
R2_BUCKET_NAME=your-bucket-name
R2_PUBLIC_URL=https://pub-xxxx.r2.dev
Contoh penggunaan di Node.js:
const { S3Client, PutObjectCommand, GetObjectCommand } = require("@aws-sdk/client-s3");
const s3 = new S3Client({
region: "auto",
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
// Upload file
async function uploadFile(key, body, contentType) {
await s3.send(new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME,
Key: key,
Body: body,
ContentType: contentType,
}));
return `${process.env.R2_PUBLIC_URL}/${key}`;
}
AWS S3
AWS S3 adalah object storage paling populer:
- 5GB gratis (12 bulan pertama)
- Sangat reliable (99.999999999% durability)
- Ekosistem AWS yang luas
Setup:
- Buat bucket di AWS Console
- Buat IAM user dengan akses S3
- Tambahkan credentials ke environment variables:
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=ap-southeast-1
S3_BUCKET_NAME=your-bucket-name
Contoh penggunaan:
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
const { getSignedUrl } = require("@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,
},
});
// Upload dengan presigned URL (untuk upload dari client)
async function getUploadUrl(key, contentType) {
const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET_NAME,
Key: key,
ContentType: contentType,
});
return getSignedUrl(s3, command, { expiresIn: 3600 });
}
Supabase Storage
Jika kamu sudah menggunakan Supabase untuk database, Storage adalah pilihan natural:
- 1GB gratis
- REST API sederhana
- Row-level security terintegrasi
- CDN built-in
Contoh penggunaan:
const { createClient } = require("@supabase/supabase-js");
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_KEY
);
// Upload file
async function uploadFile(bucket, path, file) {
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, {
contentType: file.type,
upsert: true,
});
if (error) throw error;
const { data: urlData } = supabase.storage
.from(bucket)
.getPublicUrl(path);
return urlData.publicUrl;
}
Contoh: API Upload File Lengkap
Berikut contoh lengkap API upload file menggunakan Express.js dan Cloudflare R2 yang siap di-deploy ke getlaunch.dev:
const express = require("express");
const multer = require("multer");
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
const crypto = require("crypto");
const app = express();
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit
});
const s3 = new S3Client({
region: "auto",
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
app.post("/upload", upload.single("file"), async (req, res) => {
if (!req.file) {
return res.status(400).json({ error: "No file provided" });
}
// Generate unique filename
const ext = req.file.originalname.split(".").pop();
const key = `uploads/${crypto.randomUUID()}.${ext}`;
await s3.send(new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME,
Key: key,
Body: req.file.buffer,
ContentType: req.file.mimetype,
}));
res.json({
url: `${process.env.R2_PUBLIC_URL}/${key}`,
key,
size: req.file.size,
});
});
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
const PORT = 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Best Practices
1. Validasi File Upload
Selalu validasi tipe dan ukuran file sebelum upload:
const allowedTypes = ["image/jpeg", "image/png", "image/webp", "application/pdf"];
const maxSize = 10 * 1024 * 1024; // 10MB
app.post("/upload", upload.single("file"), async (req, res) => {
if (!allowedTypes.includes(req.file.mimetype)) {
return res.status(400).json({ error: "File type not allowed" });
}
if (req.file.size > maxSize) {
return res.status(400).json({ error: "File too large" });
}
// ... proses upload
});
2. Gunakan Unique Filenames
Jangan gunakan nama file original untuk menghindari collision dan security issues:
const key = `uploads/${Date.now()}-${crypto.randomUUID()}.${ext}`;
3. Set Content-Type yang Benar
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: file.buffer,
ContentType: file.mimetype, // Penting untuk browser bisa menampilkan file dengan benar
}));
4. Gunakan Presigned URLs untuk Upload Besar
Untuk file besar, lebih baik upload langsung dari client ke storage menggunakan presigned URLs. Ini mengurangi beban di container getlaunch.dev dan menghemat CPU time (yang berarti menghemat biaya!).
5. Simpan Credentials di Environment Variables
Jangan pernah hardcode API keys. Tambahkan semua credentials sebagai environment variables di dashboard project getlaunch.dev.
Deploy ke getlaunch.dev
- Pilih storage provider dan buat bucket
- Tambahkan credentials sebagai environment variables di project settings
- Deploy — getlaunch.dev handle semuanya dari build hingga running
- Test upload melalui API yang sudah di-deploy
Dengan getlaunch.dev, kamu bisa fokus ke logic aplikasi sementara platform mengurus infrastructure. Kombinasi container stateless + external storage memberikan arsitektur yang scalable dan cost-effective.

