Kas yra tRPC ir kodėl tai svarbu
Jei kada nors kūrėte full-stack aplikaciją su TypeScript, tikriausiai susidūrėte su klasikine problema: kaip išlaikyti tipo saugumą tarp frontend ir backend? Paprastai rašote API endpoint’us backend’e, tada frontend’e rankiniu būdu aprašote tipus arba generuojate juos iš OpenAPI specifikacijos. Kartais tiesiog naudojate `any` ir tikitės geriausio. O jei kas nors pakeičia API struktūrą? Na, sėkmės ieškant visų vietų, kur reikia atnaujinti klientinį kodą.
tRPC (TypeScript Remote Procedure Call) sprendžia šią problemą radikaliai paprastu būdu: jis tiesiog dalina tipus tarp kliento ir serverio. Jokių code generatorių, jokių schema failų, jokių sudėtingų konfigūracijų. Jūsų backend funkcijos tampa tiesiogiai prieinamos frontend’e su pilnu tipo saugumu. Skamba per gerai, kad būtų tiesa? Bet tai veikia, ir veikia puikiai.
Svarbiausia suprasti, kad tRPC nėra GraphQL alternatyva tradicine prasme. Tai greičiau filosofinis posūkis – vietoj to, kad kurtumėte atskirą API sluoksnį su savo schema kalba, jūs tiesiog rašote TypeScript funkcijas. Viskas.
Kodėl ne GraphQL?
GraphQL yra nuostabus įrankis, nėra ko ginčytis. Bet jis ateina su savo kaina. Pirma, jums reikia išmokti naują query kalbą. Antra, reikia aprašyti visą schema atskirai nuo jūsų TypeScript tipų. Trečia, jums reikia resolver’ių, tipo generatorių, cache strategijų… Sąrašas tęsiasi.
Daugeliui projektų GraphQL yra overkill. Jei kuriate aplikaciją, kur ir frontend, ir backend rašomi TypeScript’u, ir jie gyvena tame pačiame monorepo, tRPC tampa akivaizdžiu pasirinkimu. Jis suteikia jums daugumą GraphQL privalumų (tipo saugumas, aiški API struktūra) be sudėtingumo.
Žinoma, GraphQL vis dar laimi tam tikrose situacijose. Jei turite viešą API, kurį naudoja išoriniai klientai, arba jei jūsų frontend ir backend komandos dirba visiškai atskirai skirtingomis kalbomis, GraphQL gali būti geresnis pasirinkimas. Bet jei esate small-to-medium dydžio komanda su TypeScript stack’u? tRPC tikriausiai sutaupys jums savaitę laiko per mėnesį.
Kaip tai veikia praktikoje
Pažiūrėkime į paprastą pavyzdį. Štai kaip atrodytų tRPC router’is backend’e:
„`typescript
import { initTRPC } from ‘@trpc/server’;
import { z } from ‘zod’;
const t = initTRPC.create();
export const appRouter = t.router({
getUser: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
// Jūsų duomenų bazės logika čia
return { id: input.id, name: ‘Jonas’, email: ‘[email protected]’ };
}),
createPost: t.procedure
.input(z.object({
title: z.string(),
content: z.string(),
}))
.mutation(async ({ input }) => {
// Sukurti įrašą duomenų bazėje
return { id: ‘123’, …input };
}),
});
export type AppRouter = typeof appRouter;
„`
O dabar frontend’e:
„`typescript
import { createTRPCProxyClient, httpBatchLink } from ‘@trpc/client’;
import type { AppRouter } from ‘./server’;
const client = createTRPCProxyClient
links: [
httpBatchLink({
url: ‘http://localhost:3000/trpc’,
}),
],
});
// Ir štai magija – pilnas tipo saugumas!
const user = await client.getUser.query({ id: ‘123’ });
// TypeScript žino, kad user turi id, name ir email
const post = await client.createPost.mutate({
title: ‘Mano naujas įrašas’,
content: ‘Turinys čia…’,
});
„`
Pastebėjote? Niekur nerašėme jokių tipų frontend’e. Jie tiesiog… yra. Jei pakeistumėte `getUser` grąžinamą objektą backend’e, TypeScript iš karto pradėtų rodyti klaidas visose vietose, kur naudojate šį endpoint’ą. Tai yra tikrasis tipo saugumas.
Zod ir validacija – idealus duetas
Vienas dalykas, kurį tRPC daro išskirtinai gerai, yra integracija su Zod. Jei dar nenaudojate Zod, tai schema validacijos biblioteka, kuri leidžia aprašyti duomenų struktūras ir automatiškai gauti iš jų TypeScript tipus.
tRPC naudoja Zod schemas ne tik validacijai, bet ir tipo išvedimui. Tai reiškia, kad jūsų validacijos logika ir tipai visada sinchronizuoti – nes jie yra tas pats dalykas. Nereikia rašyti TypeScript interface’o ir tada atskirai validacijos logikos, kuri tikrina tuos pačius dalykus.
Štai sudėtingesnis pavyzdys:
„`typescript
const createUserInput = z.object({
email: z.string().email(),
password: z.string().min(8),
age: z.number().min(18).optional(),
preferences: z.object({
newsletter: z.boolean(),
notifications: z.enum([‘all’, ‘important’, ‘none’]),
}),
});
export const appRouter = t.router({
createUser: t.procedure
.input(createUserInput)
.mutation(async ({ input }) => {
// input jau validuotas ir turi teisingus tipus
// TypeScript žino visus laukus ir jų tipus
}),
});
„`
Jei frontend’e bandysite perduoti neteisingus duomenis, gausite ne tik TypeScript klaidą kompiliavimo metu, bet ir runtime validacijos klaidą, jei kas nors aplenkė tipo sistemą.
React Query integracija – smooth kaip sviestas
tRPC puikiai integruojasi su React Query (dabar vadinamu TanStack Query), ir tai iš tiesų keičia žaidimo taisykles. Jei naudojate React, tikriausiai jau žinote, kaip nuostabus yra React Query cache’inimui, background refetch’inimui ir optimistic updates.
Su `@trpc/react-query` jūs gaunate visą React Query galią su tRPC tipo saugumu:
„`typescript
import { createTRPCReact } from ‘@trpc/react-query’;
import type { AppRouter } from ‘./server’;
const trpc = createTRPCReact
function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = trpc.getUser.useQuery({ id: userId });
if (isLoading) return
;
if (error) return
;
// data yra pilnai tipizuotas!
return
;
}
„`
Mutations irgi veikia puikiai:
„`typescript
function CreatePostForm() {
const utils = trpc.useContext();
const createPost = trpc.createPost.useMutation({
onSuccess: () => {
// Invalidate ir refetch posts
utils.getPosts.invalidate();
},
});
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
createPost.mutate({
title: ‘Naujas įrašas’,
content: ‘Turinys…’,
});
};
return
;
}
„`
Viskas, ką mylite React Query, bet su tipo saugumu. Jokių string’ų query key’ams, jokių rankinių tipų aprašymų.
Middleware ir kontekstas – kur vyksta tikroji magija
tRPC middleware sistema leidžia pridėti cross-cutting concerns kaip autentifikacija, logging’as ar rate limiting. Ir, žinoma, viskas išlieka tipizuota.
Štai kaip galėtumėte implementuoti autentifikaciją:
„`typescript
const t = initTRPC.context<{ user?: User }>().create();
const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({ code: ‘UNAUTHORIZED’ });
}
return next({
ctx: {
user: ctx.user, // user dabar yra ne-optional
},
});
});
const protectedProcedure = t.procedure.use(isAuthed);
export const appRouter = t.router({
getMyProfile: protectedProcedure.query(({ ctx }) => {
// ctx.user garantuotai egzistuoja čia
return ctx.user;
}),
});
„`
Kontekstas leidžia perduoti informaciją iš HTTP request’o į jūsų procedure’as. Tai puiki vieta session informacijai, database connection’ams ar bet kam, kas reikalinga daugeliui endpoint’ų:
„`typescript
export const createContext = async ({ req, res }: CreateNextContextOptions) => {
const session = await getSession(req);
return {
session,
db: prisma,
};
};
const t = initTRPC.context
„`
Realūs use case’ai ir kada rinktis tRPC
Kalbant praktiškai, tRPC puikiai tinka:
**Next.js aplikacijoms** – tRPC ir Next.js yra kaip sukurti vienas kitam. Su App Router galite net naudoti tRPC server components’uose tiesiogiai, be jokių HTTP request’ų.
**Monorepo projektuose** – kai jūsų frontend ir backend gyvena kartu, tRPC tipo dalijimasis tampa trivialus. Turborepo ar Nx su tRPC yra developer experience svajonė.
**Startup’ams ir MVP** – kai reikia judėti greitai ir nenorite praleisti laiko kuriant sudėtingą API layer’į. tRPC leidžia fokusą nukreipti į business logiką.
**Internal tools** – admin panel’iai, dashboard’ai, internal aplikacijos – visur, kur nereikia viešo API ir galite kontroliuoti abi puses.
Kada NETURĖTUMĖTE naudoti tRPC:
– Kai reikia viešo API, kurį naudos ne-TypeScript klientai
– Kai frontend ir backend komandos yra visiškai atskiros ir nenori dalijintis kodu
– Kai jau turite didelę GraphQL infrastruktūrą ir ji veikia gerai
– Kai reikia labai specifinių GraphQL feature’ų kaip fragments ar complex caching strategies
Performance ir optimizacijos
Vienas dažnas klausimas: ar tRPC yra greitas? Trumpas atsakymas – taip, labai. Ilgesnis atsakymas – tai priklauso nuo to, kaip jį naudojate.
tRPC palaiko **request batching** – tai reiškia, kad keletas query’ų gali būti sujungti į vieną HTTP request’ą. Tai ypač naudinga, kai komponentas daro kelis skirtingus query’us mount’indamasis:
„`typescript
const client = createTRPCProxyClient
links: [
httpBatchLink({
url: ‘http://localhost:3000/trpc’,
maxBatchSize: 10, // Maksimalus batch dydis
}),
],
});
„`
**Streaming** irgi palaikomas, jei naudojate WebSockets ar Server-Sent Events. Tai puiku real-time feature’ams:
„`typescript
export const appRouter = t.router({
onPostCreated: t.procedure.subscription(() => {
return observable
// Subscribe to database changes
const unsubscribe = subscribeToNewPosts((post) => {
emit.next(post);
});
return unsubscribe;
});
}),
});
„`
Kalbant apie bundle size, tRPC yra gana lightweight. Core biblioteka yra apie 20kb minified, kas yra žymiai mažiau nei daugelis GraphQL klientų.
Migracija ir ekosistema
Jei jau turite esamą REST API ir svarstote migraciją į tRPC, geros naujienos – galite tai daryti palaipsniui. tRPC gali gyventi šalia jūsų esamo API. Tiesiog pradėkite nuo naujų feature’ų ir pamažu migravus senus endpoint’us.
Ekosistema aplink tRPC auga sparčiai. Yra oficialūs adapter’iai:
– Next.js (tiek Pages Router, tiek App Router)
– Express
– Fastify
– AWS Lambda
– Cloudflare Workers
Community sukūrė integracijų su:
– SvelteKit
– SolidStart
– Nuxt
– Astro
Dokumentacija yra puiki, o Discord community labai aktyvi ir paslaugi. Jei užstrigsite, tikriausiai rasite atsakymą per kelias minutes.
Ateities perspektyvos ir baigiamosios mintys
tRPC vis dar yra relative jaunas projektas (pirmoji versija išleista 2020-aisiais), bet jo augimas yra įspūdingas. Theo Browne (t3.gg) ir kiti influencer’iai aktyviai propaguoja tRPC kaip dalį „T3 Stack” – opinionated full-stack TypeScript setup’o.
Kas svarbu suprasti – tRPC nėra silver bullet. Tai įrankis, kuris puikiai veikia specifinėse situacijose. Jei jūsų projektas atitinka tRPC sweet spot (TypeScript full-stack, monorepo ar bent shared types, internal API), jūs tikriausiai sutaupysite daug laiko ir nervų.
Praktinis patarimas: jei svarstote tRPC, tiesiog išbandykite jį mažame projekte ar feature’e. Setup’as užtrunka gal 15 minučių, ir iš karto pajusite, ar tai jums tinka. Nereikia didelių commitmentų – jei nepatiks, galite lengvai grįžti prie REST ar išbandyti GraphQL.
Beje, jei naudojate Prisma duomenų bazei, tRPC + Prisma + Next.js kombinacija yra viena sklandžiausių developer experience, kokią teko patirti. Tipai teka per visą stack’ą nuo duomenų bazės iki UI, ir tai jaučiasi kaip ateitis.
Taigi, ar tRPC yra GraphQL killer? Ne, ir tai nėra jo tikslas. Bet ar tai yra puikus įrankis daugeliui projektų, kurie naudoja GraphQL tik dėl tipo saugumo? Absoliučiai. Kartais paprasčiausias sprendimas yra geriausias sprendimas.
