API Design Patterns: REST vs GraphQL vs tRPC
API architecture has evolved significantly. REST, GraphQL, and tRPC each offer different trade-offs. Here's how to choose the right approach for your application.
Jason Overmier
Innovative Prospects Team
The API landscape has evolved significantly from the early REST days. Today, teams choose between REST, GraphQL, and tRPC based on their specific needs. Each approach has distinct trade-offs that no single solution is optimal for all use cases.
Quick Comparison
| Factor | REST | GraphQL | tRPC |
|---|---|---|---|
| Learning curve | Low | Medium | Medium |
| Flexibility | High | Medium | Low |
| Type safety | None | Schema | Strong |
| Over-fetching | Common | Rare | None |
| N+1 problem | Common | Rare | None |
| Caching | Standard | Complex | Standard |
| Best for | Public APIs, simple CRUD | Complex queries, frontend-backend coupling | Internal microservices, type-safe APIs |
REST APIs
REST (Representational State Transfer) remains the most common API pattern. It uses HTTP methods to represent operations on resources.
Core Principles
- Resource-based URLs:
/users/123 - HTTP methods: GET (read), POST (create), PUT (update), DELETE (remove)
- Statelessness: Each request contains all needed information
- Standard response codes: 200 (success), 400 (bad request), 500 (server error)
When REST Works Well
| Scenario | Why REST |
|---|---|
| Public APIs | Wide compatibility, easy caching |
| Simple CRUD | Natural mapping to operations |
| Third-party integration | Universal support |
| Mobile apps | Low bandwidth, simple caching |
| Microservices | Standard interface between services |
REST Challenges
| Challenge | Impact |
|---|---|
| Over-fetching | Fetching entire resources when only some fields needed |
| N+1 problem | Loading related resources requires multiple requests |
| Versioning | API changes break clients |
| Documentation | Keeping docs in sync with implementation |
GraphQL
GraphQL provides a query language that lets clients request exactly what they need.
Core Principles
- Single endpoint: All queries go to
/graphql - Client-specified queries: Client defines response shape
- Strongly typed schema: Schema defines available types and fields
- Introspection: Schema is queryable
When GraphQL Works Well
| Scenario | Why GraphQL |
|---|---|
| Complex queries | Fetch nested data in single request |
| Multiple clients | Each client requests only needed data |
| Mobile apps | Minimize data transfer |
| API aggregation | Combine multiple services |
| Rapid iteration | Schema evolution without breaking clients |
GraphQL Challenges
| Challenge | Impact |
|---|---|
| Complexity | Deeply nested queries can be expensive |
| Caching | HTTP caching doesn’t work well |
| Learning curve | New paradigm for teams familiar with REST |
| Over-fetching prevention | Can still query too much if not careful |
| Security | Field-level authorization is complex |
tRPC
tRPC (TypeScript Remote Procedure Call) provides end-to-end type safety from server to client.
Core Principles
- TypeScript-first: Types are inferred from TypeScript definitions
- Single source of truth: Server types automatically generate client types
- RPC model: Call server functions like local functions
- Minimal boilerplate: No schema definition needed
When tRPC Works Well
| Scenario | Why tRPC |
|---|---|
| TypeScript monorepo | End-to-end type safety |
| Internal APIs | No need for public API compatibility |
| Microservices | Type-safe service communication |
| Rapid development | Minimal boilerplate |
| Full-stack TypeScript | Shared types across stack |
tRPC Challenges
| Challenge | Impact |
|---|---|
| TypeScript lock-in | Only works well with TypeScript clients |
| Public APIs | Not ideal for external consumers |
| Language coupling | Server and client must share type definitions |
| Flexibility | Less flexible than REST for unknown use cases |
| Learning curve | Different paradigm from typical HTTP APIs |
Decision Framework
Choose REST When
- Building public APIs for external consumption
- Team is more familiar with REST patterns
- Need simple caching at the HTTP layer
- Integrating with systems that expect REST
- API simplicity is more important than efficiency
Choose GraphQL When
- Multiple clients with different data needs
- Complex queries with nested data relationships
- Need to minimize over-the-wire data transfer
- Schema can be exposed to clients
- Team is comfortable with GraphQL paradigm
Choose tRPC When
- Full-stack TypeScript application
- Internal microservices communication
- Need end-to-end type safety
- Rapid development is a priority
- Can share types between server and client
Hybrid Approaches
Many applications benefit from multiple API styles:
| API Type | Use Case |
|---|---|
| REST | Public API, third-party webhooks |
| GraphQL | Complex frontend queries |
| tRPC | Internal service-to-service communication |
| WebSockets | Real-time subscriptions |
Example architecture:
External Clients → REST API → API Gateway
↓
Internal Frontend → GraphQL → API Gateway
↓
Microservices ← tRPC → Internal Services
Common Pitfalls
| Pitfall | Approach | Impact | Prevention |
|---|---|---|---|
| Using GraphQL for simple CRUD | GraphQL | Unnecessary complexity | Use REST for simple operations |
| REST for complex nested queries | REST | N+1 problem | Use GraphQL or design better REST endpoints |
| tRPC for public API | tRPC | Poor external developer experience | Use REST for external APIs |
| Over-nesting GraphQL queries | GraphQL | Performance issues | Limit query depth, use dataloader |
| No API versioning | Any | Breaking changes | Version APIs from the start |
The best API approach depends on your specific needs. If you’re building a new API and need guidance on architecture, book a consultation. We’ll help you choose the right approach for your situation.