กรณีศึกษา

VetMiMi

เว็บจองนัด วิดีโอคอลส่วนตัว และโพสต์ง่าย ๆ สำหรับนักบำบัดด้วยศิลปะในซิดนีย์

โปรเจกต์ลูกค้าสำหรับ Daw Mi นักบำบัดด้วยศิลปะที่ทำงานคนเดียวในซิดนีย์ เปลี่ยนบล็อกส่วนตัวของเธอให้เป็นเว็บสองภาษา คืออังกฤษและพม่า ลูกค้าจองนัดและคุยวิดีโอคอลส่วนตัวกับเธอได้ ส่วนเธอเขียนโพสต์ครั้งเดียวแล้วแชร์ไปที่เว็บไซต์ Facebook, Instagram และ LinkedIn ได้เลย

บทบาท
Lead developer ดูแล API, เว็บไซต์, หน้าแอดมิน และเซิร์ฟเวอร์
ช่วงเวลา
ส.ค. - ต.ค. 2026 · ตั้งแต่วางแผนจนเปิดใช้
สถานะ
ใช้งานจริงบน AWS
ประเภท
โปรเจกต์ลูกค้า
API action ทั้งหมดอยู่ใน contract เดียวกัน
89
เทสต์ Go ที่รันกับ PostgreSQL จริง
454
ช่องทางจากร่างเดียว: เว็บ, Facebook, Instagram, LinkedIn
4
ภาษา: อังกฤษและพม่า
2

01

โจทย์

เว็บเดิมของ Daw Mi ดูเหมือนบล็อกส่วนตัว เธออยากได้เว็บที่ดูเป็นมืออาชีพ ทั้งภาษาอังกฤษและภาษาพม่า และช่วยลดงานธุรการให้เธอด้วย

  • จองนัด ได้โดยไม่ต้องส่งอีเมลไปมา
  • เซสชันออนไลน์แบบส่วนตัว โดยไม่ต้องใช้แอปวิดีโอทั่วไป
  • แชร์งานเขียน บนเว็บและโซเชียล โดยไม่ต้องเขียนใหม่สี่รอบ

เธอทำงานคนเดียว มีนัดราวหลักสิบต่อเดือน ค่ารันระบบจึงต้องใกล้ศูนย์ และเพราะนี่คืองานบำบัด ฟอร์มจองจึงไม่ถามข้อมูลสุขภาพ ไม่ทำให้เข้าใจว่ามีบริการฉุกเฉิน และลิงก์จัดการนัดต้องเป็นความลับ

ก่อนเขียนโค้ด เขียน proposal ศึกษางานของคนที่ทำอาชีพคล้ายกันเก้าแห่ง และวางแผนหน้าเว็บกับกติกาการจองร่วมกับเธอ จากนั้นจึงร่วมกับเพื่อนร่วมทีมเปลี่ยน prototype ด้านดีไซน์ให้เป็นแอป Next.js

02

ระบบทำงานยังไง

มีโค้ดสอง repository และกฎข้อเดียว: เบราว์เซอร์คุยกับเว็บ Next.js เท่านั้น

  • Next.js (framework สำหรับทำเว็บ) รันทั้งเว็บสาธารณะและหน้าแอดมิน พออยู่ในแอปเดียวกันก็มีล็อกอินเดียวและหน้าตาแบบเดียว
  • Go API ทำงานหลัก โปรแกรมเดียวรันได้สองโหมด โหมดแรกตอบ request และดูแลวิดีโอคอล อีกโหมดทำงานเบื้องหลัง โฮสต์ของเว็บทำสองอย่างนี้ไม่ได้ จึงต้องมีเซิร์ฟเวอร์ที่เปิดตลอด Go เบามาก เลยลงเซิร์ฟเวอร์เล็ก ๆ ราคาถูกได้
  • PostgreSQL เป็นฐานข้อมูลหลัก Redis เก็บคิวงาน ถ้าข้อมูลใน Redis หาย ระบบก็สร้างงานใหม่จาก PostgreSQL ได้
  • Resend ส่งอีเมล S3 (ที่เก็บไฟล์ของ AWS) เก็บรูป พร้อมรูปขนาดเล็กสำหรับเว็บ
ClientbrowserTherapistadmin consoleNext.js 16site + /adminGo APIchi · OpenAPIGo workerasynqexternalAnthropicClaude Haiku 4.5Resendemail · English + BurmeseMeta GraphFacebook · InstagramLinkedInREST postsPostgreSQL 17EXCLUDE · sqlcRedis 7queues · limitsS3mediaservice key / sessionrevalidatesuggestionsemail · publishSQLqueueuploads
ภาพ 1. เบราว์เซอร์คุยกับ Next.js เท่านั้น Next.js เรียก Go API ส่วน worker ส่งอีเมล โพสต์คอนเทนต์ และบอกให้เว็บรีเฟรช

03

Contract เดียวที่ใช้ร่วมกัน

API action ทั้ง 89 อย่างเขียนไว้ในไฟล์เดียวคือ openapi.yaml ทั้งโค้ดฝั่ง Go server และ TypeScript type ของเว็บสร้างจากไฟล์นี้ เว็บกับ API จึงใช้ contract เดียวกัน

ถ้าสองฝั่งไม่ตรงกันเมื่อไร build จะพัง แนวคิดง่าย ๆ คือ ถ้าชื่อ field เปลี่ยน สิ่งที่ควรพังคือ build ของตัวเอง ไม่ใช่การจองของคนที่เข้าเว็บ

เรื่อง security ก็อยู่ในไฟล์เดียวกัน ทุก action ต้องบอกว่าใครใช้ได้ ถ้ามี action ไหนไม่ได้บอกไว้ server จะไม่ยอมเริ่มทำงาน

04

ระบบจองที่ไม่จองซ้อน

Go API คำนวณเวลาว่างจากเวลาทำงานรายสัปดาห์ วันหยุด และนัดที่มีอยู่แล้ว โดยใช้เวลาซิดนีย์เสมอ daylight saving มีเทสต์แยกของมันเอง ชั่วโมงที่หายไปหรือซ้ำกันตอนเปลี่ยนเวลาจึงถูกจัดการอย่างถูกต้อง

ด่านสุดท้ายอยู่ที่ฐานข้อมูล PostgreSQL เองจะไม่ยอมรับนัดที่ยังมีผลสองนัดที่เวลาทับกัน ต่อให้มี request เข้ามาพร้อมกันหลายตัวก็ตาม ถ้าเวลานั้นถูกจองแล้ว ลูกค้าจะเห็นเวลาว่างอื่นได้สูงสุดห้าช่วง

รายละเอียดอื่นที่ช่วยให้ปลอดภัย:

  • กดส่งสองครั้ง ก็ได้คำตอบเดิม ไม่เกิดนัดที่สอง
  • กับดักสแปมที่มองไม่เห็น ช่วยจับบอต ฟอร์มที่ส่งภายในสามวินาทีจะถูกปฏิเสธ
  • ลิงก์จัดการนัด ถูกเก็บเป็นค่า hash เท่านั้น ถ้าฐานข้อมูลหลุดออกไปก็ไม่มีลิงก์ไหนใช้งานได้

05

อีเมลที่ไม่ทำให้การจองพัง

ถ้าส่งอีเมลระหว่างที่กำลังจอง บริการอีเมลที่ช้าอาจทำให้การจองพังได้ จึงบันทึกอีเมลลงฐานข้อมูลไปพร้อมกับการเปลี่ยนแปลงของนัด จากนั้น worker เบื้องหลังจะส่งผ่าน Resend ลองใหม่ได้สูงสุดห้าครั้ง และเก็บอีเมลที่หลุดไปเพราะระบบ crash ได้ด้วย อีเมลฉบับเดียวกันจะไม่ถูกส่งซ้ำ

