- tRPC là công cụ chia sẻ khả năng suy luận TypeScript trong các ứng dụng TypeScript full-stack, giúp giảm sai lệch kiểu API giữa server và client
- Khi thay đổi API phía server, bạn có thể thấy ngay tác động trong code client qua lỗi TypeScript và tự động hoàn thành, nhờ đó giảm gánh nặng refactor
- Hoạt động mà không cần schema riêng hay bước sinh mã, cho phép xây dựng API type-safe mà không làm tăng quy trình build hay tải runtime
- Cung cấp adapter có thể gắn vào nhiều môi trường JavaScript như React, Next.js, Express, Fastify, AWS Lambda, Solid, Svelte, v.v.
- Luồng cơ bản là định nghĩa procedure, tạo HTTP server, rồi kết nối client bằng cách truyền kiểu
AppRouter; mục tiêu là mang lại trải nghiệm phát triển dùng code API server như một SDK
Cách tRPC thay đổi việc phát triển API
- tRPC nâng cao năng suất bằng cách cho phép server và client trong ứng dụng full-stack cùng tận dụng thông tin kiểu TypeScript
- Trọng tâm là dễ dàng tạo API type-safe từ đầu đến cuối và giảm các công việc lặp lại trong lớp API truyền thống
- Nếu thay đổi phía server ảnh hưởng đến phần sử dụng ở client, TypeScript sẽ báo lỗi, giúp nhanh chóng phát hiện sai lệch kiểu ở ranh giới client-server
Những đặc điểm chính mà lập trình viên cảm nhận được
-
An toàn kiểu tự động
- Khi thay đổi phía server ảnh hưởng đến code client, điều đó sẽ lộ ra dưới dạng lỗi TypeScript
-
Trải nghiệm lập trình viên nhanh
- tRPC không có bước build hay compile
- Có thể dùng mà không cần sinh mã, không có tải runtime, không cần quy trình build riêng
-
Độc lập framework
- Có thể sử dụng trên nhiều framework và runtime JavaScript
- Dễ dàng thêm vào cả các dự án hiện có
-
Tự động hoàn thành
- Mang lại trải nghiệm xử lý code API server như một SDK
- Nhận được gợi ý dựa trên kiểu khi sử dụng endpoint
-
Dấu chân client nhỏ
- tRPC không có dependency và kích thước phía client nhỏ
-
Adapter đi kèm
- Cung cấp adapter cho React, Next.js, Express, Fastify, AWS Lambda, Solid, Svelte, v.v.
Luồng sử dụng cơ bản
- API tRPC bắt đầu bằng cách định nghĩa procedure
- Procedure là các hàm cấu thành backend, có thể kết hợp và có thể được tạo thành query, mutation, subscription
- Nhiều procedure được gom vào router
-
Định nghĩa procedure
- Trong ví dụ, một instance tRPC được tạo bằng
initTRPC.create(), rồi cấu hìnhroutervàpublicProcedure - Procedure
greetingnhận chuỗinamelàm input và trả về chuỗi dạngHello ${input.name} - Phần xác thực input dùng Zod để đảm bảo input từ client khớp chính xác với dạng mà procedure kỳ vọng
- Ở cuối file, kiểu router được export bằng
export type AppRouter = typeof appRouter;để có thể dùng trong code frontend
- Trong ví dụ, một instance tRPC được tạo bằng
-
Tạo HTTP server
- Chạy server tRPC bằng cách truyền
appRoutervàocreateHTTPServer - Trong ví dụ, gọi
listen(3000)để API lắng nghe trên cổng3000 - tRPC cung cấp adapter cho Next.js, Express, môi trường dựa trên Fetch API, Fastify, AWS Lambda và server vanilla Node HTTP
- Ví dụ về môi trường dựa trên Fetch API gồm Astro, Remix, SvelteKit, Cloudflare Workers
- Chạy server tRPC bằng cách truyền
-
Kết nối client và chạy query
- Khi server đã chạy, có thể tạo client bằng
createTRPCClient<AppRouter>và query dữ liệu - Client ví dụ dùng
httpBatchLinkđể kết nối tớihttp://localhost:3000 - Có thể gọi như
trpc.greeting.query({ name: 'John' })để sử dụng proceduregreetingtrên server - Khi tạo client, truyền kiểu
AppRoutersẽ mang lại tự động hoàn thành TypeScript và IntelliSense khớp với API backend mà không cần sinh mã
- Khi server đã chạy, có thể tạo client bằng
Template khởi đầu và ý đồ tạo ra
- Có cung cấp template để tự thử nghiệm: Use this template
- Tác giả tRPC tạo ra tRPC nhằm giảm nhu cầu về lớp API truyền thống, đồng thời mang lại sự tin tưởng rằng ứng dụng không bị hỏng trong quá trình phát triển lặp nhanh
- tRPC đang được sử dụng bởi các đội ngũ công nghệ hàng đầu và nhiều công ty Fortune 500
Phản hồi từ lập trình viên
- Qua nhiều trích dẫn từ lập trình viên, tRPC được đánh giá là công cụ cải thiện chất lượng code, tốc độ bàn giao, mức độ hài lòng của lập trình viên, việc refactor ở ranh giới client-server, xác thực input và trải nghiệm middleware có kiểu
- Trong monorepo TypeScript, tRPC được xem là lựa chọn đơn giản hơn plain REST hoặc GraphQL nhưng vẫn cung cấp kiểu mạnh
- Một use case được giới thiệu là ngay cả khi trả về payload Stripe API từ server, các component React vẫn có thể nhận được kiểu dữ liệu phản hồi
1 bình luận
Ý kiến trên Hacker News
Hiện chúng tôi đang tiến hành loại bỏ tRPC khỏi codebase, và đó đúng là ác mộng vì mức độ gắn kết quá chặt
Nó cũng có mặt khiến các lập trình viên junior không phải suy nghĩ về interface hay mẫu truy cập dữ liệu, và tạo ra một kiểu ánh xạ kéo thẳng từ Prisma tới tận component
Rất tuyệt cho prototype nhanh, nhưng nếu về sau muốn tách codebase ra thì rất nhanh sẽ rơi vào ngõ cụt
Vì nó còn mở rộng sang cả các ngôn ngữ ngoài TypeScript, nên cũng hữu ích khi chuyển backend sang ngôn ngữ khác hoặc tạo client mobile native như Swift, Kotlin
tRPC rất tốt, nhưng rốt cuộc nó chỉ là tầng truyền tải giữa backend và frontend
Để cấu trúc nội bộ của tRPC len sâu vào business logic cũng tệ chẳng khác gì việc không có một tầng controller hay router định nghĩa rõ input, schema và ranh giới tách biệt
Vì vậy, ngay cả nếu sau này rời khỏi tRPC thì có lẽ cũng tương đối đơn giản; chẳng hạn, chuyển cả một subsystem sang chạy trên queue có vẻ cũng không khó
Những thứ như vậy có đường cong học tập, và thường bắt đầu từ các type vô dụng, chẳng giải thích được gì như
type FunctionIWroteTodayArgs = …Qua vài vòng lặp, họ dần nhận ra mục tiêu không phải là sao chép code, mà là mô tả domain và tạo ra các type, interface, API có thể tái sử dụng và hữu ích
Vì vậy tôi nghĩ nên cùng đội cải thiện phần đó, thay vì gỡ bỏ tRPC
Tôi chưa rõ vì sao khai báo HTTP client ở phía server rồi tiêu thụ ở phía client lại tệ hơn
Chúng tôi tạo các service rồi dùng một pattern mà mọi consumer như web interface hay CLI đều dùng; việc những thứ này không bị vỡ là một cải thiện lớn hơn bất kỳ cách nào tôi từng thấy trước đây
Phàn nàn rằng junior không suy nghĩ về interface nhiều khả năng sẽ xuất hiện y hệt dù dùng tRPC hay bất kỳ API nào khác
Notion đã dùng một phong cách API tương tự tRPC, và API đó có trước tRPC khoảng 4 năm
Có thể tự làm những thứ như vậy khá dễ bằng mapped types của TypeScript. Chỉ cần tạo một object type trong đó key là tên API còn value là type
{ request, response }Phía server định nghĩa mỗi API handler là một hàm nhận
APIs["addUser"]["request"]và trả vềPromise, còn client thì expose dưới dạng hàm bất đồng bộ có cùng tham số và kiểu trả vềChúng tôi dùng chiến lược này cho API nội bộ qua HTTPS, API realtime dựa trên WebSocket, IPC giữa Electron và Webview, cũng như IPC webview của OS giữa native iOS/Android và Webview
Với API native, phía server là Swift hoặc Kotlin nên chúng tôi viết lại thủ công type request/response trong TypeScript. Một ngày nào đó có thể sẽ chuyển sang định dạng nhị phân có IDL riêng, nhưng với một API cross-language đơn lẻ đang lớn dần, chi phí developer experience của những thứ như Protobuf hiện vẫn chưa có vẻ đáng bỏ ra
Không hoàn hảo, nhưng nó cung cấp một interface API khá tốt với type cho request/response
Chúng tôi còn tạo một wrapper mock API khoảng 10 dòng có thể gọi kiểu
mockApi((request) => response), trong đó mock function được type-check để đảm bảo implement API đúng cách và trả về một hàm trông y hệt hàm API thật[0]: https://github.com/ferdikoomen/openapi-typescript-codegen
Có vẻ nó chưa được bàn luận thường xuyên vì vẫn chưa có một thư viện được duy trì tốt và quảng bá tốt áp dụng cách tiếp cận này
Khi dùng tRPC, JSON Schema kèm type generation, hay Zod, giá trị quan trọng chính là phần đó
Thậm chí còn yêu cầu truyền trực tiếp đối số generic khi gọi, điều này khá tệ nếu muốn giữ dependency tree ở trạng thái gọn gàng
https://github.com/mikew/transmission-material-ui/blob/maste...
https://github.com/mikew/transmission-material-ui/blob/maste...
Rất thích tRPC. Thật đáng kinh ngạc khi nó đẩy trải nghiệm nhà phát triển đến giới hạn trong stack chỉ dành cho TypeScript, và khiến cộng đồng GraphQL nhận ra các giới hạn và đánh đổi của ngôn ngữ truy vấn
Đồng thời, tRPC đã đi qua chu kỳ thịnh hành rất nhanh, và dường như không có một cuộc dịch chuyển quy mô lớn nào từ REST và GraphQL sang RPC
Tuy vậy, hiện nay có vẻ vẫn có nhiều sự quan tâm đến RPC, và trong framework BFF mà chúng tôi tạo ra (https://wundergraph.com/), chúng tôi cũng đã tiếp thu một phần ý tưởng của tRPC và NextJS trước đây để kết hợp routing dựa trên file với RPC
Ngoài tRPC, chúng tôi tự động tạo JSON Schema cho từng thao tác, đồng thời cũng tạo đặc tả OpenAPI cho toàn bộ tập hợp thao tác
Mọi người thích cách tiếp cận này vì có thể dễ dàng chia sẻ một nhóm endpoint RPC dưới dạng đặc tả OpenAPI hoặc collection Postman. Cũng hầu như không còn tranh luận về HTTP verb; trên thực tế chỉ có truy vấn, thay đổi và đăng ký
Tôi tò mò hiện nay mọi người đang dùng API kiểu GraphQL, REST, RPC như thế nào, và có bao nhiêu người hoặc bao nhiêu nhóm tham gia vào API
Ở phía REST API thì có ts-rest(https://ts-rest.com), zodios(https://www.zodios.org), Hono(https://hono.dev)
Nếu nhóm dùng nhiều ngôn ngữ, cũng có Fern: https://www.buildwithfern.com
Chúng tôi tạo các type GraphQL mỗi khi federated schema thay đổi, và tạo type phản hồi cho truy vấn và thay đổi mỗi khi lưu file
Chúng cũng có thể được trừu tượng hóa dễ dàng bằng thư viện tạo consumer cho endpoint, nhưng ngoài chuyện đó ra thì tôi không rõ thực sự thu được thêm gì so với một web API thông thường
Dù sao thì ở mỗi lời gọi backend, những việc cần làm vẫn phải được thực hiện như cũ
Tôi thích tRPC. Đây chắc chắn là trải nghiệm full-stack developer tốt nhất tôi từng thấy, và đặc biệt khi dùng cùng Zod thì API thực sự rất tuyệt
Tôi xem Zod và tRPC là những dự án quan trọng trong tương lai của TypeScript, và nghĩ rằng trong vài năm tới, trải nghiệm developer lấy cảm hứng từ tRPC sẽ nở rộ mạnh mẽ trên toàn bộ hệ sinh thái TypeScript
Những dự án đã có DNA của tRPC rất rõ ràng gồm UploadThing của Ping(https://github.com/pingdotgg/uploadthing) nhắm tới use case khác, và Lusat của chúng tôi(https://github.com/lusatai/lusat)
Ngoài ra việc type của lỗi validation thay đổi tùy theo schema nào được kiểm tra cũng gây phiền. Xử lý lỗi trở nên khó dự đoán và luôn có quá nhiều trường hợp ngoại lệ
Còn nhiều chỗ cần cải thiện
Việc viết unit test cũng trở nên dễ hơn nhiều
Vấn đề lớn dường như là CRUD an toàn kiểu và migration dữ liệu; Mongoose được nhắc đến khá nhiều, nhưng về mặt type safety thì cảm giác như thụt lùi đáng kể so với Zod/TypeScript
Giờ tôi hơi muốn nói quá lên một chút: trong một số phong cách lập trình nhất định, việc thêm Zod vào codebase có thể mang lại lợi ích lớn ngang với việc thêm TypeScript
Pattern bắt đầu từ schema rồi tạo ra service có type rất hợp với những người thiên về functional programming
Tôi tò mò tRPC xử lý khác biệt phiên bản và migration như thế nào
Field có vòng đời của nó. Khi mới được đưa vào, không client hay server nào biết field đó
Client và server không được khởi động lại tất cả cùng lúc, và cũng không có cùng phiên bản type
Dữ liệu được ghi bằng type của một phiên bản và được đọc bằng type của phiên bản khác
Nếu có thể đảm bảo mọi binary, chương trình đang chạy và dữ liệu đều được nâng cấp, và không có dữ liệu bền vững, thì có thể không thành vấn đề
Nhưng ngay khi có nhiều tổ chức liên quan, rất khó đảm bảo mọi app và server đều được đồng bộ lên phiên bản thư viện mới nhất, được build lại và triển khai lại
Kiểm tra kiểu tĩnh giả định rằng không có khác biệt phiên bản. Toàn bộ code trong binary là một thế giới đóng, được build bằng cùng một phiên bản thư viện định nghĩa type
Zod có thể cho phép các field không biết, sau đó chuyển sang field tùy chọn, rồi khi tình hình ổn định thì biến thành field bắt buộc
Tất nhiên, nhóm người muốn tRPC làm hộ nhất lại chính là nhóm ít có khả năng nghĩ đến loại vấn đề này nhất
Nếu chỉ nhắm tới một ngôn ngữ thì việc không cần schema hay code generation là điều hiển nhiên
Tôi đã dùng tRPC cho hai ứng dụng web khoảng 50 nghìn dòng và rất thích. Trải nghiệm nhà phát triển rất tuyệt
Tuy nhiên thời kỳ tRPC thịnh hành đã qua từ khá lâu, và cảm giác là trào lưu RSC đã nhanh chóng bắt kịp
Dạo này dù có một giải pháp ổn định và tốt như tRPC, mọi người chỉ nói về việc có nên dùng RSC hay không
Tôi không phản đối RSC, nhưng có quá nhiều cuộc thảo luận. Khi xây dựng ứng dụng hiện nay, tRPC là một cách rất thực dụng
Bổ sung: RSC là React Server Components, và nó mang theo triết lý dữ liệu đọc/ghi riêng
Những từ viết tắt mà người khác có thể không biết thì nên được định nghĩa hoặc gắn link
Tôi đã thử dùng tRPC và Next.js trong vài dự án cá nhân và đó là một trải nghiệm tốt
Đặc biệt khi dùng cùng các template đã cấu hình sẵn như Create T3 App(https://create.t3.gg/), rất khó để đánh bại được tốc độ lặp lại khi phát triển
Tôi không biết đến nó cho đến khi một lập trình viên từng làm việc cùng hết lời khen tRPC, nhưng sau khi biết rồi thì nó trông rõ ràng là quá tốt
Chúng tôi đã cùng tạo một ứng dụng T3 (tRPC, Next.js, Tailwind, TypeScript, Prisma), nếu muốn xem thì ở đây: https://github.com/stytchauth/stytch-t3-example
Khi làm việc với TypeScript, API an toàn kiểu thực sự giúp ích rất nhiều