Một lập trình viên đã dành hai tuần để chuyển đổi từ REST API sang tRPC trong một công cụ nội bộ.

Tại sao việc chuyển đổi này lại quan trọng

Stack cũ buộc chúng tôi phải duy trì một DTO, một validator và một bộ kiểu TypeScript riêng biệt cho frontend. Chỉ cần đổi tên một trường ở backend, API vẫn trả về mã 200, UI vẫn tiếp tục render, và lỗi chỉ xuất hiện dưới dạng các giá trị “undefined”. Một khách hàng đã phát hiện ra vấn đề trước bất kỳ ai trong đội ngũ. Các contract của REST đã che giấu vấn đề, và các tệp bổ sung chỉ làm tăng thêm gánh nặng bảo trì mà không mang lại hiệu quả.

Thiết lập REST trông như thế nào

  • OpenAPI spec – một tệp tĩnh mô tả các endpoint nhưng chưa bao giờ đóng vai trò là nguồn sự thật duy nhất (source of truth).
  • Bước tạo Client SDK – một công việc CI tạo ra một wrapper JavaScript.
  • Postman collection – một tài liệu kiểm thử dùng chung mà không ai sử dụng.
  • Các interface TypeScript thủ công – các kiểu dữ liệu được viết tay và phải luôn đồng bộ với máy chủ.

Mỗi thay đổi API đều tác động đến ít nhất ba trong số các tài liệu đó, cộng thêm validator và bất kỳ lệnh gọi fetch hạ nguồn nào. Quy trình này rất dễ gây lỗi và chậm chạp.

tRPC đã tinh gọn mã nguồn như thế nào

tRPC loại bỏ tệp contract riêng biệt. Máy chủ xuất (export) một router type; client sẽ import chính kiểu đó. Chỉ cần đổi tên một trường ở backend, trình soạn thảo sẽ cảnh báo sự không khớp ngay lập tức—không cần chạy code, cũng không cần đợi request thất bại. Lập trình viên đã loại bỏ:

  • Tệp OpenAPI spec.
  • Bước tạo client SDK.
  • Postman collection không còn sử dụng.
  • Thư mục chứa các interface TypeScript thủ công.

Repo trở nên gọn nhẹ hơn, và phản hồi đến ngay trong quá trình phát triển thay vì sau khi triển khai.

Hạn chế của tRPC

tRPC chỉ hoạt động khi cả hai đầu đều sử dụng TypeScript. Nó sẽ bộc lộ hạn chế nếu:

  • Một đội ngũ mobile sử dụng Swift hoặc Kotlin.
  • API phải được sử dụng bởi các đối tác bên ngoài.
  • Bạn cần một giao diện công khai, không phụ thuộc vào ngôn ngữ (language-agnostic).

Việc di chuyển thực tế đòi hỏi những gì

Nỗ lực kéo dài hai tuần không chỉ đơn thuần là sao chép và dán. Lập trình viên đã phải:

  • Viết lại mọi lệnh gọi fetch để sử dụng các hàm client của tRPC.
  • Điều chỉnh logic xử lý lỗi.
  • Cập nhật các component UI vốn dựa vào các trạng thái loading và retry gắn liền với các phản hồi HTTP thô.

Những câu hỏi cần đặt ra trước khi thực hiện

  • Cả frontend và backend đều dùng TypeScript chứ? Nếu không có một hệ thống kiểu chung, tRPC sẽ mất đi lợi thế chính của nó.
  • Cùng một đội ngũ có chịu trách nhiệm cho cả hai phía không? Sự thiếu hụt về quyền sở hữu có thể dẫn đến tình trạng sai lệch contract (contract drift).
  • Bạn đang sửa một lỗi cụ thể hay chỉ đang chạy theo xu hướng? Sự gia tăng năng suất là có thật, nhưng nó nên giải quyết một vấn đề nhức nhối hiện có.

Cách tiếp cận hybrid cho năm 2025

Nhiều đội ngũ tìm thấy điểm cân bằng bằng cách chạy cả hai stack:

  • tRPC cho các lớp ứng dụng nội bộ, nơi mã nguồn hoàn toàn là TypeScript và cùng một đội ngũ quản lý cả server và client.
  • REST cho webhooks, public APIs và tích hợp đối tác, nơi yêu cầu khả năng truy cập không phụ thuộc vào ngôn ngữ.

Sử dụng đúng công cụ cho từng bề mặt tiếp xúc giúp việc phát triển nội bộ luôn nhanh chóng, đồng thời vẫn duy trì được tính mở cần thiết cho các người dùng bên ngoài.

Bài học rút ra: Thay thế REST bằng tRPC có thể loại bỏ các tài liệu contract bị trùng lặp và phát hiện lỗi ngay tại thời điểm chỉnh sửa, nhưng lợi ích chỉ thực sự hiện hữu khi toàn bộ stack dùng chung TypeScript và đội ngũ có thể hấp thụ được chi phí di chuyển. Một chiến lược hỗn hợp cho phép bạn gặt hái lợi ích mà không loại bỏ các người dùng không sử dụng TypeScript.