GraphQL Federation vs REST Microservices: Which Architecture in 2026?
Compare GraphQL Federation vs REST microservices — Apollo Federation, schema stitching, performance, team scaling, migration paths, and when to choose each in 2026.
Microservices solved the monolith problem but created a new one: API proliferation. When you have 20 services, each with its own REST API, frontend teams end up orchestrating dozens of endpoints for a single page load. GraphQL Federation emerged as an answer — a single unified GraphQL API backed by independent services that each own a slice of the schema.
But REST microservices are still the default for most teams, and for good reason. This guide compares both architectures head-to-head: technical trade-offs, team scaling, performance, and a clear decision framework for 2026.
Architecture Overview
REST Microservices
The traditional approach: each service exposes its own REST API, and clients (web, mobile, other services) call them directly or through an API gateway.
Mobile App
│
├── GET /api/users/123 → User Service
├── GET /api/orders?userId=123 → Order Service
├── GET /api/products/456 → Product Service
└── GET /api/inventory/789 → Inventory Service
Each service is independent: its own database, deployment pipeline, and API versioning. The client is responsible for assembling data from multiple calls.
GraphQL Federation
Apollo Federation lets multiple GraphQL services (“subgraphs”) each own part of a unified schema. A Router (formerly Apollo Gateway) stitches them together into a single API.
Mobile App
│
└── POST /graphql (one request)
│
Router
│
├── User Subgraph (owns User type)
├── Order Subgraph (owns Order type)
├── Product Subgraph (owns Product type, extends Order)
└── Inventory Subgraph (extends Product)
The client asks for exactly what it needs in one request. The Router figures out which subgraphs to query and assembles the response.
Quick Comparison Table
| REST Microservices | GraphQL Federation | |
|---|---|---|
| Client complexity | High (N endpoints) | Low (single endpoint) |
| Over-fetching | Common | Eliminated |
| Under-fetching | Requires N+1 calls | Solved in one query |
| Schema contract | Per-service API docs | Unified schema |
| Versioning | Per-service (v1, v2) | Schema evolution with @deprecated |
| Learning curve | Low | High |
| Tooling maturity | Extremely mature | Mature (Apollo ecosystem) |
| Caching | HTTP cache (CDN-native) | Requires custom layer |
| Error handling | HTTP status codes | Always 200, errors in body |
| Team independence | High | High (federated subgraphs) |
| Introspection | OpenAPI/Swagger | GraphQL introspection |
| Real-time | SSE, WebSockets separately | Subscriptions built-in |
REST Microservices Deep Dive
Strengths
1. HTTP cache is free
REST over HTTP gets CDN caching, ETags, and browser caching for free. A GET /products/456 response can be cached at the edge for hours.
GET /api/products/456 HTTP/1.1
Host: api.example.com
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
ETag: "abc123"
Content-Type: application/json
{ "id": 456, "name": "Widget", "price": 29.99 }
2. Universal tooling
Every developer knows REST. Every HTTP client, every testing tool, every proxy supports it. curl, Postman, HTTPie — no special knowledge required.
3. Standard error semantics
HTTP status codes are universally understood and handled by every client automatically:
404 Not Found
409 Conflict
422 Unprocessable Entity
429 Too Many Requests
4. Independent deployment with clean contracts
Each service can be versioned and deployed independently. A well-defined REST API is a stable contract between teams.
# Service A deploys v2 without affecting clients on v1
GET /api/v1/users/123 → old format, still works
GET /api/v2/users/123 → new format for updated clients
Weaknesses
The N+1 Problem
Getting a product page with inventory and reviews requires multiple roundtrips:
// Client code — 4 sequential or parallel requests
const product = await fetch('/api/products/456').then(r => r.json());
const inventory = await fetch(`/api/inventory/${product.id}`).then(r => r.json());
const reviews = await fetch(`/api/reviews?productId=${product.id}`).then(r => r.json());
const seller = await fetch(`/api/sellers/${product.sellerId}`).then(r => r.json());
// Assembled client-side
const page = { ...product, inventory, reviews, seller };
This is slow (sequential calls), fragile (partial failures), and leaks business logic to the client.
API Gateway Proliferation
With dozens of services, the API gateway becomes a bottleneck. Every new endpoint needs gateway configuration. Route maintenance is overhead.
GraphQL Federation Deep Dive
Core Concept: Schema Ownership
Each team owns their part of the unified schema. The Product team owns Product. The Order team owns Order and can extend Product without touching the Product service’s code.
# Product Subgraph owns this
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
description: String
}
# Order Subgraph extends Product (different codebase!)
extend type Product @key(fields: "id") {
id: ID! @external
orders: [Order!]! # Product now has orders, without Product team's involvement
}
type Order {
id: ID!
product: Product!
quantity: Int!
createdAt: String!
}
Apollo Router: The Query Planner
The Router (written in Rust for performance) decomposes a single client query into optimized sub-queries sent to the right subgraphs:
# Client sends ONE query
query GetProductPage($productId: ID!) {
product(id: $productId) {
name # → Product Subgraph
price # → Product Subgraph
inventory { # → Inventory Subgraph
quantity
warehouse
}
reviews { # → Review Subgraph
rating
comment
}
}
}
The Router’s query plan:
- Fetch
Productfrom Product Subgraph (name, price) - Parallel: fetch inventory + reviews using the product ID
- Assemble and return in one response
No N+1. No client orchestration.
Setting Up Federation
npm install @apollo/server @apollo/subgraph
npm install @apollo/gateway # or use Apollo Router (Rust binary)
Product Subgraph:
import { ApolloServer } from '@apollo/server';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';
const typeDefs = gql`
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
}
type Query {
product(id: ID!): Product
products: [Product!]!
}
`;
const resolvers = {
Product: {
__resolveReference({ id }: { id: string }) {
return productDatabase.find(p => p.id === id);
}
},
Query: {
product: (_, { id }) => productDatabase.find(p => p.id === id),
products: () => productDatabase,
}
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
Router Configuration:
# router.yaml
supergraph:
listen: 0.0.0.0:4000
subgraphs:
products:
routing_url: http://products-service:4001/graphql
orders:
routing_url: http://orders-service:4002/graphql
inventory:
routing_url: http://inventory-service:4003/graphql
Federation Strengths
Single request, exactly what you need:
query DashboardData {
currentUser {
name
email
recentOrders(limit: 5) {
id
total
product { name }
}
recommendations {
product { name, price }
reason
}
}
}
One request replaces what would be 4+ REST calls. Frontend developers can build features without waiting for backend teams to add new endpoints.
Schema evolution without versioning:
type User {
id: ID!
name: String!
email: String!
# Old field — deprecated but not removed
username: String @deprecated(reason: "Use email instead")
# New field added non-breaking
displayName: String
}
Federation Weaknesses
Caching requires a custom layer:
GraphQL always uses POST, so HTTP caching doesn’t apply. You need Apollo Client’s InMemoryCache, server-side DataLoader, or a custom CDN configuration.
// DataLoader prevents N+1 within a single request
const productLoader = new DataLoader(async (ids: string[]) => {
const products = await db.products.findMany({ where: { id: { in: ids } } });
return ids.map(id => products.find(p => p.id === id));
});
Debugging is harder:
Errors are always HTTP 200. You must parse the response body to find errors:
{
"data": {
"product": null
},
"errors": [
{
"message": "Product not found",
"locations": [{ "line": 3, "column": 5 }],
"path": ["product"]
}
]
}
Standard HTTP monitoring tools won’t catch 4xx/5xx — you need GraphQL-aware observability.
Performance Reality
Network Latency
| Scenario | REST | GraphQL Federation |
|---|---|---|
| Single resource fetch | 1 RTT | 1 RTT |
| Page with 4 related resources | 4 RTTs (sequential) or 1 RTT (parallel) | 1 RTT to Router + parallel subgraph fetches |
| Paginated list + metadata | 2+ RTTs | 1 RTT |
Federation wins on client network latency for complex data requirements. REST wins for simple resource fetches where HTTP caching applies.
Router Overhead
The Apollo Router adds ~1–3ms per request for query planning. For typical web apps (20–200ms total response time), this is negligible. For ultra-low-latency APIs (<10ms SLA), REST without a federation layer is more appropriate.
Team Scaling: The Real Differentiator
This is where Federation often wins. At scale, the frontend team is the bottleneck:
REST at scale:
- Frontend needs a new data shape → asks backend team
- Backend team creates new endpoint or modifies existing
- Deploy, document, update API gateway
- Frontend waits
Federation at scale:
- Frontend writes the query they need
- If all data exists in subgraphs, no backend change needed
- Frontend ships immediately
This is the GraphQL promise: frontend teams don’t wait for backend teams to add endpoints. At organizations with 5+ frontend developers and 2+ backend teams, this autonomy compounds significantly.
Migration Paths
From REST to GraphQL Federation
Don’t rewrite — wrap:
// GraphQL resolvers that call existing REST APIs
const resolvers = {
Query: {
product: async (_, { id }) => {
// Wrap existing REST service
const response = await fetch(`http://products-service/api/products/${id}`);
return response.json();
}
}
};
Migrate subgraph-by-subgraph:
- Add a thin GraphQL layer over existing REST services
- Move clients to GraphQL
- Gradually migrate resolvers from REST calls to direct DB access
Schema Stitching vs Federation
Schema stitching is the older approach to combining GraphQL schemas. Apollo Federation is better because:
- Subgraphs are independently deployable
@keydirectives make entity ownership explicit- The Router handles routing without a monolithic gateway config
Avoid schema stitching for new projects.
Decision Framework
How many services do you have?
├── 1-3 → REST API gateway or single GraphQL server
└── 4+ → Consider Federation
Who are your primary clients?
├── Mobile + Web (multiple clients, complex data needs) → GraphQL Federation
└── Internal services / simple API consumers → REST
Does your team know GraphQL?
├── No → REST (lower barrier, faster start)
└── Yes → Federation if complexity justifies it
Do you need aggressive HTTP caching?
└── Yes → REST (CDN caching, ETags, browser cache)
Are frontend teams blocked waiting for new endpoints?
└── Yes → Federation solves this directly
Real-World Adoption
Companies using GraphQL Federation in production:
- Netflix: unified API for their microservices
- Shopify: Storefront API (GraphQL)
- GitHub: GraphQL API alongside REST v3
- Airbnb: internal data graph
Companies still primarily REST:
- Stripe (beautiful REST API, intentional choice)
- Twilio (REST is right for their developer experience)
- Most B2B SaaS companies
The pattern: consumer-facing products with complex data requirements benefit from Federation. API products for developers often stick with REST for its simplicity and cacheability.
Tools for GraphQL Development
These DevPlaybook tools support GraphQL workflows:
- JSON Formatter — format GraphQL query results and introspection responses
- JWT Decoder — decode auth tokens used with GraphQL APIs
- Base64 Encoder/Decoder — decode cursor-based pagination tokens
Summary
| Choose REST Microservices when: | Choose GraphQL Federation when: |
|---|---|
| Simple, cacheable API | Complex, relational data needs |
| Developer-facing API product | Many frontend clients with varied data needs |
| Team unfamiliar with GraphQL | Frontend teams blocked by backend API delays |
| <5 services | 5+ services with cross-cutting data |
| Strict HTTP caching requirements | Flexibility and schema evolution matter more |
Both architectures work. Both scale. The choice depends on your team’s shape, your data access patterns, and whether N+1 queries and API endpoint proliferation are actual problems you’re experiencing.
2026 recommendation: For greenfield projects with complex frontends and 5+ services, Apollo Federation with the Rust-based Apollo Router is production-ready and delivers genuine developer experience improvements. For everything else, REST microservices remain the pragmatic default.
50 CLI Commands That Save Hours Every Week
The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.
- Git mastery (10 cmds)
- File & search wizardry
- Docker & DevOps tricks
- Process & network tools
- Productivity boosters
- macOS & Linux install guide
No spam. 1 email/week max. Unsubscribe anytime.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links — we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.