- tRPC, full-stack TypeScript uygulamalarında TypeScript çıkarımını paylaşarak sunucu ile istemci arasındaki API tip uyumsuzluklarını azaltan bir araçtır
- Sunucu API’si değiştiğinde istemci kodunda etkileri anında TypeScript hataları ve otomatik tamamlama ile görebilirsiniz; bu da refactor yükünü azaltır
- Ayrı bir şema ya da kod üretimi adımı olmadan çalıştığı için build sürecini ve runtime yükünü artırmadan type-safe API oluşturmayı mümkün kılar
- React, Next.js, Express, Fastify, AWS Lambda, Solid, Svelte gibi birçok JavaScript ortamına bağlanabilen adaptörler sunar
- Temel akış; prosedür tanımı, HTTP sunucusu oluşturma ve
AppRoutertipini geçirerek istemciyi bağlamadan oluşur; amaç, API sunucu kodunu bir SDK gibi kullanma deneyimidir
tRPC’nin değiştirdiği API geliştirme yaklaşımı
- tRPC, full-stack uygulamalarda sunucu ve istemcinin aynı TypeScript tip bilgisini kullanmasını sağlayarak üretkenliği artırır
- Temel amaç, uçtan uca type-safe API’leri kolayca oluşturmak ve geleneksel API katmanlarının getirdiği tekrar eden işleri azaltmaktır
- Sunucu tarafındaki değişiklikler istemci kullanımını etkilediğinde TypeScript hata verdiği için, istemci-sunucu sınırındaki tip uyumsuzluklarını hızla fark edebilirsiniz
Geliştiricilerin doğrudan hissettiği başlıca özellikler
-
Otomatik tip güvenliği
- Sunucu tarafındaki değişiklikler istemci kodunu etkilediğinde bu durum TypeScript hataları olarak görünür
-
Hızlı geliştirici deneyimi
- tRPC’de build veya compile aşaması yoktur
- Kod üretimi, runtime yükü veya ek build adımları olmadan kullanılabilir
-
Framework bağımsızlığı
- JavaScript framework’leri ve runtime’larının genelinde kullanılabilir
- Mevcut projelere de kolayca eklenebilir
-
Otomatik tamamlama
- API sunucu kodunu bir SDK gibi ele alma deneyimi sunar
- Endpoint kullanırken tipe dayalı ipuçları alınabilir
-
Küçük istemci ayak izi
- tRPC’nin bağımlılığı yoktur ve istemci tarafı boyutu küçüktür
-
Dahil adaptörler
- React, Next.js, Express, Fastify, AWS Lambda, Solid, Svelte vb. için adaptörler sağlar
Temel kullanım akışı
- tRPC API’si önce bir procedure (prosedür) tanımlayarak başlar
- Prosedürler backend’i oluşturan fonksiyonlardır; birleştirilebilir ve query, mutation, subscription olarak tanımlanabilir
- Birden fazla prosedür router içinde gruplanır
-
Prosedür tanımı
- Örnekte
initTRPC.create()ile bir tRPC instance’ı oluşturuluproutervepublicProcedureyapılandırılır greetingprosedürü girdi olaraknamestring’ini alır veHello ${input.name}biçiminde bir string döndürür- Girdi doğrulaması için Zod kullanılır; böylece istemci girdisinin prosedürün beklediği yapıyla tam olarak eşleşmesi sağlanır
- Dosyanın sonunda
export type AppRouter = typeof appRouter;ile router tipi dışa aktarılır ve frontend kodunda kullanılabilir hale gelir
- Örnekte
-
HTTP sunucusu oluşturma
- tRPC sunucusunu çalıştırmak için
createHTTPServeriçineappRouterverilir - Örnekte API’nin
3000portunda dinlemesi içinlisten(3000)çağrılır - tRPC; Next.js, Express, Fetch API tabanlı ortamlar, Fastify, AWS Lambda ve vanilla Node HTTP sunucuları için adaptörler sunar
- Fetch API tabanlı ortamlara Astro, Remix, SvelteKit ve Cloudflare Workers örnek verilir
- tRPC sunucusunu çalıştırmak için
-
İstemciyi bağlama ve query çalıştırma
- Sunucu çalıştıktan sonra
createTRPCClient<AppRouter>ile istemci oluşturulup veri sorgulanabilir - Örnek istemci,
httpBatchLinkkullanarakhttp://localhost:3000adresine bağlanır trpc.greeting.query({ name: 'John' })gibi bir çağrıyla sunucudakigreetingprosedürü kullanılabilir- İstemci oluşturulurken
AppRoutertipi verilirse, backend API ile uyumlu TypeScript otomatik tamamlama ve IntelliSense kod üretimi olmadan elde edilir
- Sunucu çalıştıktan sonra
Başlangıç şablonu ve oluşturulma amacı
- Doğrudan deneyebilmeniz için Use this template şablonu sunuluyor
- tRPC’nin geliştiricisi, geleneksel API katmanlarına olan ihtiyacı azaltmak ve hızlı iterasyon yaparken uygulamanın bozulmayacağına dair güven vermek için tRPC’yi geliştirdi
- tRPC, önde gelen teknik ekipler ve birçok Fortune 500 şirketi tarafından kullanılıyor
Geliştirici tepkileri
- Çeşitli geliştirici alıntılarında tRPC; kod kalitesini, teslim hızını, geliştirici memnuniyetini, istemci-sunucu sınırındaki refactor süreçlerini, girdi doğrulamasını ve tiplenmiş middleware deneyimini iyileştiren bir araç olarak değerlendiriliyor
- TypeScript monorepo’larında plain REST veya GraphQL’e göre daha basit ama daha güçlü tipler sunan bir seçenek olarak görülüyor
- Sunucuda Stripe API payload’ı döndürülse bile React bileşenlerinde yanıt veri tiplerinin alınabildiği bir kullanım örneği de paylaşılıyor
1 yorum
Hacker News yorumları
Şu an mevcut kod tabanından tRPC'yi kaldırma sürecindeyiz; sıkı bağlılık yüzünden kâbus gibiydi.
Junior geliştiricileri arayüzleri veya veri erişim kalıplarını düşünmemeye iten bir yönü de vardı; Prisma'dan bileşenlere kadar aynen uzanan bir eşleme oluştu.
Hızlı prototipleme için harika, ama daha sonra kod tabanını ayırmaya çalışınca çok çabuk çıkmaz sokağa giriyorsunuz.
TypeScript dışındaki dillere de genişleyebildiği için, backend'i başka bir dile taşırken veya Swift, Kotlin gibi native mobil istemciler oluştururken de yardımcı olur.
tRPC harika, ama sonuçta backend ile frontend arasındaki bir taşıma katmanından ibaret.
tRPC'nin iç yapısını iş mantığının derinlerine kadar kullandırmak, girdileri, şemaları ve ayrımı net tanımlayan bir controller veya router katmanı olmaması kadar kötü.
Bu yüzden ileride tRPC'den ayrılsak bile nispeten basit olacağını düşünüyorum; örneğin bütün bir alt sistemi kuyruk üzerinde çalışacak şekilde taşımak da zor olmayacak gibi.
Bunun bir öğrenme eğrisi var ve genelde
type FunctionIWroteTodayArgs = …gibi işe yaramaz, hiçbir şeyi açıklamayan tiplerle başlıyor.Birkaç iterasyondan sonra hedefin kodu kopyalamak değil, domain'i açıklayan, yeniden kullanılabilir ve faydalı tipler, arayüzler ve API'ler oluşturmak olduğu yavaş yavaş anlaşılıyor.
Bu yüzden tRPC'yi söküp atmak yerine, ekiple birlikte bu kısmı iyileştirmenin doğru olduğunu düşünüyorum.
Sunucu tarafında bir HTTP istemcisi tanımlayıp istemci tarafında tüketmenin neden daha kötü olduğunu pek anlamıyorum.
Biz servisler oluşturuyoruz ve web arayüzü ya da CLI gibi tüm tüketicilerin kullandığı bir kalıp kullanıyoruz; bunların bozulmaması, eskiden gördüğüm her yaklaşıma göre büyük bir iyileştirmeydi.
Junior geliştiricilerin arayüzleri düşünmediği şikâyeti, yalnızca tRPC'de değil, herhangi bir API kullanıldığında da muhtemelen aynen ortaya çıkar.
Notion'da tRPC'ye benzer bir API stilini kullanıyorduk ve bu API, tRPC'den yaklaşık 4 yıl önce vardı.
TypeScript'in mapped type'larıyla bunu kendiniz kolayca yapabilirsiniz. Anahtarları API adları, değerleri de
{ request, response }tipinde olan bir nesne tipi oluşturmanız yeterli.Sunucu tarafını, her API handler'ını
APIs["addUser"]["request"]alıpPromisedöndüren bir fonksiyon olarak tanımlarsınız; istemciyi de aynı argüman ve dönüş tiplerine sahip async fonksiyonlar olarak sunarsınız.Bu stratejiyi HTTPS internal API'lerde, WebSocket tabanlı gerçek zamanlı API'lerde, Electron ile Webview arasındaki IPC'de, iOS/Android native ile Webview arasındaki OS webview IPC'sinde kullandık.
Native API'lerde sunucu tarafı Swift veya Kotlin olduğu için request/response tiplerini TypeScript'te elle yeniden yazıyoruz. Bir gün bunu kendi IDL'ine sahip binary bir formata çevireceğiz; ancak yavaş yavaş büyüyen tek bir çapraz dil API'si için Protobuf gibi şeylerin geliştirici deneyimi maliyeti henüz buna değiyor gibi görünmedi.
Mükemmel değil ama request/response tipleri eklenmiş oldukça iyi bir API arayüzü sağlıyor.
Ayrıca
mockApi((request) => response)gibi çağrılabilen 10 satırlık bir mock API wrapper'ı yaptık; mock fonksiyonun API'yi doğru uygulayıp uygulamadığını tip denetiminden geçiriyor ve gerçek API fonksiyonuyla birebir aynı görünen bir fonksiyon döndürüyor.[0]: https://github.com/ferdikoomen/openapi-typescript-codegen
Bu yaklaşımı benimseyen, iyi bakılan ve tanıtımı yapılan bir kütüphane henüz çıkmadığı için sık tartışılmıyor sanırım.
tRPC, JSON Schema ile tip üretimi, Zod gibi şeyleri kullanırken önemli değer tam da bu kısım.
Çağırırken generic argümanı doğrudan vermenizi bile istiyor; dependency tree'yi düzenli tutmaya çalışırken bu epey kötü.
https://github.com/mikew/transmission-material-ui/blob/maste...
https://github.com/mikew/transmission-material-ui/blob/maste...
tRPC’yi çok seviyorum. TypeScript’e özel bir stack’te geliştirici deneyimini sınırlarına kadar zorlaması etkileyici; GraphQL topluluğunun da sorgu dilinin sınırlarını ve ödünlerini fark etmesini sağladı.
Aynı zamanda tRPC gerçekten çok hızlı bir hype döngüsünden geçti ve REST ile GraphQL’den RPC’ye büyük ölçekli bir göç yaşanıyor gibi görünmüyor.
Yine de bugünlerde RPC’ye ilgi epey yüksek görünüyor; bizim yaptığımız BFF framework’ünde (https://wundergraph.com/) de tRPC’den ve eski NextJS fikirlerinden bazılarını alıp dosya tabanlı routing ile RPC’yi birleştirdik.
tRPC’ye ek olarak her işlem için otomatik JSON Schema üretiyor, tüm işlem kümesi için de OpenAPI spesifikasyonu oluşturuyoruz.
RPC endpoint paketlerini OpenAPI spesifikasyonu veya Postman koleksiyonu olarak kolayca paylaşabildikleri için insanlar bu yaklaşımı seviyor. HTTP fiilleri tartışması da neredeyse yok; pratikte yalnızca sorgular, değişiklikler ve abonelikler var.
Bugünlerde GraphQL, REST ve RPC tarzı API’leri nasıl kullandığınızı, API’lere kaç kişinin ya da kaç ekibin dahil olduğunu merak ediyorum.
REST API tarafında ts-rest(https://ts-rest.com), zodios(https://www.zodios.org), Hono(https://hono.dev) var.
Ekipte birden fazla dil kullanılıyorsa Fern de var: https://www.buildwithfern.com
Biz birleşik şema her değiştiğinde GraphQL tiplerini üretiyoruz; dosya her kaydedildiğinde de sorguların ve değişikliklerin yanıt tiplerini üretiyoruz.
Endpoint tüketicisi üreten kütüphanelerle kolayca soyutlanabilirler; bunun dışında sıradan bir web API’ye kıyasla gerçekte ne kazandığımızı bilmiyorum.
Sonuçta her backend çağrısında yapılması gereken iş yine aynen yapılmak zorunda.
tRPC’yi seviyorum. Şimdiye kadar gördüğüm full-stack geliştirici deneyimleri arasında açık ara en iyisi; özellikle Zod ile birlikte kullanıldığında API gerçekten harika oluyor.
Zod ve tRPC’nin TypeScript’in geleceğinde önemli projeler olduğunu düşünüyorum; önümüzdeki birkaç yıl boyunca TypeScript ekosistemi genelinde tRPC’den ilham alan geliştirici deneyimlerinin ciddi biçimde yayılacağını sanıyorum.
tRPC’nin DNA’sını açıkça taşıyan projeler arasında farklı kullanım senaryolarını hedefleyen Ping’in UploadThing’i (https://github.com/pingdotgg/uploadthing) ve bizim Lusat’ımız (https://github.com/lusatai/lusat) var.
Ayrıca doğrulama hatalarının tipinin hangi şemayı kontrol ettiğinize göre değişmesi de can sıkıcı. Hata işleme öngörülemez hale geliyor ve istisnai durumlar hep fazla oluyor.
Geliştirilecek çok yanı var.
Birim testleri yazmak da çok daha kolaylaştı.
Büyük sorun tip güvenli CRUD ve veri migrasyonları gibi görünüyor; Mongoose’tan çok bahsediliyor ama tip güvenliği açısından Zod/TypeScript’e göre ciddi bir geri adım gibi hissettiriyor.
Şu an biraz abartılı konuşmak istiyorum ama belirli bir programlama tarzında codebase’e Zod eklemenin TypeScript eklemek kadar büyük bir kazanç olduğunu bile söyleyebilirim.
Şemadan başlayıp tipli servisler oluşturma deseni, fonksiyonel programlamaya yatkın kişilere iyi uyuyor.
tRPC’nin sürüm farklarını ve migrasyonları nasıl ele aldığını merak ediyorum.
Alanların bir yaşam döngüsü vardır. İlk eklendiklerinde hiçbir istemci veya sunucu o alanı bilmez.
İstemciler ve sunucular tek seferde hep birlikte yeniden başlatılmaz ve aynı tip sürümüne de sahip olmazlar.
Veri bir tip sürümüyle yazılıp başka bir tip sürümüyle okunur.
Tüm ikililerin, çalışan programların ve verinin yükseltileceğini garanti edebiliyorsanız ve kalıcı veri yoksa bu sorun olmayabilir.
Ama birden fazla organizasyon işin içine girdiği anda tüm uygulamaların ve sunucuların en güncel kütüphane sürümüne senkronize olacağını, yeniden derleneceğini ve yeniden dağıtılacağını garanti etmek zorlaşır.
Statik tip denetimi sürüm farkı olmadığını varsayar. İkili içindeki tüm kod, tipleri tanımlayan aynı kütüphane sürümüyle derlenmiş kapalı bir dünyadır.
Zod bilinmeyen alanlara izin verebilir; ardından bunları opsiyonel alanlara taşıyıp durum istikrara kavuştuğunda zorunlu alan haline getirebilirsiniz.
Elbette tRPC’nin yaptıklarını en çok isteyen kesim, bu tür sorunları düşünme ihtimali en düşük olan kesimdir.
Yalnızca tek bir dili hedefliyorsanız şema veya kod üretimine gerek olmaması zaten doğal.
tRPC’yi yaklaşık 50 bin satırlık iki web uygulamasında kullandım ve çok sevdim. Geliştirici deneyimi harika.
Yine de tRPC’nin popülerlik dönemi bir süre önce geçti; RSC hype’ı hızla arayı kapatmış gibi.
Bugünlerde tRPC gibi istikrarlı ve iyi bir çözüm varken bile herkes yalnızca RSC kullanıp kullanmamayı konuşuyor.
RSC’ye karşı değilim ama tartışma çok fazla. Günümüzde uygulama geliştirirken tRPC çok pratik bir yol.
Ek: RSC, React Server Components demek ve beraberinde kendine özgü bir okuma/yazma veri felsefesi getiriyor.
İnsanların bilmeyebileceği kısaltmaları tanımlasanız ya da bağlantı verseniz iyi olur.
Birkaç kişisel projede tRPC ve Next.js kullandım ve iyi bir deneyimdi
Özellikle Create T3 App(https://create.t3.gg/) gibi önceden yapılandırılmış şablonlarla birlikte kullanıldığında yineleme hızını yakalamak zor
Birlikte çalıştığım bir geliştirici tRPC’yi öve öve bitirene kadar bilmiyordum; öğrendikten sonra ise çok bariz şekilde iyi göründü
Birlikte bir T3 uygulaması (tRPC, Next.js, Tailwind, TypeScript, Prisma) yaptık; görmek isterseniz burada: https://github.com/stytchauth/stytch-t3-example
TypeScript ile çalışırken tip güvenli API gerçekten çok yardımcı oluyor