🛑 1. The Problem First: "รันบน Server ไม่ได้แปลว่าปลอดภัย"
ลองนึกถึงแอปที่คุณใช้ Server Actions เพื่อลบข้อความในฐานข้อมูล:
// ❌ Naive Approach: เชื่อใจค่าที่ส่งมาจาก Client
'use server'
export async function deleteComment(commentId: string) {
await db.comment.delete({ where: { id: commentId } });
}
// 🌋 พัง! แม้ฟังก์ชันนี้จะรันบน Server แต่แฮกเกอร์สามารถแกะชื่อฟังก์ชัน
# และใช้เครื่องมืออย่าง Postman ยิง request เข้ามาโดยเปลี่ยน commentId
# เป็นของคนอื่นเพื่อลบข้อมูลทั้้งระบบทิ้งได้ทันที!
ปัญหา: ความผิดพลาดที่ใหญ่ที่สุดของ Developer คือการคิดว่า Server Actions คือฟังก์ชันลับ ในความเป็นจริง มันคือ Public API Endpoint ที่ถูกสร้างขึ้นมาภายใต้ชื่อฟังก์ชัน การขาดระบบตรวจสอบสิทธิ์ภายในฟังก์ชัน (Authorize) คือการเปิดประตูบ้านทิ้งไว้ให้มิจฉาชีพครับ
💡 2. Real-Life Analogy: การสั่งงานผ่านวิทยุสื่อสาร
- Server Action: เหมือน "วิทยุสื่อสาร (Walkie-Talkie)". คุณส่งคำสั่งจากหน้างาน (Client) ไปหาที่ฐาน (Server) แต่ใครก็ตามที่มีช่องสัญญาณเดียวกัน (แฮกเกอร์) ก็สามารถจูนเข้ามาแล้วส่งคำสั่งปลอมว่า "ทุบตึกทิ้ง!" ได้
- Auth Verification: เหมือน "รหัสลับและเสียงที่คุ้นเคย". ก่อนที่ฐานจะทำตามคำสั่ง เขาต้องถามก่อนว่า "คุณเป็นใคร?" (Authentication) และ "คุณมีสิทธิ์สั่งทุบตึกไหม?" (Authorization)
- Parallel Fetching: เหมือน "ทีมก่อสร้าง 5 ทีมที่เริ่มงานพร้อมกัน". แทนที่จะให้ช่างไฟรอช่างประปาเสร็จ ช่างทุุกคนเริ่มทำงานส่วนของตัวเองทันที ทำให้บ้านเสร็จเร็วขึ้นหลายเท่า
🚀 3. Execution Journey: มหากาพย์การสร้างระบบที่ 'นิ่ง' และ 'ปลอดภัย'
Senior จะใส่ด่านตรวจไว้ในทุุกการทำงานของ Server เพื่อความชัวร์ 100%
🛠 Step-by-step:
- The Strict Validator: ใช้
Zodตรวจสอบข้อมูลขาเข้าทุุกครั้ง (เช่น id ต้องเป็น UUID เท่านั้น) - The Owner Check: ตรวจสอบ Session และสิทธิ์การเข้าถึงทุุกครั้งข้างใน Server Action (ห้ามพึ่งแค่ Middleware)
- The Parallel Power: แยก Fetch ข้อมูลใส่ใน Component ย่อยๆ เพื่อให้ทุุกส่วนเริ่มทำงานพร้อมกันทันที (Streaming)
- The Cache Master: ใช้
React.cacheหุ้มฟังก์ชันดึงข้อมูล เพื่อให้ถ้ามี 3 คอมโพเนนต์เรียกข้อมูลเดียวกัน มันจะยิง DB แค่รอบเดียว
// ✅ Best Practice: Server Action ที่ปลอดภัยระดับ Enterprise
"use server";
export async function updateProfile(formData: FormData) {
// 🛠 1. ตรจสอบตัวตน (Authentication)
const session = await getSession();
if (!session) throw new Error("Unauthorized");
// 🛠 2. ตรวจความถูกต้องข้อมูล (Validation)
const safeData = profileSchema.parse(Object.fromEntries(formData));
// 🛠 3. ตรวจสอบสิทธิ์ (Authorization)
if (session.user.id !== safeData.userId) throw new Error("Forbidden");
await db.user.update({ where: { id: session.user.id }, data: safeData });
}
🪤 4. The Junior Trap: โรค "Sequential Await" (รอจนหลับ)
จูเนียร์มักจะเอา await ไปวางต่อกันในไฟล์ page.tsx:
// ❌ Junior Trap: สร้างคอขวดให้ตัวเอง
const user = await fetchUser(); // ⏳ 2 วินาที
const posts = await fetchPosts(); // ⏳ 3 วินาที (เริ่มทำหลังจาก 2 วินาทีแรก)
// 🌋 พัง! หน้าเว็บของคุณจะค้างรอนานถึง 5 วินาที
# ทั้งที่ข้อมูลทั้งคู่ไม่ได้เกี่ยวข้องกันเลย
ระวัง: ทุุกวินาทีที่ User รอนานขึ้น คือโอกาสที่เขาจะปิดหน้าเว็บคุณทิ้งไป
✅ การแก้ไข: จงยืดหยุ่นด้วยการใส่ Suspense แยกส่วน หรือใช้ Promise.all เพื่อรันขนานกันครับ
⚖️ 5. The Why Matrix: Client-Side Auth vs Server Action Auth
| หัวข้อ | Client-Side Auth (Naive) | Server Action Auth (Pro) |
|---|---|---|
| ความปลอดภัย | ต่ำ (Browser แก้ไขได้เสม) | 🚀 สูงสุด (Hacker เข้าไม่ถึง Logic) |
| ความมั่นใจ | หลอกได้ง่ายด้วย DevTools | 🛡 หลอกไม่ได้ (รันในสภาพแวดล้อมปิด) |
| UX | เร็ว (ไม่ต้องรอ Server) | ปานกลาง (ต้องรัน Logic ตรวจสอบ) |
| สรุป | เหมาะกับเรื่องหน้าตา UI | ต้องทำเพื่อความปลอดภัยข้อมูล |
🎓 6. Senior Mindset Summary
การเป็น Senior คือการมองว่า "เราไม่ได้เขียนโค้ดแค่ให้มัน 'ทำงานได้' แต่ต้องเขียนโค้ดให้มัน 'ถูกแฮกไม่ได้' และ 'เร็วที่สุด' ในทุุกสภาวะ". ความปลอดภัยไม่ใช่ทางเลือก แต่มันคือสิ่งเติมเต็มที่จะทำให้งานของคุณกลายเป็นสถาปัตยกรรมระดับโลกครับ!
Mission Accomplished
You've reached the end of this module. Time to apply these senior mindsets to your real-world projects!