Websites10 min read

tRPC vs GraphQL vs REST: API design 2026

Mohamed Bah·Fondateur, Kolonell
May 26, 2026
Share:
tRPC vs GraphQL vs REST: API design 2026

tRPC vs GraphQL vs REST: API design 2026

Websites

3 paradigms dominate API design in 2026: tRPC (full-TypeScript), GraphQL (flexible query language), REST (universal). Each has its sweet spot. Choice impacts dev velocity, maintenance, performance.

TL;DR

- tRPC: full-TypeScript, end-to-end type safety, fastest dev.

- GraphQL: max flexibility, multiple resources, mobile-friendly.

- REST: universal, standard HTTP cache, mature.

Detailed comparison

CriteriontRPCGraphQLREST
Type safety100% autoWith codegenManual
StackTS frontend + backendAnyAny
SetupVery fastMediumFast
Over-fetchingNoneNoPossible
Under-fetchingNoneNoPossible (n+1)
CDN cachingLimitedLimitedExcellent
Real-timeSubscriptions OKNative subscriptionsWebhooks/SSE
VersioningBuilt-inSchema versioningURL versioning
Lock-inHigh (TS only)MediumNone
ToolingGoodExcellentExcellent

When to pick tRPC

Ideal cases:

  • End-to-end TypeScript Next.js app
  • Small team (1-10 devs)
  • No separate mobile app
  • No major third-party integrations

Pros:

  • 100% auto type safety (schema change → UI compile error)
  • No codegen
  • 30-min setup
  • Trivial refactoring

`ts

import { z } from 'zod';

import { router, publicProcedure } from '../trpc';

export const postsRouter = router({

list: publicProcedure

.input(z.object({ limit: z.number().default(10) }))

.query(async ({ input, ctx }) => {

return ctx.prisma.post.findMany({ take: input.limit });

}),

create: publicProcedure

.input(z.object({ title: z.string(), content: z.string() }))

.mutation(async ({ input, ctx }) => {

return ctx.prisma.post.create({ data: input });

}),

});

`

`tsx

import { trpc } from '@/utils/trpc';

export function PostList() {

const { data: posts, isLoading } = trpc.posts.list.useQuery({ limit: 20 });

if (isLoading) return ;

return posts.map(p => );

}

export function NewPostForm() {

const create = trpc.posts.create.useMutation();

return (

{

e.preventDefault();

const formData = new FormData(e.currentTarget);

await create.mutateAsync({

title: formData.get('title') as string,

content: formData.get('content') as string,

});

}}>

{/* form fields */}

);

}

`

All type-safe. Backend refactor = instant frontend compile errors.

When to pick GraphQL

Ideal cases:

  • Multi-client (web + mobile + tablet)
  • Varied per-client queries (mobile wants less data)
  • Public backend consumed by third-parties
  • Complex data graphs (e-commerce with products + reviews + variants)

Pros:

  • Client decides what to fetch
  • Single endpoint for all
  • Excellent tooling (GraphiQL, Apollo Studio, Codegen)
  • Schema-first design

`graphql

type Post {

id: ID!

title: String!

content: String!

author: User!

comments: [Comment!]!

}

type Query {

posts(limit: Int = 10): [Post!]!

post(id: ID!): Post

}

type Mutation {

createPost(title: String!, content: String!): Post!

}

`

`ts

const resolvers = {

Query: {

posts: async (_, { limit }, ctx) => {

return ctx.prisma.post.findMany({ take: limit });

},

},

Need a professional website?

Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.

Mutation: {

createPost: async (_, { title, content }, ctx) => {

return ctx.prisma.post.create({ data: { title, content } });

},

},

};

`

`tsx

const { data } = useGetPostsQuery({ variables: { limit: 20 } });

`

When to pick REST

Ideal cases:

  • Public API consumed by many third-parties
  • Aggressive CDN caching (Cloudflare, Fastly)
  • Mobile native (iOS Swift, Android Kotlin) where GraphQL setup is heavy
  • Standardized compliance / audit trails
  • Polyglot microservices

Pros:

  • Standard HTTP cache (Cache-Control, ETag)
  • Universal tooling (Postman, curl)
  • No framework lock-in
  • Simple to understand for third-parties
  • Mature DDoS protection (Cloudflare)

`ts

export async function GET(request: Request) {

const { searchParams } = new URL(request.url);

const limit = parseInt(searchParams.get('limit') ?? '10');

const posts = await prisma.post.findMany({ take: limit });

return Response.json(posts, {

headers: {

'Cache-Control': 'public, max-age=60, stale-while-revalidate=300',

},

});

}

export async function POST(request: Request) {

const data = await request.json();

const post = await prisma.post.create({ data });

return Response.json(post, { status: 201 });

}

`

`

[Internal Next.js Frontend] ←→ [tRPC]

[Native mobile app] ←→ [REST API]

[Partner integrations] ←→ [REST API]

[Public OAuth API] ←→ [REST API + optional GraphQL]

`

A modern 2026 app often exposes 2 surfaces:

  • tRPC for Next.js frontend (velocity)
  • REST for external integrations (compatibility)

Real case — Dakar SaaS

Chosen architecture:

  • Next.js + tRPC frontend (internal team, fast refactoring)
  • Stripe/Wave webhooks received via REST API routes
  • Public REST API for 8 integrated partners
  • No GraphQL (no native mobile)

Result: -50% API dev time vs full-REST + 100% type safety.

Performance compared

  • Test : list 50 posts with author + 5 comments each

REST (n+1 problem) :

  • 1 GET /posts
  • 50 GET /users/{id}
  • 50 GET /posts/{id}/comments
  • Total : 101 requests, 800ms

REST (with includes/embed) :

  • 1 GET /posts?include=author,comments
  • Total : 1 request, 250ms

GraphQL :

  • 1 POST /graphql with query
  • Total : 1 request, 200ms
  • tRPC (Prisma includes):
  • 1 POST /trpc/posts.list
  • Total : 1 request, 220ms

GraphQL and tRPC natively avoid n+1. REST requires careful design (includes, embed).

Common pitfalls

tRPC

  • No TypeScript backend — tRPC requires strict TS end-to-end.
  • Public API consumed externally — tRPC not ideal, lock-in.
  • CDN cache — limited, not standard HTTP cache.

GraphQL

  • N+1 in resolvers — use DataLoader (Facebook).
  • Over-engineered for simple app — GraphQL = complexity overhead.
  • Difficult CDN cache — single endpoint, request-based.

REST

  • Over-fetching — bad design if /users returns 50 fields when 3 needed.
  • N+1 problem — always offer ?include=relations.
  • Versioning — clean /v1, /v2 from start.

FAQ

Q: Do Server Actions replace tRPC?

A: For Next.js alone, often yes. tRPC remains relevant if separate backend (Express, Hono).

Q: GraphQL Federation 2026?

A: Apollo Federation for microservices stays relevant. But high complexity.

Q: Migration?

A: tRPC → REST/GraphQL: rewrite. REST → GraphQL: possible incremental.

Conclusion

2026 API choice:

  • Next.js mono-app + TS: tRPC (or Server Actions)
  • Multi-client + complex data: GraphQL
  • Public API + integrations: REST

Dominant hybrid pattern: tRPC internal + REST external. No single winner.

Tags:#tRPC#GraphQL#REST#API Design#Architecture#Web Dev
Share:

Mohamed Bah

Fondateur, Kolonell

Passionate about digital and entrepreneurship in Africa, Mohamed has been helping Sénégalese businesses with their digital transformation since 2020. Founder of Kolonell, he believes every SME deserves a professional and accessible online présence.