tRPC vs REST vs GraphQL — API Architecture Comparison
In this tutorial, you'll learn about trpc vs rest vs GraphQL. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
tRPC gives end-to-end type safety between server and client over HTTP, REST provides resource-oriented simplicity with universal caching, and GraphQL enables flexible client-driven data fetching — three API approaches for different needs.
At a Glance
| Feature | tRPC | REST | GraphQL |
|---|---|---|---|
| Type safety | Full (end-to-end) | Manual | Via codegen |
| Learning curve | Low (just functions) | Low | Medium |
| Caching | HTTP (limited) | HTTP (excellent) | Client (Apollo/URQL) |
| Bundle size | Zero (tree-shaken) | Zero | ~25KB+ (client lib) |
| Network requests | One per procedure | One per resource | One per query |
| Over-fetching | None (by design) | Common | None |
| Tooling ecosystem | Minimal | Massive | Large |
| IDEs/Playground | None | Swagger/Postman | GraphiQL/Playground |
| Browsable API | No | Yes (with docs) | Yes |
Key Differences
- Type safety: tRPC infers types from your backend functions directly into the client. No Code Generation. No duplication. REST requires manual type sharing. GraphQL needs codegen tools like GraphQL Code Generator.
- Caching: REST has excellent HTTP caching support via
Cache-Control, ETags, and CDNs. GraphQL typically uses client-side normalized caches (Apollo). tRPC relies on HTTP caching or manual React Query integration. - Network efficiency: GraphQL and tRPC send fewer requests because one query can fetch everything needed. REST requires multiple round trips for related resources.
- Ecosystem maturity: REST has the largest ecosystem — every tool, proxy, and framework supports it. GraphQL has strong tooling (Apollo, Relay, Hasura). tRPC is TypeScript-only and relatively new.
Side by Side: Fetching a Threat with Related Data
tRPC
// server/router.ts
import { z } from "zod";
import { publicProcedure, router } from "./trpc";
export const threatRouter = router({
getById: publicProcedure
.input(z.string())
.query(async (opts) => {
const threat = await db.threat.findUnique({
where: { id: opts.input },
include: { reports: true, analyst: true },
});
return threat;
}),
});
// client/component.tsx
import { trpc } from "./trpc";
function ThreatDetail({ id }: { id: string }) {
const { data } = trpc.threat.getById.useQuery(id);
return <div>{data?.name} - {data?.analyst.name}</div>;
}
REST
// client/fetch.ts
async function getThreat(id: string) {
const threat = await fetch(`/api/threats/${id}`).then(r => r.json());
const reports = await fetch(`/api/threats/${id}/reports`).then(r => r.json());
const analyst = await fetch(`/api/analysts/${threat.analystId}`).then(r => r.json());
return { ...threat, reports, analyst };
}
GraphQL
query GetThreat($id: ID!) {
threat(id: $id) {
name
severity
reports { title }
analyst { name }
}
}
const { data } = useQuery(GET_THREAT, { variables: { id } });
Expected output (all three):
Threat: "Emotet", Severity: "critical"
Reports: ["Analysis Report #1", "IOC Report"]
Analyst: "Dr. Smith"
Side by Side: Mutation
tRPC
// server/router.ts
export const threatRouter = router({
create: publicProcedure
.input(z.object({ name: z.string(), severity: z.enum(["low","high"]) }))
.mutation(async (opts) => {
return db.threat.create({ data: opts.input });
}),
});
// client
const mutation = trpc.threat.create.useMutation();
mutation.mutate({ name: "New Threat", severity: "high" });
REST
await fetch("/api/threats", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "New Threat", severity: "high" }),
});
GraphQL
mutation CreateThreat($name: String!, $severity: Severity!) {
createThreat(name: $name, severity: $severity) { id }
}
flowchart TD
A["Choose API Architecture"] --> B{"TypeScript\nfull-stack?"}
B -->|Yes| C{"Need public\nAPI?"}
B -->|No| F["REST or GraphQL"]
C -->|Yes| D{"Flexible data\nfetching needed?"}
C -->|No| E["tRPC\nE2E type safety\nZero boilerplate"]
D -->|Yes| G["GraphQL\nClient-driven queries\nSchema-first"]
D -->|No| F["REST\nUniversal caching\nTooling maturity"]
style E fill:#bbf7d0,stroke:#16a34a
style F fill:#dbeafe,stroke:#2563eb
style G fill:#fef3c7,stroke:#d97706
FAQ
{{< faq "When should I choose REST over tRPC or GraphQL?">}} Choose REST for public APIs consumed by diverse clients (mobile, web, third-party), when you need universal HTTP caching, or when your team spans multiple languages. {{< /faq >}}
Related Comparisons
REST vs GraphQL — GraphQL vs REST — REST vs gRPC — tRPC vs REST
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-24.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro