Skip to content
C Codeloom
GraphQL

GraphQL Federation: A Practical Overview

Understand Apollo Federation: subgraphs, the gateway, entity references, and when to choose federation over a monolithic GraphQL schema.

·3 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • What federation solves
  • Subgraphs, the gateway, and the supergraph
  • Entity references with @key and __resolveReference
  • When federation is worth it
  • Common federation pitfalls

Prerequisites

  • Comfortable with GraphQL schemas

What and Why

A single GraphQL schema works great for one team. When many teams own different domains, the schema becomes a battleground. Federation splits it: each team runs its own subgraph, and a gateway composes them into one supergraph for clients.

Mental Model

A federated graph has three layers. Subgraphs are services that own a piece of the schema. The gateway plans and executes queries by routing field resolution to the right subgraph. Clients see one unified schema.

         Client query
            |
            v
       Gateway (router)
      /     |       \
Users    Products    Orders
subgraph   subgraph   subgraph
 |          |          |
Users DB  Catalog DB  Orders DB
Federation high level

Hands-on Example

Users subgraph declares the User entity.

extend schema @link(url: "https://specs.apollo.dev/federation/v2.5",
                    import: ["@key"])

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

type Query {
  me: User
}

Orders subgraph extends User with a field it owns.

extend schema @link(url: "https://specs.apollo.dev/federation/v2.5",
                    import: ["@key", "@external"])

type User @key(fields: "id", resolvable: false) {
  id: ID! @external
}

type Order {
  id: ID!
  total: Float!
  user: User!
}

type Query {
  myOrders: [Order!]!
}

extend type User {
  orders: [Order!]!
}

Resolvers in JavaScript subgraph.

import { buildSubgraphSchema } from "@apollo/subgraph";

const typeDefs = gql`...`;

const resolvers = {
  Query: { myOrders: (_, __, ctx) => ctx.orders.forUser(ctx.user.id) },
  User: {
    orders: (user, _, ctx) => ctx.orders.forUser(user.id),
    __resolveReference: (ref, ctx) => ({ id: ref.id }),
  },
};

const schema = buildSubgraphSchema({ typeDefs, resolvers });

The gateway composes subgraphs into a single endpoint. With Apollo Router, you publish each subgraph schema to a registry and the router pulls down a supergraph SDL.

# router.yaml
supergraph:
  listen: 0.0.0.0:4000
include_subgraph_errors:
  all: true

A client query that spans services.

query {
  me {
    name
    orders { id total }
  }
}

The router calls Users to resolve me, gets a User ref, then asks Orders to expand orders for that id.

Common Pitfalls

  • Adopting federation too early. For one team, a single monolithic schema is usually simpler and faster.
  • Sharing database tables across subgraphs. Each subgraph should own its data.
  • Forgetting that @key fields are stable identifiers. Choose ids you will never change.
  • Letting subgraph SDLs drift. Use schema checks in CI to catch breaking changes before merge.
  • Misusing @requires and @provides. They are powerful but easy to misuse; read the docs carefully.

Practical Tips

  • Define an owner per type. Other subgraphs should extend rather than redefine fields.
  • Add schema linting to enforce naming, nullability, and federation directives.
  • Track operation usage so you can deprecate fields with data, not guesses.
  • Run integration tests at the gateway level with representative queries that touch multiple subgraphs.
  • Treat the supergraph as a product. Version it carefully; clients depend on it.

Wrap-up

Federation is the right answer when multiple teams need autonomy over their part of a unified GraphQL API. It introduces real operational cost, so use it once your team boundaries demand it. Pick clear entity owners, automate schema checks, and design subgraphs around domains, not databases, and the supergraph becomes a powerful contract for all your clients.