Skip to content

GraphQL Federation — Distributed GraphQL for Microservices

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about GraphQL Federation. We cover key concepts, practical examples, and best practices to help you master this topic.

GraphQL Federation is an architecture pattern for composing a single GraphQL schema from multiple Microservices, each owning a portion of the schema.

What You'll Learn

You will learn how federation splits schemas across services, implement subgraphs with Apollo Federation, use the Gateway to compose them, and understand entity references and resolvers.

Why Federation Matters

Monolithic GraphQL servers don't scale as teams grow — one team's schema changes can break another team's queries. Federation lets each microservice own its part of the schema. DodaTech's Durga Antivirus Pro uses three federated services: Devices (device management), Threats (threat intelligence), and Users (authentication and profiles). Each team deploys independently, and the Gateway composes everything into one seamless API.

flowchart TB
    Client --> Gateway["Apollo Gateway"]
    Gateway --> Devices["Devices Service\n/devices/graphql"]
    Gateway --> Threats["Threats Service\n/threats/graphql"]
    Gateway --> Users["Users Service\n/users/graphql"]
    Gateway --> Scans["Scans Service\n/scans/graphql"]
    Devices --> DB1[(Devices DB)]
    Threats --> DB2[(Threat Intelligence)]
    Users --> DB3[(User Profiles)]
    Scans --> DB4[(Scan Results)]
    style Gateway fill:#dbeafe,stroke:#2563eb
    style Devices fill:#fef3c7,stroke:#d97706
    style Threats fill:#fef3c7,stroke:#d97706
    style Users fill:#fef3c7,stroke:#d97706
    style Scans fill:#fef3c7,stroke:#d97706
â„šī¸ Info

Prerequisites: GraphQL architecture. Microservices concepts. Apollo Server experience.

Defining a Subgraph (Devices Service)

# devices.graphql — Devices service schema
extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])

type Device @key(fields: "id") {
  id: ID!
  name: String!
  os: String!
  version: String
  userId: ID!
}

type Query {
  devices: [Device!]!
  device(id: ID!): Device
}
const { ApolloServer, gql } = require('apollo-server');
const { buildSubgraphSchema } = require('@apollo/subgraph');

const typeDefs = gql`
  extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])
  
  type Device @key(fields: "id") {
    id: ID!
    name: String!
    os: String!
    version: String
    userId: ID!
  }
  
  type Query {
    devices: [Device!]!
    device(id: ID!): Device
  }
`;

const resolvers = {
  Query: {
    devices: () => db.devices.findAll(),
    device: (_, { id }) => db.devices.findById(id),
  },
  // Reference resolver — resolves Device by ID for other services
  Device: {
    __resolveReference(ref) {
      return db.devices.findById(ref.id);
    },
  },
};

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

Extending a Type (Users Service)

# users.graphql — Users service extends Device with user data
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@requires"])

type Device @key(fields: "id") {
  id: ID! @external
  userId: ID! @external
  user: User! @requires(fields: "userId")
}

type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
  role: String!
  devices: [Device!]!
}

extend type Query {
  users: [User!]!
  user(id: ID!): User
}
const resolvers = {
  Device: {
    __resolveReference(ref) {
      // Get the device's userId and fetch the user
      return { id: ref.id, userId: ref.userId };
    },
    user(device) {
      return db.users.findById(device.userId);
    },
  },
  User: {
    __resolveReference(ref) {
      return db.users.findById(ref.id);
    },
    devices(user) {
      return db.devices.findByUserId(user.id);
    },
  },
};

Setting Up the Gateway

const { ApolloGateway } = require('@apollo/gateway');
const { ApolloServer } = require('apollo-server');

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'devices', url: 'http://devices-service:4001/graphql' },
    { name: 'threats', url: 'http://threats-service:4002/graphql' },
    { name: 'users', url: 'http://users-service:4003/graphql' },
    { name: 'scans', url: 'http://scans-service:4004/graphql' },
  ],
});

const server = new ApolloServer({
  gateway,
  subscriptions: false, // Federation doesn't support subscriptions yet
  context: ({ req }) => ({
    user: authenticate(req),
  }),
});

server.listen(4000).then(({ url }) => {
  console.log(`Gateway ready at ${url}`);
});
# Client query — seamlessly spans all services
query DashboardData {
  user(id: "user-001") {
    name
    email
    devices {        # From Users service
      id
      name
      os             # From Devices service
      threats {      # From Threats service
        id
        name
        severity
      }
      scans {        # From Scans service
        id
        status
        completedAt
      }
    }
  }
}

