สร้างคลังภาพของคุณเองด้วยแผนฟรีของ Cloudflare — เริ่มต้นที่ $0
คุณเคยคิดบ้างไหมว่าจะรวบรวมภาพฟรีเชิงพาณิชย์ที่ใช้บ่อยไว้ในที่เดียว ค้นหาได้ทันที — และไม่เสียเงินสักบาท?
เราทำแล้ว บทความนี้แชร์สถาปัตยกรรมทั้งหมด
ทำไมต้องสร้างเอง?
นักออกแบบ นักพัฒนาเว็บ และนักการตลาดต้องการภาพอยู่เสมอ Pixabay, Pexels, Unsplash ดีทั้งคู่ แต่การเปิดแต่ละไซต์แยกกันและค้นหาทีละที่เสียเวลามาก
ยิ่งน่ารำคาญกว่านั้น: คุณเคยใช้ภาพหนึ่ง แต่ครั้งต่อมาจำไม่ได้ว่าเจอที่ไหน
ถ้ามีจุดเข้าถึงเดียวที่รวมทุกแหล่ง ค้นครั้งเดียวได้จากทุกแหล่ง — นั่นแหละถึงจะประหยัดเวลาจริงๆ
นั่นคือแนวคิดเบื้องหลัง FreePicHub
โควต้าฟรีของ Cloudflare ครอบคลุมแค่ไหน?
หลายคนไม่รู้ว่า Cloudflare ใจกว้างแค่ไหน นี่คือบริการที่โปรเจกต์นี้ใช้จริง ทั้งหมดอยู่ในแผนฟรี:
| บริการ | การใช้งาน | โควต้าฟรี |
|---|---|---|
| R2 Object Storage | เก็บไฟล์ภาพ | 10 GB / GET 10 ล้านครั้ง/เดือน |
| D1 Database | เก็บ metadata | 5 GB / อ่าน 5 ล้านครั้ง/วัน |
| Workers | API backend | 100K request/วัน |
| Pages | Frontend | Deploy static ไม่จำกัด |
สำหรับคลังภาพส่วนตัวขนาดเล็กถึงกลาง โควต้านี้เกินพอ และไม่ต้องใช้บัตรเครดิตเพื่อเริ่มต้น
ภาพรวมสถาปัตยกรรม
[Python Script อัตโนมัติ]
↓
ประมวลผลภาพ (บีบอัด WebP)
↓
┌──────────────────┐
│ Cloudflare │
│ │
│ R2 (ไฟล์) │
│ D1 (ฐานข้อมูล) │
│ Workers (API) │
│ Pages (UI) │
└──────────────────┘
↓
เบราว์เซอร์ / ผู้ใช้สี่บริการ Cloudflare แต่ละตัวมีบทบาทชัดเจน:
- R2: เก็บไฟล์ภาพที่บีบอัดแล้ว (thumbnail / preview / original)
- D1: SQLite database — ชื่อ ขนาด tag เส้นทาง R2
- Workers: สอง API —
/api/searchและ/api/featured - Pages: Frontend แบบ static, HTML + CSS + JS ล้วนๆ ไม่มี framework dependency
Step 1: สร้าง R2 Bucket
ไปที่ Cloudflare Dashboard → R2 → สร้าง Bucket (เช่น my-image-library)
จากนั้นที่ Manage R2 API Tokens สร้าง User API Token:
- สิทธิ์: "Object Read & Write"
- จำกัดเฉพาะ Bucket ของคุณ
จดบันทึก Access Key ID, Secret Access Key และ Account ID — Python script จะใช้สิ่งเหล่านี้
R2 ใช้ S3-compatible API ดังนั้น boto3 ของ Python ใช้ได้โดยตรง
Step 2: สร้าง D1 Database
รันใน Wrangler CLI:
bash
npx wrangler d1 create my-image-dbจด database_id ที่ได้รับ จากนั้นสร้าง 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 (ผ่าน Pages Functions)
วิธีที่สะดวกที่สุดคือ Cloudflare Pages Functions — วางไฟล์ JS ใน /functions/ แล้ว Cloudflare จะ deploy อัตโนมัติเป็น 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: ประมวลผลภาพและการนำเข้าอัตโนมัติ
นี่คือส่วนที่น่าสนใจที่สุด Python script ประมวลผลภาพ อัปโหลดไปยัง R2 และเขียน metadata ลง D1
ประมวลผลภาพ — สามเวอร์ชันต่อภาพ ทั้งหมดเก็บเป็น 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 (แกลเลอรี)
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)
# ต้นฉบับแปลงเป็น WebP บีบอัด
img.save(output_dir / "original.webp", "WEBP", quality=90)
return {"width": w, "height": h}WebP มักเล็กกว่า JPEG 25–35% — ประหยัด storage R2 ได้มากในระยะยาว
อัปโหลดไปยัง 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)เขียนลง D1 — ป้องกันข้อมูลซ้ำ:
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)WHERE NOT EXISTS ทำให้รัน script ซ้ำกี่ครั้งก็ไม่เขียนข้อมูลซ้ำ — สำคัญมากสำหรับ pipeline อัตโนมัติรายวัน
Step 5: Deploy Frontend
ไฟล์ static สามไฟล์ — index.html, style.css, app.js — push ไปยัง Cloudflare Pages:
bash
npx wrangler pages deploy ./frontend --project-name my-image-libraryLayout masonry waterfall ด้วย CSS ไม่ต้องใช้ library JS:
css
.image-grid {
columns: 4 220px;
gap: 14px;
}
.image-card {
break-inside: avoid;
margin-bottom: 14px;
}คำนวณต้นทุน
สมมตินำเข้า 500 ภาพ/วัน คลัง 10,000 ภาพ (thumbnail ~50KB, preview ~200KB):
| รายการ | การใช้งาน | ค่าใช้จ่าย |
|---|---|---|
| R2 Storage | ≈ 2.5 GB | $0 |
| D1 Reads (ผู้เยี่ยมชม 1,000 คน/วัน) | 5,000/วัน | $0 |
| Workers Requests | 5,000/วัน | $0 |
| Pages Deploy | ไม่จำกัด | $0 |
| รวม | $0 / เดือน |
โควต้าฟรีของ Cloudflare แทบไม่มีวันหมดสำหรับคลังภาพส่วนตัวหรือเชิงพาณิชย์ขนาดเล็ก
สรุป
แนวคิดหลักของระบบนี้ง่ายมาก:
- R2 เก็บไฟล์ — ราคาถูกและเร็ว
- D1 เก็บ index — ขับเคลื่อนการค้นหา
- Workers จัดการ query logic
- Pages แสดง UI ให้ผู้ใช้
- Python scripts ป้อนข้อมูลตามกำหนดเวลา
ทุกอย่างรันบน edge network ของ Cloudflare — เร็วทั่วโลก ไม่มีค่าบำรุงรักษา
ดูผลลัพธ์จริง: FreePicHub
มีคำถามเกี่ยวกับข้อกำหนดทางเทคนิคหรือสถาปัตยกรรม?
ติดต่อเราได้ที่ ascentek.info ไม่ว่าจะเป็นการออกแบบสถาปัตยกรรม Cloudflare, pipeline อัตโนมัติ Python หรือการวางแผนคลังภาพ เรายินดีพูดคุย
อ่านเพิ่มเติม