Data Fetching Patterns

Medium•

Choosing between REST, GraphQL, and real-time data fetching strategies for optimal application performance and maintainable API contracts.

Quick Navigation: REST • GraphQL • RPC (tRPC / gRPC-Web) • Real-time • Optimistic Updates • Comparison

REST

Resource-based endpoints

  • - Simple cache semantics
  • - Can over/under-fetch

GraphQL

Client-shaped queries

  • - Flexible payload shape
  • - Schema + resolver complexity

tRPC

Type-safe app boundary

  • - Strong TS ergonomics
  • - Tighter FE/BE coupling

gRPC-Web

Protobuf over browser proxy

  • - Contract-first transport
  • - Extra proxy/runtime constraints

Real-time

Push over pull

  • - Freshness for live UX
  • - Connection/state complexity

Core Lens

Match transport model to data volatility, consistency guarantees, and client complexity tolerance.

Flow

Fetch→
Cache→
Revalidate→
Render

REST API

Representational State Transfer - resource-based architecture using HTTP methods (GET, POST, PUT, DELETE).

Advantages

  • ✓Simple & Familiar: Easy to understand, well-documented
  • ✓HTTP Caching: Built-in caching with Cache-Control headers
  • ✓Stateless: Each request is independent, easy to scale
  • ✓Tooling: Excellent browser DevTools, Postman, etc.

Disadvantages

  • ✗Over-fetching: Returns all fields even if not needed
  • ✗Under-fetching: Multiple requests for related data
  • ✗Versioning: Need to manage API versions
  • ✗No type safety: Requires separate type definitions

🎯 Best For:

CRUD applications, public APIs, simple data requirements, teams familiar with REST, when HTTP caching is important.

GraphQL

Query language for APIs - clients request exactly the data they need through a single endpoint.

Advantages

  • ✓No over/under-fetching: Request exactly what you need
  • ✓Single request: Fetch related data in one query
  • ✓Strongly typed: Schema provides type safety
  • ✓Self-documenting: Introspection for auto-docs
  • ✓Subscriptions: Built-in real-time support

Disadvantages

  • ✗Complexity: Steeper learning curve
  • ✗Caching challenges: No HTTP caching by default
  • ✗N+1 queries: Can cause performance issues if not careful
  • ✗File uploads: Not natively supported

🎯 Best For:

Complex data requirements, mobile apps (bandwidth sensitive), multiple clients with different needs, rapid iteration on frontend.

RPC Boundaries: tRPC vs gRPC-Web

tRPC

Type-safe procedure calls for full-stack TypeScript apps.

  • ✓ Strong end-to-end TypeScript ergonomics
  • ✓ No schema/codegen step for core DX
  • ✗ Tight FE/BE coupling to TS ecosystem
  • ✗ Less ideal for public multi-language APIs

Best for: Product teams shipping one app boundary.

gRPC-Web

Browser-compatible gRPC via proxy/transcoding layer.

  • ✓ Efficient protobuf contracts and strong schema discipline
  • ✓ Good fit when backend already uses gRPC
  • ✗ Browser limits require gRPC-Web proxy
  • ✗ Added infra complexity vs REST/tRPC

Best for: Polyglot backends with existing gRPC platform.

Real-time Data Fetching

Polling

Repeatedly fetch data at fixed intervals.

Pros

  • ✓ Simple to implement
  • ✓ Works with any API
  • ✓ No special infrastructure

Cons

  • ✗ Wastes resources (unnecessary requests)
  • ✗ Delayed updates (depends on interval)
  • ✗ Battery drain on mobile

🎯 Best For: Low-frequency updates, fallback for WebSocket failures

WebSockets

Persistent bidirectional connection between client and server.

Pros

  • ✓ True real-time (instant updates)
  • ✓ Bidirectional communication
  • ✓ Lower latency than polling
  • ✓ Efficient for high-frequency updates

Cons

  • ✗ Complex connection management
  • ✗ Reconnection logic needed
  • ✗ Stateful (harder to scale)
  • ✗ Firewall/proxy issues

🎯 Best For: Chat apps, gaming, collaborative editing, live trading

Server-Sent Events (SSE)

Server pushes data to client over HTTP connection.

Pros

  • ✓ Simpler than WebSockets
  • ✓ Auto-reconnection built-in
  • ✓ Works over HTTP (firewall friendly)
  • ✓ Less resource intensive

Cons

  • ✗ Unidirectional (server → client only)
  • ✗ Limited browser connections (6 per domain)
  • ✗ No binary data support

🎯 Best For: Notifications, live feeds, stock tickers, progress updates

Optimistic vs Pessimistic Updates

Optimistic Updates

Update UI immediately, assume success, rollback on failure.

✓Instant feedback (feels fast)
✓Better perceived performance
✗Complex rollback logic
✗Can show incorrect state briefly

🎯 Best For: Likes, comments, todos, cart updates

Pessimistic Updates

Wait for server confirmation before updating UI.

✓Always shows accurate state
✓Simpler error handling
✗Feels slower
✗Loading states for every action

🎯 Best For: Payments, critical data, complex validation

Comparison Table

ApproachLatencyComplexityBest Use Case
RESTMediumLowCRUD, Public APIs
GraphQLLow (single request)MediumComplex data needs
PollingHigh (interval dependent)LowLow-frequency updates
tRPCLow to MediumMediumFull-stack TypeScript apps
gRPC-WebLowHighPolyglot microservices + typed contracts
WebSocketsVery LowHighReal-time collaboration
SSELowMediumLive feeds, notifications

Decision Guide

  • 1.Simple CRUD with caching? → REST
  • 2.Complex queries, multiple clients? → GraphQL
  • 3.Bidirectional real-time? → WebSockets
  • 4.Server-push notifications? → SSE
  • 5.End-to-end TypeScript, single app boundary? → tRPC
  • 6.Backend already on gRPC + strict contracts? → gRPC-Web