ก่อนส่ง reminder ระบบจะเช็กนัดอีกรอบ ถ้านัดถูกเลื่อน ลูกค้าจะไม่ได้ reminder ของเวลาเก่า และถ้าอีเมลส่งไม่ผ่าน นัดก็ไม่ถูกยกเลิก

มีอีเมล 12 แบบ แต่ละแบบมีสองภาษา และทุกฉบับมีเทสต์เช็ก

06

วิดีโอคอลตัวต่อตัวแบบส่วนตัว

แอปวิดีโอทั่วไปไม่ได้รับประกันเรื่องความเป็นส่วนตัว ส่วนบริการวิดีโอแบบเสียเงินก็คิดเป็นนาที สำหรับลูกค้าหนึ่งคนกับนักบำบัดหนึ่งคน WebRTC เหมาะที่สุด มันส่งวิดีโอตรงระหว่างเบราว์เซอร์สองฝั่ง ไม่มีใครอยู่ตรงกลาง

  • การเข้าห้อง: Go API ช่วยให้เบราว์เซอร์สองฝั่งหากันเจอ ต้องใช้บัตรผ่านที่ใช้ได้แค่ห้านาที ห้องเปิดก่อนเซสชัน 15 นาที และปิดหลังจบหนึ่งชั่วโมง server ไม่เก็บข้อความที่ส่งต่อเลย
  • เครือข่ายที่ต่อยาก: เครือข่ายมือถือและออฟฟิศบางแห่งบล็อกการต่อตรง กรณีนี้ relay server (TURN) จะช่วยส่งวิดีโอต่อให้ และไม่มีใครใช้มันเจาะเข้าไปข้างในเซิร์ฟเวอร์ได้
  • ในเบราว์เซอร์: ทดสอบกล้องและไมค์ก่อนเข้าคอลได้ ถ้าหลุด คอลจะต่อใหม่เอง และถ้าสัญญาณอ่อนจะมีแถบแจ้งเตือน

ไม่มีการอัดคอลเด็ดขาด

Client browserrole: clientTherapist browserrole: practitionerGo signalling hubWebSocket · 5-min ticketGoogle STUNfind public addresscoturnTURN relay · HMAC credsoffer · answer · ICEoffer · answer · ICEmedia, peer to peerSTUNrelay when direct fails
ภาพ 2. ข้อความสำหรับเริ่มคอลวิ่งผ่าน Go API ส่วนภาพและเสียงวิ่งตรงระหว่างเบราว์เซอร์สองฝั่ง หรือผ่าน relay ถ้าเครือข่ายบล็อกการต่อตรง

07

เขียนครั้งเดียว โพสต์ได้ทุกที่

ตอนแรกวางแผนจะทำ CMS (ระบบจัดการคอนเทนต์) เต็มรูปแบบ แต่พอเห็นว่าเธอต้องการอะไรจริง ๆ ก็เปลี่ยนมาทำ publishing portal ที่เล็กกว่ามาก ไว้เขียนครั้งเดียวแล้วแชร์ออกไป

โพสต์หนึ่งชิ้นมีหนึ่งเวอร์ชันต่อหนึ่งช่องทาง และเดินจาก draft ไป review, approved, scheduled แล้วก็ published มีแค่แอดมินของเว็บที่อนุมัติได้ ตอนอนุมัติ ระบบจะเช็กกติกาของแต่ละช่องทาง เช่น Instagram ต้องมีรูป 1-10 รูป และข้อความไม่เกิน 2,200 ตัวอักษร

  • เว็บไซต์: คนอ่านจะเห็นฉบับที่เผยแพร่ล่าสุด จนกว่าฉบับแก้จะผ่าน review
  • Facebook, Instagram, LinkedIn: แต่ละช่องทางถูกส่งเป็นงานเบื้องหลังแยกกัน ถ้าไม่รู้ว่าโพสต์ขึ้นไปแล้วหรือยัง ระบบจะตั้งสถานะเป็น "ไม่ทราบผล" และจะไม่ส่งซ้ำเอง เพราะไม่มีใครอยากได้โพสต์ซ้ำ
  • ยังไม่ได้เชื่อมบัญชี? เธอคัดลอกข้อความไปโพสต์เอง แล้วกดว่าโพสต์แล้วได้