Entity Reference Resolver Pattern

// Each subgraph must implement __resolveReference for entities it owns
const resolvers = {
  Device: {
    __resolveReference(ref, context) {
      // ref contains { __typename: "Device", id: "dev-001" }
      return context.db.devices.findById(ref.id);
    },
  },
  User: {
    __resolveReference(ref) {
      return context.db.users.findById(ref.id);
    },
  },
};

Federation Directives

# @key — declares an entity's primary key
type Device @key(fields: "id") { ... }

# @external — marks a field defined in another subgraph
type Device @key(fields: "id") {
  id: ID! @external
  userId: ID! @external
}

# @requires — declares that this field depends on external fields
type Device @key(fields: "id") {
  id: ID! @external
  userId: ID! @external
  user: User! @requires(fields: "userId")
}

# @shareable — field can be resolved by multiple subgraphs
type Threat @key(fields: "id") {
  id: ID!
  name: String! @shareable
  severity: Severity! @shareable
}

# @provides — subgraph can resolve this field for other subgraphs
type Device @key(fields: "id") {
  id: ID!
  threatSummary: ThreatSummary @provides(fields: "count")
}

Common Mistakes

1. Circular Entity References

If Service A extends Service B's type and Service B extends Service A's type, you get circular resolution. Break the cycle by making one side use @external.

2. Not Implementing __resolveReference

Without __resolveReference, the gateway cannot resolve entities across service boundaries. Every entity with @key needs this resolver.

3. Duplicating Field Definitions

Defining the same field in multiple subgraphs without @shareable causes schema composition errors. Use @shareable for fields that multiple services can resolve.

4. Ignoring Gateway Performance

Each query may trigger multiple network calls between the gateway and subgraphs. Add DataLoader in the gateway or use @requires to minimize calls.

5. Federation Without Proper Error Handling

A failing subgraph can take down the entire gateway. Implement circuit breakers, timeouts, and fallback responses in the gateway.

Practice Questions

  1. What problem does GraphQL Federation solve?
  2. What is the role of the Apollo Gateway?
  3. What does the @key directive do?
  4. How does __resolveReference work?
  5. What is the difference between @external and @shareable?

Answers:

  1. Federation enables multiple microservices to own parts of a single GraphQL schema. Each team deploys independently while the Gateway composes a unified API.
  2. The Gateway fetches the schema from each subgraph, composes them into one schema, and routes incoming queries to the appropriate subgraphs.
  3. @key(fields: "id") declares an entity type and its primary key. Other subgraphs can reference this entity using the key fields.
  4. __resolveReference(ref) resolves an entity by its key fields when another subgraph references it. It receives { __typename, id } and returns the full entity.
  5. @external marks a field that is defined in another subgraph. @shareable marks a field that multiple subgraphs can resolve.

Challenge: Design a federated GraphQL architecture for DodaTech's full platform. Create subgraphs for Users (auth, profiles), Devices (management, inventory), Threats (detection, intelligence), Scans (scheduling, results), Alerts (notifications, routing), and Billing (plans, invoices). Implement the Gateway with proper error handling and Caching.

FAQ

Is Federation production-ready?

Yes — Apollo Federation v2 is production-ready and used by Netflix, Airbnb, and Expedia. It supports entity references, computed fields, and gradual adoption.

Can I adopt Federation gradually?

Yes — start with a monolithic schema, then extract subgraphs one at a time. The Gateway can serve both monolithic and federated services during migration.

Does Federation support subscriptions?

Not natively — Apollo Gateway does not support subscriptions across subgraphs. Each subgraph can expose its own WebSocket endpoint for subscriptions.

How does Federation compare to schema stitching?

Federation is more opinionated and optimized for Apollo ecosystem. Schema stitching is more flexible but requires manual merge configuration.

What happens when a subgraph is down?

The Gateway caches subgraph schemas but cannot resolve queries requiring the downed subgraph. Implement circuit breakers and degrade gracefully.

Mini Project

Build a federated GraphQL API for DodaTech. Create three subgraphs: Devices (device management, @key on Device), Threats (threat detection, extends Device), and Users (user profiles, extends Device). Set up the Gateway with proper composition, implement __resolveReference on all entities, and build a client query that fetches user devices with their threats.

What's Next

Topic Description
Apollo Server Production server configuration
Code Generation TypeScript types from schema
Testing Testing GraphQL APIs
âŦ… Authorization
➡ Apollo Server

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro