Təsəvvür edin: title sahəsinin adını heading etdiniz — həm verilənlər bazasında, həm də serverdə. Server işləyir, backend testləri yaşıldır, amma istifadəçilərin tətbiqində qeydlərin başlıqları yoxa çıxıb: frontend hələ də note.title oxuyur. Xətanı kompilyator yox, istifadəçi tapdı. Bu dərsdə tRPC-nin belə xətaları «istifadəçi tapdı» vəziyyətindən «redaktorda qırmızı dalğalı xətt» vəziyyətinə necə çevirdiyini araşdıracağıq — həm də sxem faylı və ya kod generasiyası olmadan.

Problem: frontend ilə backend arasındakı sərhəd

Frontend və backend şəbəkə üzərindən ünsiyyət qurur, şəbəkədə isə sadəcə mətn — JSON — gedir. TypeScript bu JSON haqqında heç nə bilmir: kompilyasiya zamanı serveri görmür, server işləyəndə isə artıq heç bir tip qalmır. Ona görə adi REST tətbiqində cavabın tipini sadəcə özümüzə söz veririk:

ts
// REST: the response type is only a promise
type Note = { id: string; title: string };

const res = await fetch('/api/notes');
const notes = (await res.json()) as Note[]; // nobody checks this

console.log(notes[0].title); // undefined, if the server renamed the field

as Note[] yazısı yoxlama deyil, sözdür: «bilirəm ki, server məhz belə cavab göndərəcək». Server dəyişsə, TypeScript bunu görməyəcək, notes[0].title isə işləyərkən undefined olacaq.

Bu boşluğu bağlamaq üçün komandalar iki məşhur həll fikirləşib:

  • OpenAPI və kod generasiyası (REST). API openapi.yaml faylında təsvir olunur, xüsusi proqram — generator — isə həmin fayla görə müştəri üçün tiplər yaradır.
  • GraphQL sxemi və kod generasiyası. Sxem — GraphQL modulundan tanıdığınız həmin müqavilədir; müştəri tipləri yenə ondan generasiya olunur.

Hər iki yanaşmanın qiyməti var: serverin koduna əlavə olaraq daha bir artefakt (spesifikasiya və ya sxem) yazıb saxlamaq və generatoru işə salmağı unutmamaq lazımdır. Bəs müqavilə serverin koduna çevrilsə?

RPC ideyası: başqa kompüterdəki funksiyanı çağırmaq

RPC (Remote Procedure Call, «uzaq prosedurun çağırılması») köhnə ideyadır: başqa kompüterdə yaşayan funksiyanı elə çağırmaq ki, sanki o, kodunuzda, yanınızdadır.

Bənzətmə: çayxanada siz aşpaza məktub yazıb çayı necə dəmləmək lazım olduğunu izah etmirsiniz. Ofisiantə «bir çay, zəhmət olmasa» deyirsiniz — və bir stəkan çay alırsınız. Ofisiant RPC kitabxanasıdır: o, sizin «funksiya çağırışınızı» mətbəxə (serverə) çatdırır və nəticəni geri gətirir.

REST isə başqa cür qurulub: o, resurslar və HTTP metodları ətrafında dayanır (GET /notes/n2, POST /notes), yəni isimlər ətrafında. RPC isə əməllər, yəni fellər ətrafında qurulur: note.byId, note.create. tRPC — TypeScript RPC-dir: serverin procedure-lərini adi funksiya kimi çağırırsınız, TypeScript isə bu çağırışları yoxlayır.

Çağırış adi funksiyaya bənzəyir, şəbəkədə isə adi HTTP sorğusu gedir
Çağırış adi funksiyaya bənzəyir, şəbəkədə isə adi HTTP sorğusu gedir

Burada sehr yoxdur: arxa planda tanış HTTP və JSON işləyir. Müştəri trpc.note.byId.query({ id: 'n2' }) çağıranda şəbəkədə əslində belə sorğu gedir:

http
GET /api/trpc/note.byId?batch=1&input={"0":{"id":"n2"}} HTTP/1.1
Host: localhost:3000

Və serverin cavabı:

http
HTTP/1.1 200 OK
Content-Type: application/json