Skip to content
Codeloom
GraphQL

Apollo Federation for GraphQL Microservices

Learn how Apollo Federation composes multiple GraphQL subgraphs into a single unified API gateway for microservice architectures.

·6 min read · By Codeloom
Intermediate 12 min read

What you'll learn

  • How Apollo Federation splits a GraphQL schema across microservices
  • How to define entities and reference resolvers for cross-service relationships
  • How the Apollo Router composes subgraphs into a unified supergraph

Prerequisites

  • Basic GraphQL knowledge
  • Understanding of microservice architecture

The Problem with Monolithic Schemas

As your GraphQL API grows, a single monolithic schema becomes a bottleneck. Multiple teams editing the same schema file causes merge conflicts, deployment coupling, and unclear ownership. If the Users team and the Orders team both need to modify the same server, every deployment carries risk for both teams.

Apollo Federation solves this by letting each team own their own GraphQL service (called a subgraph) while presenting a single, unified API to clients through a gateway (the Apollo Router).

How Federation Works

Federation has three core concepts:

  1. Subgraphs are independent GraphQL services, each owning a portion of the overall schema.
  2. The supergraph schema is the composed result of all subgraph schemas merged together.
  3. The Apollo Router sits in front of all subgraphs, receives client queries, breaks them into sub-queries, sends each piece to the appropriate subgraph, and assembles the final response.

Setting Up a Subgraph

Install the federation package:

npm install @apollo/server @apollo/subgraph graphql

A Users subgraph defines the User entity:

import { ApolloServer } from '@apollo/server';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { startStandaloneServer } from '@apollo/server/standalone';
import gql from 'graphql-tag';

const typeDefs = gql`
  extend schema @link(url: "https://specs.apollo.dev/federation/v2.0",
    import: ["@key"])

  type Query {
    me: User
  }

  type User @key(fields: "id") {
    id: ID!
    name: String!
    email: String!
  }
`;

const resolvers = {
  Query: {
    me: (_, __, context) => {
      return db.users.findById(context.userId);
    },
  },
  User: {
    __resolveReference: (reference) => {
      // Called when another subgraph references a User by ID
      return db.users.findById(reference.id);
    },
  },
};

const server = new ApolloServer({
  schema: buildSubgraphSchema({ typeDefs, resolvers }),
});

const { url } = await startStandaloneServer(server, { listen: { port: 4001 } });
console.log(`Users subgraph running at ${url}`);

The @key Directive

The @key directive marks a type as an entity that can be referenced across subgraphs. The fields argument specifies which fields uniquely identify this entity:

type User @key(fields: "id") {
  id: ID!
  name: String!
}

You can define composite keys when a single field is not sufficient:

type Product @key(fields: "upc sku") {
  upc: String!
  sku: String!
  name: String!
}

Extending Entities Across Subgraphs

The power of federation is that one subgraph can extend an entity owned by another. An Orders subgraph can add an orders field to the User type:

const typeDefs = gql`
  extend schema @link(url: "https://specs.apollo.dev/federation/v2.0",
    import: ["@key", "@external"])

  type Query {
    order(id: ID!): Order
  }

  type Order @key(fields: "id") {
    id: ID!
    product: String!
    quantity: Int!
    user: User!
  }

  type User @key(fields: "id") {
    id: ID!
    orders: [Order!]!
  }
`;

const resolvers = {
  Query: {
    order: (_, { id }) => db.orders.findById(id),
  },
  User: {
    orders: (user) => {
      // The router provides the user.id from the Users subgraph
      return db.orders.findByUserId(user.id);
    },
  },
  Order: {
    __resolveReference: (ref) => db.orders.findById(ref.id),
  },
};

When a client queries { me { name orders { product } } }, the router fetches name from the Users subgraph and orders from the Orders subgraph, then combines the results.

Reference Resolvers

The __resolveReference resolver is how federation fetches entity data. When the Orders subgraph returns an Order with a user field containing just { id: "123" }, the router calls the Users subgraph’s __resolveReference with that reference to get the full User object.

User: {
  __resolveReference: async (reference) => {
    // reference = { id: "123" }
    return db.users.findById(reference.id);
  },
},

Setting Up the Apollo Router

The Apollo Router is a high-performance Rust binary that replaces the older Node.js gateway. Download it and create a supergraph configuration:

# supergraph-config.yaml
federation_version: =2.0.0
subgraphs:
  users:
    routing_url: http://localhost:4001
    schema:
      subgraph_url: http://localhost:4001
  orders:
    routing_url: http://localhost:4002
    schema:
      subgraph_url: http://localhost:4002

Compose the supergraph schema and start the router:

# Install the Rover CLI
npm install -g @apollo/rover

# Compose the supergraph schema
rover supergraph compose --config supergraph-config.yaml > supergraph.graphql

# Start the router
./router --supergraph supergraph.graphql

The router now accepts queries on port 4000 and routes them to the appropriate subgraphs.

Query Planning

When the router receives a query, it creates a query plan that determines which subgraphs to call and in what order. For independent fields, it parallelizes requests. For dependent fields (like fetching a user, then their orders), it sequences them.

You can inspect query plans for debugging:

query GetUserOrders {
  me {
    name        # Fetched from Users subgraph
    email       # Fetched from Users subgraph
    orders {    # Fetched from Orders subgraph (requires User.id first)
      product
      quantity
    }
  }
}

The router sends one request to the Users subgraph for name, email, and id, then uses the id to request orders from the Orders subgraph.

Shared Types with @shareable

When multiple subgraphs need to resolve the same field on a type, use the @shareable directive:

# In both subgraphs
type Product @key(fields: "id") {
  id: ID!
  name: String! @shareable
}

Without @shareable, composition fails because the router does not know which subgraph should resolve name.

Migrating to Federation Incrementally

You do not need to break your monolith into microservices all at once. Start by wrapping your existing monolith as a single subgraph:

  1. Add @apollo/subgraph and mark your types with @key.
  2. Set up the router pointing to your monolith as the only subgraph.
  3. Extract one service at a time. Move the Orders type to a new subgraph while extending User from the original.
  4. Repeat until each domain has its own subgraph.

This incremental approach lets you validate federation works with your existing schema before committing to a full microservice split.

Common Pitfalls

Circular dependencies: Subgraph A extends a type from Subgraph B, and B extends a type from A. This works in federation but makes query plans complex. Keep dependencies unidirectional when possible.

Over-fetching in reference resolvers: If __resolveReference always fetches the full entity from the database, you may load data the client did not request. Use the info parameter or field-level resolvers for expensive fields.

Missing entities: If a subgraph references an entity that does not exist in the owning subgraph, the router returns null. Always handle this case gracefully.

Summary

Apollo Federation lets teams independently develop and deploy GraphQL subgraphs while presenting a unified API to clients. Each subgraph owns its domain types, entities can be extended across subgraphs using @key and reference resolvers, and the Apollo Router handles query planning and composition. Start by federating your existing monolith as a single subgraph, then incrementally extract services as your team structure demands.