TechLead
Lesson 17 of 20
5 min read
GraphQL

Federation and Supergraphs

Build distributed GraphQL architectures with Apollo Federation, composing multiple services into a unified supergraph

What Is GraphQL Federation?

GraphQL Federation is an architecture pattern for building a distributed GraphQL API from multiple smaller GraphQL services (called subgraphs). A gateway (called a router) composes these subgraphs into a single unified schema (the supergraph) that clients query as if it were a single API.

Federation solves the problem of scaling GraphQL APIs across teams. Instead of one monolithic schema maintained by every team, each team owns their own subgraph and can develop, deploy, and scale it independently.

Federation Architecture

  • Subgraph: An individual GraphQL service that owns a portion of the schema (e.g., Users service, Posts service)
  • Supergraph: The composed schema from all subgraphs — what clients see
  • Router: The gateway that receives client queries and distributes them across subgraphs
  • Schema Registry: A central store for subgraph schemas used for composition and validation

Federation Directives

# Users Subgraph — owns User type
type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
  bio: String
  avatarUrl: String
}

type Query {
  user(id: ID!): User
  me: User
}

# Posts Subgraph — owns Post type, extends User
type Post @key(fields: "id") {
  id: ID!
  title: String!
  body: String!
  author: User!
  publishedAt: String
}

# Extend the User type defined in Users subgraph
type User @key(fields: "id") {
  id: ID!
  posts: [Post!]!          # Added by Posts subgraph
  postsCount: Int!          # Added by Posts subgraph
}

type Query {
  post(id: ID!): Post
  posts(limit: Int, offset: Int): [Post!]!
}

# Comments Subgraph — owns Comment type, extends Post and User
type Comment @key(fields: "id") {
  id: ID!
  text: String!
  author: User!
  post: Post!
  createdAt: String!
}

type User @key(fields: "id") {
  id: ID!
  recentComments: [Comment!]!  # Added by Comments subgraph
}

type Post @key(fields: "id") {
  id: ID!
  comments: [Comment!]!       # Added by Comments subgraph
  commentsCount: Int!          # Added by Comments subgraph
}

Implementing a Subgraph

// users-subgraph/src/index.ts
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", "@shareable"])

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

  type Query {
    user(id: ID!): User
    users(limit: Int = 20, offset: Int = 0): [User!]!
    me: User
  }

  type Mutation {
    createUser(input: CreateUserInput!): User!
    updateUser(id: ID!, input: UpdateUserInput!): User!
  }

  input CreateUserInput {
    name: String!
    email: String!
  }

  input UpdateUserInput {
    name: String
    bio: String
    avatarUrl: String
  }
`;

const resolvers = {
  Query: {
    user: async (_: unknown, { id }: { id: string }, ctx: Context) => {
      return ctx.prisma.user.findUnique({ where: { id } });
    },
    users: async (_: unknown, args: { limit: number; offset: number }, ctx: Context) => {
      return ctx.prisma.user.findMany({ take: args.limit, skip: args.offset });
    },
    me: async (_: unknown, __: unknown, ctx: Context) => ctx.currentUser,
  },

  User: {
    // __resolveReference is called by the router when another subgraph
    // references a User entity
    __resolveReference: async (ref: { id: string }, ctx: Context) => {
      return ctx.prisma.user.findUnique({ where: { id: ref.id } });
    },
  },
};

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

const { url } = await startStandaloneServer(server, {
  listen: { port: 4001 },
  context: async ({ req }) => createContext(req),
});

console.log(`Users subgraph running at ${url}`);

Posts Subgraph with Entity References

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

  type Post @key(fields: "id") {
    id: ID!
    title: String!
    body: String!
    author: User!
    publishedAt: String
    createdAt: String!
  }

  # Extend User type from Users subgraph
  type User @key(fields: "id") {
    id: ID!
    posts: [Post!]!
    postsCount: Int!
  }

  type Query {
    post(id: ID!): Post
    posts(limit: Int = 20, offset: Int = 0): [Post!]!
  }
`;

const resolvers = {
  Post: {
    __resolveReference: async (ref: { id: string }, ctx: Context) => {
      return ctx.prisma.post.findUnique({ where: { id: ref.id } });
    },
    author: (post: Post) => {
      // Return a reference — the router will resolve this via Users subgraph
      return { __typename: 'User', id: post.authorId };
    },
  },

  User: {
    // These fields are added to User by this subgraph
    posts: async (user: { id: string }, _: unknown, ctx: Context) => {
      return ctx.prisma.post.findMany({
        where: { authorId: user.id },
        orderBy: { createdAt: 'desc' },
      });
    },
    postsCount: async (user: { id: string }, _: unknown, ctx: Context) => {
      return ctx.prisma.post.count({ where: { authorId: user.id } });
    },
  },

  Query: {
    post: (_, { id }, ctx) => ctx.prisma.post.findUnique({ where: { id } }),
    posts: (_, args, ctx) => ctx.prisma.post.findMany({
      take: args.limit, skip: args.offset, orderBy: { createdAt: 'desc' },
    }),
  },
};

Router Configuration

# router.yaml — Apollo Router configuration
supergraph:
  listen: 0.0.0.0:4000

subgraphs:
  users:
    routing_url: http://users-service:4001/graphql
  posts:
    routing_url: http://posts-service:4002/graphql
  comments:
    routing_url: http://comments-service:4003/graphql

cors:
  origins:
    - https://myapp.com
  allow_credentials: true

headers:
  all:
    request:
      - propagate:
          named: authorization

Federation Best Practices

  • Own your types: Each subgraph should own the types it is responsible for and extend others
  • Use @key for entities: Every type that is referenced across subgraphs needs a @key directive
  • Schema registry: Use Apollo Studio or a similar registry to manage composition and check for breaking changes
  • Independent deployment: Each subgraph should be independently deployable and testable
  • Start monolith, migrate to federation: Do not start with federation — migrate when team boundaries require it

Continue Learning