In 2026 there are five honest answers to “what API layer should I use?”: REST, GraphQL, tRPC, gRPC, and Connect. Each is the right call for a specific shape of system. This post is the decision guide.

The trade space

RESTGraphQLtRPCgRPCConnect
Schema-firstOptional (OpenAPI)YesInferred from TSYes (proto)Yes (proto)
End-to-end typesVia OpenAPI codegenVia codegenNativeVia codegenVia codegen
Browser nativeYesYesYesNo (needs proxy)Yes (HTTP/JSON)
StreamingSSE / WebSocketsSubscriptionsYes (subscriptions)First-classFirst-class
ProtocolHTTP/1.1, 2HTTP/1.1, 2HTTP/1.1, 2HTTP/2 binaryHTTP/1.1, 2, gRPC
Tooling maturityHighestHighHighHighMid (newer)
Best forPublic APIs, webhooksMulti-client orgsTS fullstackBackend-to-backendBoth

REST + OpenAPI

The lingua franca. Use REST when:

  • The API is public.
  • Webhooks send events to your service.
  • You want curl-able endpoints.
  • Multiple clients (mobile, web, third-party) need a stable contract.

OpenAPI 3.1 is the schema. Generate clients in any language. Use Pydantic v2 for typed request/response. Use FastAPI or Hono — both auto-generate OpenAPI from typed handlers.

What REST does poorly: fine-grained client-driven queries. A mobile client wanting just name and email from a user gets the whole User object. Solutions: sparse fieldsets (?fields=name,email), HAL/JSON:API, or… GraphQL.

GraphQL

GraphQL solves the “I want exactly these fields” problem. The client sends a query; the server returns matching shape:

query {
  user(id: 42) {
    id
    name
    posts(first: 5) {
      title
      publishedAt
    }
  }
}

When GraphQL wins:

  • Many clients with different needs. Mobile wants compact; web wants rich. One endpoint, two queries.
  • Many backend services. GraphQL Federation (Apollo Federation, Wundergraph) joins schemas across teams.
  • Deeply graph-shaped data. Social networks, marketplaces.

When GraphQL hurts:

  • Caching is hard. HTTP caching is GET-based; GraphQL is mostly POST.
  • N+1 problem. Without DataLoader, naive resolvers issue one DB query per nested field.
  • Operational complexity. Schema versioning, query budgeting, persisted queries — all real work.
  • Public surface area. Exposing arbitrary GraphQL queries lets clients construct expensive shapes. Mitigate with depth limits, query budgets, persisted queries.

In 2026 GraphQL is mature but not the default. Reach for it when its strengths solve a problem you have. Otherwise REST or tRPC.

tRPC

tRPC is the default for TypeScript fullstack in 2026. Server defines functions; client calls them with full type inference. No schema file, no codegen.

// server
export const appRouter = router({
  getUser: publicProcedure
    .input(z.object({ id: z.number() }))
    .query(async ({ input }) => db.users.findFirst({ where: eq(users.id, input.id) })),

  createUser: publicProcedure
    .input(z.object({ email: z.string().email(), name: z.string() }))
    .mutation(async ({ input }) => db.insert(users).values(input).returning()),
});

// client
import { trpc } from "./trpc";
const user = await trpc.getUser.query({ id: 42 });
//          ^^^^ fully typed end-to-end

What tRPC excels at:

  • Same codebase. Works best in a monorepo where server and client share types.
  • Refactor safety. Rename a server field; the TS compiler flags every client usage.
  • Zero codegen. No npm run gen. No OpenAPI parsing. Just imports.

When tRPC stops fitting:

  • Multiple language clients (mobile native, third-party). Use REST/OpenAPI or gRPC instead.
  • Public API. tRPC isn’t standard; non-TS clients have a harder time.

For TS-only fullstack apps, tRPC is the path of least resistance.

gRPC

gRPC is the right call for service-to-service communication with strict typed contracts:

  • Binary protocol over HTTP/2.
  • Code generation across every language.
  • First-class streaming (server, client, bidi).
  • Massive ecosystem — Kubernetes, Envoy, every cloud-native tool speaks it.

What gRPC does poorly:

  • Browsers. Plain gRPC isn’t browser-callable; you need a proxy (gRPC-Web) or to use Connect.
  • Curl debugging. Binary payloads aren’t human-readable.
  • One-off endpoints — overkill for “give me the time.”

For backend-to-backend in 2026, gRPC remains the right choice. See Go + gRPC + Protocol Buffers for production patterns.

Connect

Connect is the modern hybrid. The same .proto generates a server that speaks gRPC, gRPC-Web, and Connect’s own JSON-over-HTTP protocol. One server, three protocols.

service OrderService {
  rpc CreateOrder (CreateOrderRequest) returns (Order);
  rpc StreamOrders (StreamOrdersRequest) returns (stream Order);
}

A browser calls it as JSON over HTTP/1.1; a service calls it as gRPC. The handler is identical.

When to pick Connect:

  • New project, both backend and frontend under your control.
  • Want typed contracts, browser support, and gRPC ecosystem fit.
  • Fine with adopting buf for proto + codegen.

This is what I reach for in 2026 for greenfield projects that need a single API surface for browser + services. The story used to be “gRPC for backend, REST for browser” — Connect collapses that.

Decision matrix

ScenarioBest choice
Public API consumed by anonymous clientsREST + OpenAPI
WebhooksREST
TS monorepo, fullstack web apptRPC
Mobile + web with shared backendREST or GraphQL
Many backend microservicesgRPC or Connect
Backend services + browser, same projectConnect
Federated graph across many teamsGraphQL with Federation
Real-time streaming, server-to-clientSSE (with REST endpoint to start)
Bidirectional real-timeWebSockets or gRPC bidi
Internal admin tooltRPC if TS, REST otherwise

For streaming specifically, see SSE vs WebSockets in 2026 .

Common stack patterns

TypeScript fullstack (Hono / Next.js + your DB)

tRPC for the app’s own API; REST + OpenAPI for the public API. Consider Drizzle ORM for type-safe data.

Python backend, multiple clients

REST + OpenAPI with FastAPI auto-generation. See FastAPI + Pydantic v2 + SQLAlchemy 2.0 .

Microservices on Kubernetes

gRPC or Connect for service-to-service. REST at the edge. mTLS via Cilium and eBPF .

Mobile-first product

GraphQL if mobile teams ask for it. Otherwise REST.

Common mistakes

1. GraphQL because “it’s modern”

GraphQL’s complexity is real. If you don’t have multiple clients with different needs, you’re paying for flexibility you won’t use.

2. tRPC for non-TS clients

tRPC types are TypeScript types. They don’t exist in Swift. Don’t pick it if you have native mobile.

3. gRPC for the browser without Connect

Plain gRPC needs a gRPC-Web proxy in the path. Easier to use Connect from day one.

4. REST without OpenAPI

If the contract isn’t documented machine-readably, your clients will hand-roll types and they’ll drift. Generate OpenAPI; generate clients.

5. Multiple API layers in the same product

Pick one for the app’s own API. Add more only when the second has a clear, narrow purpose (e.g., REST for public consumers + gRPC for service-to-service).

What I’d ship today

For a new product in 2026:

  • Public API → REST + OpenAPI (FastAPI or Hono with @hono/zod-openapi).
  • Internal app API → tRPC if TS-only, else REST.
  • Service-to-service → Connect or gRPC.
  • Streaming → SSE with a sibling cancel endpoint (most cases).

Predictable, debuggable, replaceable.

Read this next

If you want a Hono + tRPC + Drizzle starter with auth and OTel tracing, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .