เชื่อม Express กับ Database ให้เป็น Flow ที่อ่านง่ายและดูแลง่าย
การทำ CRUD API ด้วย Express ไม่ได้ยากแค่เรื่อง query แต่ยากที่การออกแบบ flow ของงานให้แยกหน้าที่ชัดเจน ตั้งแต่ route, controller, service ไปจนถึง repository หรือ model เพื่อให้โค้ดอ่านง่าย ทดสอบง่าย และรองรับการเติบโตของ

เชื่อม Express กับ Database ให้เป็น Flow ที่อ่านง่ายและดูแลง่าย
หลายคนเริ่มทำ API ด้วย Express จากโจทย์ง่าย ๆ อย่าง CRUD ได้แก่ Create, Read, Update และ Delete ซึ่งในช่วงแรกอาจดูเหมือนเป็นแค่การรับ request แล้วเขียน query ไปคุยกับฐานข้อมูลให้จบในจุดเดียว แต่เมื่อระบบเริ่มมีเงื่อนไขมากขึ้น ความซับซ้อนที่แท้จริงจะไม่ใช่เรื่อง query เพียงอย่างเดียว แต่คือการออกแบบ flow ของการทำงานให้ชัดเจน
Express เป็นเพียงประตูรับคำขอจากภายนอก ส่วน database คือแหล่งความจริงของข้อมูล หากเราแบ่งเส้นทางการทำงานตั้งแต่ request ไปจนถึง query ได้ดี โค้ดจะอ่านง่าย แก้ไขง่าย และรองรับการเปลี่ยนแปลงในอนาคตได้มากกว่าเดิม
ทำไมไม่ควรให้ route คุยกับ database โดยตรง
ปัญหาที่พบได้บ่อยในโปรเจกต์เริ่มต้นคือใส่ทุกอย่างไว้ใน handler ของ route เดียว ไม่ว่าจะเป็นการ validate ข้อมูล การเขียน query การจัดการ error และการส่ง response กลับให้ผู้ใช้ วิธีนี้อาจทำงานได้ในช่วงแรก แต่เมื่อเวลาผ่านไปไฟล์ route จะเริ่มยาวและปนกันจนดูแลยาก
ผลลัพธ์คือ:
- อ่าน flow ของระบบได้ยาก
- แก้ไข business rule ได้ลำบาก
- test แต่ละส่วนแยกกันได้ยาก
- โค้ดมีโอกาสซ้ำในหลาย endpoint
แนวคิดที่ดีกว่าคือแยกหน้าที่ของแต่ละชั้นให้ชัดเจน เพื่อให้แต่ละส่วนรับผิดชอบเฉพาะสิ่งที่ตัวเองควรทำ
โครงสร้างที่แนะนำ: route, controller, service, repository
โครงสร้างที่นิยมและอ่านง่าย มักแบ่งออกเป็น 4 ชั้นหลักดังนี้
1. Route: รับทางเข้า
route ทำหน้าที่กำหนด endpoint และเชื่อมคำขอไปยังส่วนถัดไป ไม่ควรบรรจุ logic ยาว ๆ หรือ query จำนวนมาก
ตัวอย่างหน้าที่ของ route:
- รับคำขอจาก
POST /users - ส่งต่อไปยัง controller ที่เหมาะสม
- อาจแนบ middleware ที่จำเป็น เช่น authentication
2. Controller: แปล request เป็นคำสั่ง
controller ทำหน้าที่รับ request แล้วแปลงให้เป็นรูปแบบที่ระบบภายในเข้าใจ เช่น ดึงค่า params, body หรือ query ออกมา จากนั้นเรียก service ให้ทำงาน
controller ควรโฟกัสที่:
- รับข้อมูลจาก request
- เรียก service
- คืน response และ status code ที่เหมาะสม
3. Service: ตัดสินใจทาง business
service เป็นหัวใจของ business logic เช่น กฎว่าห้ามใช้ email ซ้ำ ต้องตรวจเงื่อนไขบางอย่างก่อนสร้างข้อมูล หรืออาจต้องส่งอีเมลต้อนรับหลังสร้างผู้ใช้สำเร็จ
service จึงเป็นชั้นที่เหมาะกับการใส่กฎของระบบ เพราะช่วยให้ logic ไม่กระจายไปทั่ว route หรือ controller
4. Repository/Model: คุยกับ database
ชั้นนี้รับผิดชอบการเข้าถึงฐานข้อมูลโดยตรง เช่น select, insert, update, delete โดยไม่ควรแบก business rule มากเกินไป
ข้อดีของการแยกชั้นนี้คือ:
- เปลี่ยนวิธีเข้าถึงฐานข้อมูลได้ง่ายขึ้น
- ทดสอบ logic โดย mock database ได้สะดวก
- query ไม่ปะปนกับ logic ส่วนอื่น
ตัวอย่าง flow ของการสร้างผู้ใช้
ลองดูตัวอย่าง endpoint POST /users ซึ่งเป็น flow ที่พบได้บ่อย
- client ส่งคำขอ
POST /users - route รับคำขอและส่งต่อไปยัง controller
- controller ตรวจรูปแบบข้อมูลเบื้องต้นหรือเรียก validator
- service ตรวจว่า email ซ้ำหรือไม่
- ถ้าไม่ซ้ำ repository หรือ model จึงค่อย insert ลง database
- controller ส่ง response กลับด้วยสถานะที่เหมาะสม เช่น
201 Created
แนวทางนี้ช่วยให้การทำงานแต่ละขั้นชัดเจนมาก หากภายหลังต้องเพิ่มเงื่อนไข เช่น
- ห้ามสมัครซ้ำ
- บังคับใช้กฎของรหัสผ่าน
- ส่ง welcome email
- บันทึก audit log
เราจะสามารถเพิ่มเข้าไปใน service หรือแยกเป็นส่วนเสริมที่เหมาะสมได้ โดยไม่ทำให้ handler เดิมกลายเป็นก้อนโค้ดยาว ๆ ที่ดูแลยาก
เริ่มจาก CRUD ง่าย ๆ ได้ แต่ควรออกแบบให้พร้อมโต
สำหรับมือใหม่ การเริ่มจาก endpoint พื้นฐานถือเป็นแนวทางที่ดี เช่น
GET /itemsGET /items/:idPOST /itemsPATCH /items/:idDELETE /items/:id
แม้จะเป็น CRUD แบบง่าย แต่ก็ควรฝึกคิดเรื่องโครงสร้างตั้งแต่ต้น เพราะถ้าเริ่มจากนิสัยที่ดี ระบบจะขยายต่อได้ง่ายกว่าการย้อนกลับมา refactor ทั้งหมดภายหลัง
การออกแบบ flow ที่ดีไม่ได้แปลว่าต้องทำให้ซับซ้อนเกินจำเป็น แต่คือการแยกส่วนเท่าที่จำเป็นเพื่อให้โค้ดไม่ปนกัน
การตอบ status code ให้ชัดเจนสำคัญมาก
API ที่ดีไม่ใช่แค่ทำงานได้ แต่ต้องสื่อสารกับผู้ใช้หรือ frontend ได้ชัดเจนด้วย หนึ่งในเครื่องมือสำคัญคือ HTTP status code
ตัวอย่างที่ควรใช้ให้เหมาะสม:
201เมื่อสร้างข้อมูลสำเร็จ404เมื่อไม่พบข้อมูลที่ร้องขอ400เมื่อ input ไม่ถูกต้อง204เมื่อการลบสำเร็จและไม่จำเป็นต้องส่งข้อมูลกลับ
ในบางกรณีทีมอาจเลือกส่ง object กลับหลังลบหรืออัปเดตสำเร็จ ซึ่งทำได้เช่นกัน ขอเพียงมีข้อตกลงร่วมกันให้ชัดเจนทั้งระบบ
การจัดการ connection ให้ถูกวิธี
อีกเรื่องที่สำคัญมากแต่หลายคนมองข้าม คือการเชื่อมต่อกับฐานข้อมูล
สิ่งที่ไม่ควรทำคือเปิด connection ใหม่ทุก request แบบไม่จำเป็น เพราะจะกระทบต่อประสิทธิภาพของระบบและเพิ่มภาระให้ database โดยไม่จำเป็น
แนวทางที่ควรใช้คือ:
- ใช้ connection pool
- ใช้ client หรือ library ตามแนวทางที่ framework หรือ database แนะนำ
- จัดการ lifecycle ของการเชื่อมต่อให้เหมาะสม
เมื่อระบบมีผู้ใช้พร้อมกันมากขึ้น วิธีจัดการ connection จะส่งผลต่อความเสถียรของ API อย่างชัดเจน
อย่าลืมรับมือกับปัญหาที่เกิดจริงใน database
ในโลกการทำงานจริง ฐานข้อมูลไม่ได้ตอบกลับอย่างสวยงามเสมอไป จึงควรเตรียมรับมือกับกรณีต่าง ๆ เช่น
- database ตอบสนองช้า
- query เกิด error
- unique constraint fail
- การทำงานหลายขั้นที่ต้องสำเร็จพร้อมกัน
กรณีสุดท้ายคือเรื่อง transaction ซึ่งสำคัญมาก หากงานหนึ่งต้องเขียนข้อมูลหลายตารางหรือหลายขั้นตอนที่เกี่ยวข้องกัน การใช้ transaction จะช่วยให้ข้อมูลไม่อยู่ในสถานะค้างครึ่งกลาง เช่น บันทึกส่วนแรกสำเร็จแต่ส่วนที่สองล้มเหลว
การออกแบบให้ระบบ handle error เหล่านี้อย่างชัดเจน จะช่วยลดปัญหา data inconsistency และทำให้ทีม debug ได้ง่ายขึ้นมาก
Flow ที่ดีช่วยให้ test ง่ายขึ้น
เมื่อแต่ละชั้นมีหน้าที่ชัดเจน การทดสอบก็จะง่ายขึ้นตามไปด้วย เช่น
- ทดสอบ route ว่าส่งต่อถูก endpoint
- ทดสอบ controller ว่าจัดการ request/response ถูกต้อง
- ทดสอบ service ว่าทำ business logic ถูกต้อง
- ทดสอบ repository ว่าคุยกับ database ได้ตามที่ต้องการ
หากทุกอย่างถูกรวมใน handler เดียว การทดสอบจะยากและเปราะบาง เพราะต้องจัดการหลายเรื่องพร้อมกันในการทดสอบหนึ่งครั้ง
สรุป
Express ไม่ได้มีหน้าที่เป็นมากกว่าประตูของ API ส่วน database คือแหล่งเก็บความจริงของข้อมูล สิ่งที่ทำให้ระบบอ่านง่ายและดูแลง่ายในระยะยาว คือการคิดเป็น flow ไม่ใช่คิดแค่ query
เมื่อแยกหน้าที่ของ route, controller, service และ repository หรือ model ให้ชัด เราจะได้โค้ดที่เป็นระเบียบ รองรับ business rule ที่เพิ่มขึ้นได้ดี และทดสอบได้ง่ายกว่าเดิมมาก
ต่อให้เริ่มจาก CRUD ง่าย ๆ ก็ควรฝึกออกแบบเส้นทางของข้อมูลตั้งแต่ request ไปจนถึง database ให้ชัดเจน เพราะนี่คือพื้นฐานสำคัญของการทำ backend ที่เติบโตต่อได้จริง