นักพัฒนาใช้เวลาสองสัปดาห์ในการเปลี่ยนจาก REST API มาเป็น tRPC ในเครื่องมือภายในองค์กร

ทำไมการเปลี่ยนครั้งนี้ถึงสำคัญ

Stack เดิมบังคับให้เราต้องดูแลทั้ง DTO, validator และชุด TypeScript types แยกต่างหากสำหรับ frontend เมื่อมีการเปลี่ยนชื่อฟิลด์ใน backend ตัว API ก็ยังคงส่งค่า 200 กลับมา UI ยังคงแสดงผลต่อไป และบั๊กจะปรากฏออกมาในรูปแบบของค่า "undefined" เท่านั้น ลูกค้าเป็นคนพบปัญหาก่อนคนในทีมเสียอีก สัญญา (contracts) ของ REST ได้ซ่อนปัญหาเอาไว้ และไฟล์ส่วนเกินเหล่านั้นก็เพิ่มภาระในการดูแลรักษาโดยไม่คุ้มค่าเลย

รูปแบบการตั้งค่าแบบ REST เดิมเป็นอย่างไร

  • OpenAPI spec – ไฟล์สแตติกที่ใช้อธิบาย endpoints แต่ไม่เคยทำหน้าที่เป็นแหล่งข้อมูลที่ถูกต้องเพียงหนึ่งเดียว (source of truth)
  • Client SDK generation step – งานใน CI ที่สร้าง JavaScript wrapper ขึ้นมา
  • Postman collection – ไฟล์สำหรับทดสอบที่ใช้ร่วมกันแต่ไม่มีใครใช้
  • Manual TypeScript interfaces – types ที่เขียนด้วยมือซึ่งต้องคอยปรับให้ตรงกับ server อยู่เสมอ

ทุกการเปลี่ยนแปลงของ API จะต้องไปแตะต้อง artifact อย่างน้อยสามอย่างจากรายการข้างต้น รวมไปถึง validator และการเรียกใช้ fetch ต่างๆ ในลำดับถัดไป กระบวนการนี้จึงเกิดข้อผิดพลาดได้ง่ายและล่าช้า

tRPC ช่วยลดขนาด codebase ได้อย่างไร

tRPC ช่วยกำจัดไฟล์ contract ที่แยกต่างหากออกไป โดย server จะ export router type ออกมา และ client จะ import type เดียวกันนั้นมาใช้ เมื่อมีการเปลี่ยนชื่อฟิลด์ใน backend ตัว editor จะแจ้งเตือนความไม่สอดคล้องทันที โดยไม่ต้องรันโค้ดหรือรอให้ request ล้มเหลว นักพัฒนาจึงสามารถตัดสิ่งเหล่านี้ออกไปได้:

  • ไฟล์ OpenAPI spec
  • ขั้นตอนการสร้าง client SDK
  • Postman collection ที่ไม่ได้ใช้งาน
  • โฟลเดอร์ของ manual TypeScript interfaces

repo มีความกระชับมากขึ้น และได้รับ feedback ในระหว่างการพัฒนาแทนที่จะเป็นหลังจากการ deploy

ข้อจำกัดของ tRPC

tRPC จะทำงานได้ดีก็ต่อเมื่อทั้งสองฝั่งใช้ TypeScript เท่านั้น ซึ่งจะมีข้อจำกัดหาก:

  • ทีม mobile ใช้ Swift หรือ Kotlin
  • API ต้องถูกใช้งานโดยพาร์ทเนอร์ภายนอก
  • คุณต้องการ interface สาธารณะที่ไม่ยึดติดกับภาษาใดภาษาหนึ่ง (language-agnostic)

สิ่งที่ต้องทำจริง ๆ ในการย้ายระบบ

ความพยายามตลอดสองสัปดาห์นั้นเป็นมากกว่าแค่การ copy-and-paste นักพัฒนาต้อง:

  • เขียนการเรียกใช้ fetch ใหม่ทั้งหมดเพื่อใช้ฟังก์ชัน client ของ tRPC
  • ปรับปรุง logic การจัดการ error
  • อัปเดต UI components ที่ต้องพึ่งพาสถานะ loading และ retry ซึ่งผูกติดกับ raw HTTP responses

คำถามที่ควรพิจารณาก่อนเริ่ม

  • ทั้ง frontend และ backend ใช้ TypeScript หรือไม่? หากไม่มีระบบ type ที่ใช้ร่วมกัน tRPC จะสูญเสียข้อได้เปรียบหลักไป
  • ทีมเดียวกันเป็นผู้รับผิดชอบทั้งสองฝั่งหรือไม่? ช่องว่างในการดูแล (ownership gaps) อาจทำให้เกิดปัญหา contract drift กลับมาอีกครั้ง
  • คุณกำลังแก้บั๊กที่เกิดขึ้นจริงหรือแค่ทำตามเทรนด์? แม้ว่าประสิทธิภาพการทำงานจะเพิ่มขึ้นจริง แต่ควรใช้เพื่อแก้ปัญหา (pain point) ที่มีอยู่เดิม

แนวทางแบบไฮบริดสำหรับปี 2025

หลายทีมพบจุดที่ลงตัวด้วยการรันทั้งสอง stack:

  • tRPC สำหรับ internal application layers ในกรณีที่ codebase ทั้งหมดเป็น TypeScript และทีมเดียวกันเป็นเจ้าของทั้ง server และ client
  • REST สำหรับ webhooks, public APIs และการเชื่อมต่อกับพาร์ทเนอร์ ในกรณีที่ต้องการการเข้าถึงแบบ language-agnostic

การใช้เครื่องมือที่เหมาะสมกับแต่ละส่วนช่วยให้การพัฒนาภายในรวดเร็ว ในขณะที่ยังคงความเปิดกว้างที่จำเป็นสำหรับผู้ใช้งานภายนอก

สรุป: การเปลี่ยนจาก REST มาเป็น tRPC สามารถกำจัด artifact ของ contract ที่ซ้ำซ้อนและช่วยให้พบข้อผิดพลาดได้ตั้งแต่ตอนแก้ไขโค้ด แต่ผลลัพธ์จะเกิดขึ้นจริงก็ต่อเมื่อทั้ง stack ใช้ TypeScript ร่วมกัน และทีมสามารถรับภาระต้นทุนในการย้ายระบบได้ การใช้กลยุทธ์แบบผสมผสานจะช่วยให้คุณได้รับประโยชน์โดยไม่ปิดกั้นผู้ใช้งานที่ไม่ใช้ TypeScript