การทำระบบที่อัปโหลดรูปภาพหนึ่งในตัวเลือกยอดนิยมคือ Google Cloud Storage หรือ GCS เพราะเก็บไฟล์ได scale ง่าย และแยกไฟล์ออกจาก application server ได้

แต่คำถามคือ แล้วเราจะให้รูปภาพถูกอัปโหลดขึ้น GCS ด้วยวิธีไหนดี?

โดยปกติมี 2 แนวทางหลัก ๆ

  1. อัปโหลดผ่าน backend ก่อน แล้ว backend ค่อยส่งขึ้น GCS
  2. ให้ frontend อัปโหลดตรงขึ้น GCS ผ่าน Signed URL

แบบที่ 1: Upload โดยไม่ใช้ Signed URL

วิธีนี้ frontend จะส่งไฟล์รูปภาพมาที่ backend ก่อน จากนั้น backend จะใช้ GCSอัปโหลดไฟล์ขึ้น bucket อีกที

Flow

Frontend → Backend → Google Cloud Storage

ตัวอย่างเช่น ผู้ใช้เลือกรูป แล้วส่ง multipart/form-data ไปที่ API ของเรา

const formData = new FormData();
formData.append("image", file);
await fetch("/api/upload", {
  method: "POST",
  body: formData,
});

ฝั่ง backend จะได้ไฟล์จาก forntend แล้วเมื่อได้ไฟล์แล้ว backend จะอัปโหลดขึ้น GCS ด้วย service account

ข้อดีของวิธีนี้คือ backend ควบคุมทุกอย่างได้เต็มที่ เช่น ตรวจขนาดไฟล์ ตรวจชนิดไฟล์ resize รูป หรือสแกนไฟล์ก่อนอัปโหลดจริง

แต่ข้อเสียคือ backend ต้องรับ traffic เยอะถ้าผู้ใช้เยอะหรือไฟล์ใหญ่ server จะทำงานหนักขึ้น ทั้ง memory, CPU, network และ timeout ก็อาจเกิดง่ายขึ้นหรือแย่มากกกว่านั้นคือ service ตาย

แบบที่ 2: Upload ด้วย Signed URL

Signed URL คือ URL ชั่วคราวที่ backend สร้างให้ client ไว้ใช้ทำ action บางอย่างกับไฟล์ GCS ได้ เช่น upload โดยไม่ต้องเปิด bucket เป็น public

Flow

Frontend → Backend: ขอ Signed URL

Backend → GCS: สร้าง Signed URL

Frontend → GCS: Upload รูปโดยตรง

ตัวอย่าง flow

  1. frontend ส่งชื่อไฟล์และ content type ไปที่ backend
  2. backend ตรวจสอบสิทธิ์ user
  3. backend สร้าง signed URL สำหรับ upload
  4. frontend ใช้ URL นั้น PUT ไฟล์ขึ้น GCS โดยตรง

ตัวอย่างฝั่ง frontend

const res = await fetch("/api/generate-upload-url", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    filename: file.name,
    contentType: file.type,
  }),
});
const { uploadUrl, publicUrl } = await res.json();
await fetch(uploadUrl, {
  method: "PUT",
  headers: {
    "Content-Type": file.type,
  },
  body: file,
});

ตัวอย่าง backend Node.js

import { Storage } from "@google-cloud/storage";
const storage = new Storage();
const bucket = storage.bucket("your-bucket-name");
export async function generateUploadUrl(req, res) {
  const { filename, contentType } = req.body;
  const objectName = `uploads/${Date.now()}-${filename}`;
  const file = bucket.file(objectName);
  const [url] = await file.getSignedUrl({
    version: "v4",
    action: "write",
    expires: Date.now() + 15 * 60 * 1000,
    contentType,
  });
  res.json({
    uploadUrl: url,
    publicUrl: `https://storage.googleapis.com/your-bucket-name/${objectName}`,
  });
}

ตอนใช้ Signed URL จริง

ควรกำหนดอายุ URL ให้สั้น เช่น 5–10 นาที เพราะใครที่มี URL ก็สามารถใช้ได้ภายในเวลาที่ยังไม่หมดอายุ

ควร validate contentType และขนาดไฟล์ก่อนขอ signed URL และอาจตรวจซ้ำหลัง upload ด้วย Cloud Functions

สรุป

การ upload รูปภาพขึ้น GCS ทำได้ทั้งแบบผ่าน backend และแบบใช้ Signed URL

ถ้าต้องการความง่ายและควบคุมทุกอย่างใน backend ให้เลือกแบบไม่ใช้ Signed URL

แต่ถ้าต้องการระบบที่ scale ดี ลด load server และให้ client upload เข้า storage โดยตรง ให้เลือก Signed URL