Bangun Pustaka Gambar Sendiri dengan Paket Gratis Cloudflare — Mulai dari $0
Pernahkah kamu berpikir untuk mengumpulkan semua gambar komersial gratis favoritmu dalam satu tempat, lalu tinggal cari — tanpa bayar sepeser pun?
Kami sudah melakukannya. Artikel ini menjelaskan seluruh arsitekturnya.
Kenapa Harus Bangun Sendiri?
Desainer, developer web, dan marketer selalu butuh gambar. Pixabay, Pexels, Unsplash semuanya bagus, tapi membuka masing-masing dan mencari secara terpisah itu membuang waktu.
Yang lebih menyebalkan: kamu sudah pakai sebuah gambar, tapi lain kali tidak ingat menemukannya di mana.
Kalau ada satu pintu masuk yang mengintegrasikan semua sumber, cari sekali hasilnya dari semua sumber — itu baru efisien.
Itulah ide di balik FreePicHub.
Seberapa Besar Kuota Gratis Cloudflare?
Banyak orang tidak tahu betapa murah hatinya Cloudflare. Berikut layanan yang digunakan proyek ini, semuanya dalam paket gratis:
| Layanan | Kegunaan | Kuota Gratis |
|---|---|---|
| R2 Object Storage | Simpan file gambar | 10 GB storage / 10 juta GET/bulan |
| D1 Database | Simpan metadata gambar | 5 GB storage / 5 juta baca/hari |
| Workers | Backend API | 100 ribu request/hari |
| Pages | Website frontend | Deploy statis tanpa batas |
Untuk pustaka gambar pribadi skala kecil hingga menengah, kuota ini lebih dari cukup — dan tidak perlu kartu kredit untuk mulai.
Gambaran Arsitektur
[Script Python Otomatis]
↓
Pemrosesan Gambar (kompresi WebP)
↓
┌──────────────────┐
│ Cloudflare │
│ │
│ R2 (file) │
│ D1 (database) │
│ Workers (API) │
│ Pages (UI) │
└──────────────────┘
↓
Browser / PenggunaEmpat layanan Cloudflare, masing-masing punya peran jelas:
- R2: Menyimpan file gambar terkompresi (thumbnail / preview / original)
- D1: Database SQLite — judul, dimensi, tag, path R2
- Workers: Dua API —
/api/searchdan/api/featured - Pages: Frontend statis, HTML + CSS + JS murni, tanpa dependensi framework
Step 1: Buat R2 Bucket
Masuk Cloudflare Dashboard → R2 → Buat Bucket (misal my-image-library).
Lalu di Manage R2 API Tokens, buat User API Token:
- Izin: "Object Read & Write"
- Dibatasi untuk bucket kamu
Catat Access Key ID, Secret Access Key, dan Account ID — dibutuhkan script Python.
R2 menggunakan API kompatibel S3, jadi boto3 Python langsung bisa dipakai.
Step 2: Buat D1 Database
Jalankan di Wrangler CLI:
bash
npx wrangler d1 create my-image-dbCatat database_id yang dikembalikan. Lalu buat schema:
sql
CREATE TABLE IF NOT EXISTS images (
id TEXT PRIMARY KEY,
source TEXT NOT NULL,
source_id TEXT,
title TEXT,
tags TEXT DEFAULT '[]',
width INTEGER,
height INTEGER,
aspect_ratio TEXT,
license_type TEXT NOT NULL,
thumb_key TEXT,
preview_key TEXT,
original_key TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_source ON images(source);
CREATE INDEX IF NOT EXISTS idx_created ON images(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_source_id ON images(source, source_id);bash
npx wrangler d1 execute my-image-db --file=schema.sqlStep 3: Workers API (via Pages Functions)
Cara termudah adalah Cloudflare Pages Functions — taruh file JS di /functions/, Cloudflare otomatis deploy sebagai Worker.
Search API (functions/api/search.js):
javascript
export async function onRequestGet({ request, env }) {
const url = new URL(request.url)
const q = url.searchParams.get('q') || ''
const source = url.searchParams.get('source') || ''
const page = parseInt(url.searchParams.get('page') || '1')
const limit = 24
const offset = (page - 1) * limit
let sql = 'SELECT * FROM images WHERE 1=1'
let params = []
if (q) {
sql += ' AND (title LIKE ? OR tags LIKE ?)'
params.push(`%${q}%`, `%${q}%`)
}
if (source) {
sql += ' AND source = ?'
params.push(source)
}
sql += ` ORDER BY created_at DESC LIMIT ${limit + 1} OFFSET ${offset}`
const { results } = await env.MY_DB.prepare(sql).bind(...params).all()
const hasMore = results.length > limit
return Response.json({
results: results.slice(0, limit).map(r => ({
...r,
tags: JSON.parse(r.tags || '[]')
})),
has_more: hasMore
}, { headers: { 'Access-Control-Allow-Origin': '*' } })
}Step 4: Pemrosesan Gambar & Ingesti Otomatis
Ini bagian paling menarik. Script Python memproses gambar, upload ke R2, dan menulis metadata ke D1.
Pemrosesan gambar — tiga versi per gambar, semua disimpan sebagai WebP:
python
from PIL import Image
def process_image(source_path, output_dir):
img = Image.open(source_path).convert("RGB")
w, h = img.size
# Thumbnail 512px (galeri)
thumb = img.copy()
thumb.thumbnail((512, 512), Image.LANCZOS)
thumb.save(output_dir / "thumb.webp", "WEBP", quality=82)
# Preview 1024px (modal / lightbox)
preview = img.copy()
preview.thumbnail((1024, 1024), Image.LANCZOS)
preview.save(output_dir / "preview.webp", "WEBP", quality=85)
# Original dikonversi ke WebP terkompresi
img.save(output_dir / "original.webp", "WEBP", quality=90)
return {"width": w, "height": h}WebP biasanya 25–35% lebih kecil dari JPEG — penghematan nyata di storage R2 jangka panjang.
Upload ke R2 (boto3 S3-compatible):
python
import boto3
s3 = boto3.client(
"s3",
endpoint_url=f"https://{ACCOUNT_ID}.r2.cloudflarestorage.com",
aws_access_key_id=ACCESS_KEY,
aws_secret_access_key=SECRET_KEY,
region_name="auto",
)
s3.put_object(Bucket="my-image-library", Key="source/uuid/thumb.webp", Body=file_bytes)Tulis ke D1 — aman dari duplikasi:
python
import requests, uuid, json
def write_to_d1(record):
resp = requests.post(
f"https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/d1/database/{DB_ID}/query",
headers={"Authorization": f"Bearer {D1_TOKEN}"},
json={
"sql": """
INSERT INTO images (id, source, source_id, title, tags,
width, height, aspect_ratio, license_type,
thumb_key, preview_key, original_key)
SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
WHERE NOT EXISTS (
SELECT 1 FROM images WHERE source=? AND source_id=?
)
""",
"params": [str(uuid.uuid4()), record["source"], record["source_id"],
record["title"], json.dumps(record["tags"]),
record["width"], record["height"], record["aspect_ratio"],
record["license_type"], record["thumb_key"],
record["preview_key"], record["original_key"],
record["source"], record["source_id"]]
}
)
return resp.json().get("success", False)Klausa WHERE NOT EXISTS memastikan script bisa dijalankan berulang kali tanpa menulis duplikat — penting untuk pipeline harian otomatis.
Step 5: Deploy Frontend
Tiga file statis — index.html, style.css, app.js — push ke Cloudflare Pages:
bash
npx wrangler pages deploy ./frontend --project-name my-image-libraryLayout masonry waterfall dengan CSS, tanpa library JS:
css
.image-grid {
columns: 4 220px;
gap: 14px;
}
.image-card {
break-inside: avoid;
margin-bottom: 14px;
}Kalkulasi Biaya
Asumsi 500 gambar/hari, pustaka 10.000 gambar (thumbnail ~50KB, preview ~200KB):
| Item | Penggunaan | Biaya |
|---|---|---|
| R2 Storage | ≈ 2,5 GB | $0 |
| D1 Reads (1.000 pengunjung/hari) | 5.000/hari | $0 |
| Workers Requests | 5.000/hari | $0 |
| Pages Deploy | Tak terbatas | $0 |
| Total | $0 / bulan |
Kuota gratis Cloudflare hampir tidak habis untuk pustaka gambar pribadi atau komersial skala kecil.
Kesimpulan
Inti sistemnya sederhana:
- R2 menyimpan file — murah dan cepat
- D1 menyimpan indeks — kekuatan pencarian
- Workers menangani logika query
- Pages menyajikan UI ke pengguna
- Script Python mengisi data secara terjadwal
Semua berjalan di edge network Cloudflare — dipercepat secara global, tanpa biaya pemeliharaan.
Lihat hasil nyatanya: FreePicHub
Ada pertanyaan tentang spesifikasi teknis atau arsitektur?
Silakan hubungi kami di ascentek.info — baik itu desain arsitektur Cloudflare, pipeline otomasi Python, maupun perencanaan pustaka gambar, kami siap berdiskusi.
Bacaan Lanjutan