Summary
REST, GraphQL, and tRPC are three ways to move data between client and server. REST maps operations to HTTP resources and gets free HTTP caching. GraphQL lets clients shape queries but trades that flexibility for N+1 risk and cache complexity. tRPC skips the schema layer entirely — TypeScript types are the contract — but only works inside a monorepo.
Jump to the interview angleREST vs GraphQL vs tRPC at a glance
| Dimension | REST | GraphQL | tRPC | |
|---|---|---|---|---|
| Fetching | Fixed shape per endpoint; over/under-fetch is common | Client declares exact fields; one round-trip for nested data | Typed procedure call; shape defined by server return type | |
| Typing | Manual or OpenAPI-generated; drift is possible | Schema + codegen (e.g. GraphQL Code Generator) | Inferred from router — zero codegen, instant type errors | |
| Caching | `Cache-Control: s-maxage` on GET; CDN caches by URL | POST bypasses CDN; persisted queries restore GET caching | TanStack Query handles client cache; no HTTP-level caching | |
| Best fit | Public APIs, CDN-cached reads, non-TS consumers | Diverse clients needing different field sets | Full-stack TypeScript monorepos only |
tRPC router and typed client call
Define typed procedures on the server; the exported AppRouter type is the sole contract. No schema file, no codegen step.
// server/router.ts
import { initTRPC } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.create();
export const appRouter = t.router({
post: t.router({
byId: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.post.findUnique({ where: { id: input.id } });
}),
}),
});
export type AppRouter = typeof appRouter;
// client/PostPage.tsx
import { trpc } from "@/utils/trpc";
export function PostPage({ id }: { id: string }) {
// Type of data is inferred from the router — no codegen.
const { data } = trpc.post.byId.useQuery({ id });
return <h1>{data?.title}</h1>;
}Change the return shape server-side and the client has a type error immediately — no generated files to sync.
GraphQL caching gap
Queries sent as HTTP POST bypass CDN caches entirely. Use persisted queries to convert them to GET requests — the client sends a hash, the server expands it — restoring URL-based cache hits.
Interview angle
Expect a trade-off question: "when would you pick GraphQL over REST?" Hit over/under-fetching, N+1 with DataLoader, and caching complexity. For tRPC, name the monorepo constraint and zero-codegen benefit.
Soundbite: "GraphQL gives clients control over shape at the cost of HTTP caching; tRPC gives TypeScript control over types at the cost of portability."
Key terms
- Over-fetching
- Receiving more fields than the client needs from a REST endpoint.
- Under-fetching
- Needing multiple REST round-trips because one endpoint lacks required data.
- N+1 problem
- One query for a list plus one query per item — O(N) DB calls instead of two.
- DataLoader
- Batches and caches per-tick GraphQL resolver calls into a single DB query.
- Persisted query
- Stores a query by hash server-side so clients send a GET with just the hash.