ตัวช่วย AI: Claude Haiku 4.5 ช่วยร่างโพสต์สำหรับโซเชียลหรือคำแปล โดยสั่งให้คงน้ำเสียงของเธอและไม่แต่งเรื่องขึ้นมาเอง ร่างที่ได้จะไม่ถูกบันทึกเอง เธอเป็นคนแก้และอนุมัติเสมอ

Draftone per channelIn reviewApprovedsite_admin onlyPublishingnow or scheduledAI suggestionsClaude Haiku 4.5 · never savedWebsitesnapshot + revalidateFacebook Pagephotos → feedInstagramcontainer → publishLinkedInimage → postshe edits and approves
ภาพ 3. ร่างเดียวกลายเป็นสี่เวอร์ชัน หนึ่งเวอร์ชันต่อหนึ่งช่องทาง แต่ละโพสต์โซเชียลถูกส่งเป็นงานแยกกัน ถ้าไม่แน่ใจผล ระบบจะไม่ส่งซ้ำ

08

ความปลอดภัยและความเป็นส่วนตัว

  • การล็อกอินของแอดมิน: เก็บรหัสผ่านแบบปลอดภัย (argon2id) การล็อกอินของแอดมินรองรับรหัสจากแอป authenticator ด้วย ถ้าล็อกอินผิด 10 ครั้งใน 15 นาที บัญชีจะถูกล็อก
  • Token ของโซเชียล ถูกเข้ารหัสก่อนเก็บลงฐานข้อมูล
  • Log ไม่เก็บชื่อ อีเมล โน้ต หรือลิงก์ส่วนตัว
  • เก็บข้อมูลให้น้อย: เอกสารออกแบบข้อมูลระบุไว้ว่าอะไรที่ตั้งใจไม่เก็บ และมีงานรายวันคอยลบข้อมูลเก่า

09

การทดสอบ

  • เทสต์ Go 454 ตัวรันกับ PostgreSQL จริง ไม่ใช่ของปลอม เทสต์แต่ละชุดได้ฐานข้อมูลใหม่ของตัวเอง
  • Race test เช็กว่าเกิดอะไรขึ้นเมื่อสองอย่างเกิดพร้อมกัน เช่น สองคนจองเวลาเดียวกัน ต้องมีคนได้แค่คนเดียว
  • พิสูจน์ว่าด่านป้องกันทุกตัวได้ผลจริง ลองลบด่านนั้นออก ดูว่าเทสต์ fail แล้วค่อยใส่กลับ
  • เว็บไซต์ มี unit test 179 ตัว และเทสต์ Playwright 9 ตัวเช็ก accessibility การใช้คีย์บอร์ด และ layout

10

รันบน AWS

