Skip to main content
← All Articles · · 12 min read

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.

graphqlgraphql-federationrestmicroservicesapiarchitectureapollo

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 MicroservicesGraphQL Federation
Client complexityHigh (N endpoints)Low (single endpoint)
Over-fetchingCommonEliminated
Under-fetchingRequires N+1 callsSolved in one query
Schema contractPer-service API docsUnified schema
VersioningPer-service (v1, v2)Schema evolution with @deprecated
Learning curveLowHigh
Tooling maturityExtremely matureMature (Apollo ecosystem)
CachingHTTP cache (CDN-native)Requires custom layer
Error handlingHTTP status codesAlways 200, errors in body
Team independenceHighHigh (federated subgraphs)
IntrospectionOpenAPI/SwaggerGraphQL introspection
Real-timeSSE, WebSockets separatelySubscriptions 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:

  1. Fetch Product from Product Subgraph (name, price)
  2. Parallel: fetch inventory + reviews using the product ID
  3. 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

ScenarioRESTGraphQL Federation
Single resource fetch1 RTT1 RTT
Page with 4 related resources4 RTTs (sequential) or 1 RTT (parallel)1 RTT to Router + parallel subgraph fetches
Paginated list + metadata2+ RTTs1 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:

  1. Add a thin GraphQL layer over existing REST services
  2. Move clients to GraphQL
  3. 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
  • @key directives 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:


Summary

Choose REST Microservices when:Choose GraphQL Federation when:
Simple, cacheable APIComplex, relational data needs
Developer-facing API productMany frontend clients with varied data needs
Team unfamiliar with GraphQLFrontend teams blocked by backend API delays
<5 services5+ services with cross-cutting data
Strict HTTP caching requirementsFlexibility 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.

Free Download

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.