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
| Criterion | tRPC | GraphQL | REST |
|---|---|---|---|
| Type safety | 100% auto | With codegen | Manual |
| Stack | TS frontend + backend | Any | Any |
| Setup | Very fast | Medium | Fast |
| Over-fetching | None | No | Possible |
| Under-fetching | None | No | Possible (n+1) |
| CDN caching | Limited | Limited | Excellent |
| Real-time | Subscriptions OK | Native subscriptions | Webhooks/SSE |
| Versioning | Built-in | Schema versioning | URL versioning |
| Lock-in | High (TS only) | Medium | None |
| Tooling | Good | Excellent | Excellent |
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 });
}
`
Recommended 2026 hybrid pattern
`
[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.
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.