ทุกอย่างรันอยู่บน เซิร์ฟเวอร์ AWS EC2 เล็ก ๆ เครื่องเดียว ตั้งค่าด้วย Terraform ซึ่งเป็นเครื่องมือที่ให้เขียนเซิร์ฟเวอร์เป็นโค้ดได้ ทั้ง API, worker, เว็บไซต์, ฐานข้อมูล, Redis และ relay ของวิดีโอ อยู่บนเครื่องนี้ทั้งหมด แต่ละส่วนมีเพดาน memory ของตัวเอง

  • Deploy อัตโนมัติ: เมื่อเทสต์บน GitHub ผ่าน GitHub Actions จะเอาเวอร์ชันใหม่ขึ้นเซิร์ฟเวอร์ แล้วเช็กหน้า health ถ้าเวอร์ชันใหม่ไม่ขึ้น ระบบจะย้อนกลับไปเวอร์ชันก่อนหน้าเอง
  • Backup: ทุกคืนจะ backup ฐานข้อมูลไปที่ S3 bucket ส่วนตัว และเก็บไว้ 30 วัน เซิร์ฟเวอร์เพิ่ม backup ได้ แต่ลบไม่ได้
  • Alarm: CloudWatch คอยดูเซิร์ฟเวอร์ ส่วน budget alarm แยกไว้ต่างหาก ลบเซิร์ฟเวอร์ไปก็ไม่หาย
  • ระบบที่ใหญ่กว่า: เคยสร้างระบบที่ใหญ่กว่าบน ECS Fargate และ RDS (บริการของ AWS ที่ดูแลแอปและฐานข้อมูลให้) แล้วก็ปิดไป สำหรับนัดหลักสิบต่อเดือน เซิร์ฟเวอร์เครื่องเดียวก็พอ และยังเปิด port ที่ relay วิดีโอต้องใช้ได้ไม่ถูก ระบบนี้พร้อมกลับมาใช้ถ้างานของเธอโตขึ้น
GitHub ActionsCI → releaseAmazon ECRarm64 imagesEC2 t4g.small · Docker ComposeCaddyTLSNext.jswebsiteGo APIapi modeGo workerworker modePostgreSQL 17Redis 7noevictioncoturnUDP relayAWSS3backups · mediaCloudWatchalarmsAWS Budgetsseparate statepush via OIDCdeploypullnightlymetrics
ภาพ 4. เซิร์ฟเวอร์ EC2 เครื่องเดียวรันทุกอย่าง GitHub Actions เป็นคน deploy และย้อนกลับเองถ้า health check ไม่ผ่าน backup ไปเก็บที่ S3 และ CloudWatch คอยดูเซิร์ฟเวอร์

11

ตอนนี้และต่อจากนี้

VetMiMi ใช้งานจริงแล้วบน AWS งานครบทั้งสี่ช่วง คือโครงสร้างพื้นฐาน การจองและการติดต่อ วิดีโอ และ publishing portal

ที่ยังเหลือคือ ให้ Daw Mi ตรวจอีเมล ให้เจ้าของภาษาตรวจข้อความภาษาพม่า ทำหน้าข้อกฎหมาย เทสต์แบบ end-to-end และซ้อมกู้ข้อมูลจาก backup

บางอย่างเลือกโดยตั้งใจ และจดไว้หมดแล้ว เซิร์ฟเวอร์เครื่องเดียวหมายความว่าถ้าเครื่องนี้ล่ม ทั้งระบบก็ล่ม วิดีโอคอลทำได้แค่ตัวต่อตัว และการเชื่อมกับโซเชียลต้องต่อใหม่ทุก 60-90 วัน ทั้งหมดนี้เหมาะกับงานของคนคนเดียว และแต่ละข้อมีขั้นต่อไปที่วางไว้แล้วถ้างานโตขึ้น

เทคโนโลยี

  • Go
  • chi
  • sqlc
  • OpenAPI 3.1
  • Next.js 16
  • TypeScript
  • PostgreSQL
  • Redis
  • asynq
  • WebRTC
  • coturn
  • Resend
  • Claude API
  • Docker
  • Terraform
  • AWS EC2
  • Amazon ECR
  • CloudWatch
  • GitHub Actions (OIDC)
  • Playwright

อัปเดตล่าสุด:

กรณีศึกษาถัดไปAU-Vanระบบจองรถตู้ผ่าน LINE ไม่มีวันจองซ้อนอ่านกรณีศึกษา

อยากรู้เพิ่มเติม?

ยินดีเล่าให้ฟังทั้งเรื่องโค้ด เหตุผลที่เลือกแบบนี้ และสิ่งที่อยากทำต่อ ส่งข้อความมาได้เลย

ติดต่อพูดคุย