Skip to content
All essays
CraftMarch 27, 202414 min

Introduction to GraphQL Federation

Scale your GraphQL architecture with federated microservices.

Ü
Ümit Uz
Mobile & Full Stack Developer

GraphQL Federation enables you to compose multiple GraphQL services into a unified graph, perfect for microservices architectures.

What Is GraphQL Federation?

Federation splits a monolithic GraphQL schema into smaller, deployable services that can be developed independently.

text
Traditional Monolithic GraphQL:
┌─────────────────────────┐
│   GraphQL Gateway       │
│  (All resolvers here)   │
└───────────┬─────────────┘
            │
    ┌───────┴───────┐
    │               │
┌───┴───┐       ┌───┴───┐
│ Users │       │ Posts │
│Service│       │Service│
└───────┘       └───────┘

GraphQL Federation:
┌─────────────────────────┐
│   Apollo Gateway        │
│   (Composition only)    │
└───────────┬─────────────┘
            │
    ┌───────┴───────┐
    │               │
┌───┴───────┐  ┌───┴───────┐
│  Users    │  │  Posts   │
│Subgraph   │  │Subgraph  │
└───────────┘  └───────────┘

Core Concepts

Subgraphs

Each microservice is a subgraph with its own schema.

graphql
# Users subgraph
type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
}

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

# Posts subgraph
type Post @key(fields: "id") {
  id: ID!
  title: String!
  content: String!
  author: User @provides(fields: "name")
}

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

type Query {
  post(id: ID!): Post
}

Entities

Entities are types that can be extended across subgraphs.

graphql
# Define entity with @key
type User @key(fields: "id") {
  id: ID!
  name: String!
}

# Extend entity in another subgraph
extend type User @key(fields: "id") {
  id: ID! @external
  posts: [Post]
}

Setting Up Federation

Users Subgraph

javascript
// users-service/index.js
const { ApolloServer } = require('@apollo/server');
const { buildSubgraphSchema } = require('@apollo/subgraph');

const typeDefs = `#graphql
  type User @key(fields: "id") {
    id: ID!
    name: String!
    email: String!
  }

  type Query {
    user(id: ID!): User
    users: [User]
  }
`;

const resolvers = {
  Query: {
    user: (_, { id }) => users.find(u => u.id === id),
    users: () => users,
  },
  User: {
    __resolveReference: (user) => users.find(u => u.id === user.id),
  },
};

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

server.listen({ port: 4001 }).then(({ url }) => {
  console.log(`Users subgraph ready at ${url}`);
});

Posts Subgraph

javascript
// posts-service/index.js
const { ApolloServer } = require('@apollo/server');
const { buildSubgraphSchema } = require('@apollo/subgraph');

const typeDefs = `#graphql
  type Post @key(fields: "id") {
    id: ID!
    title: String!
    content: String!
    author: User
  }

  extend type User @key(fields: "id") {
    id: ID! @external
    posts: [Post]
  }

  type Query {
    post(id: ID!): Post
    posts: [Post]
  }
`;

const resolvers = {
  Query: {
    post: (_, { id }) => posts.find(p => p.id === id),
    posts: () => posts,
  },
  Post: {
    author: (post) => ({ __typename: 'User', id: post.authorId }),
  },
  User: {
    posts: (user) => posts.filter(p => p.authorId === user.id),
  },
};

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

server.listen({ port: 4002 }).then(({ url }) => {
  console.log(`Posts subgraph ready at ${url}`);
});

Apollo Gateway

javascript
// gateway/index.js
const { ApolloGateway } = require('@apollo/gateway');
const { ApolloServer } = require('@apollo/server');

const gateway = new ApolloGateway({
  supergraphSdl: new IntrospectAndCompose({
    subgraphs: [
      { name: 'users', url: 'http://localhost:4001' },
      { name: 'posts', url: 'http://localhost:4002' },
    ],
  }),
});

const server = new ApolloServer({
  gateway,
});

server.listen({ port: 4000 }).then(({ url }) => {
  console.log(`Gateway ready at ${url}`);
});

Federation Directives

@key

Defines the primary key for an entity.

graphql
type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Float!
}

# Composite key
type Product @key(fields: "id category") {
  id: ID!
  category: String!
  name: String!
}

@external

Marks a field as defined in another subgraph.

graphql
extend type Product @key(fields: "id") {
  id: ID! @external
  reviews: [Review]
}

@provides

Specifies fields that a subgraph can provide.

graphql
type Review {
  id: ID!
  product: Product @provides(fields: "price")
}

extend type Product @key(fields: "id") {
  id: ID! @external
  price: Float @external
}

@requires

Specifies fields required from another subgraph.

graphql
extend type User @key(fields: "id") {
  id: ID! @external
  email: String! @external
  fullName: String! @requires(fields: "email")
}

Entity Resolution

Reference Resolution

javascript
const resolvers = {
  User: {
    __resolveReference: (reference, context) => {
      // reference = { id: "123" }
      return fetchUserById(reference.id);
    },
  },
};

With DataLoader

javascript
const DataLoader = require('dataloader');

const userBatchLoader = new DataLoader(async (userIds) => {
  const users = await fetchUsersByIds(userIds);
  return userIds.map(id => users.find(u => u.id === id));
});

const resolvers = {
  User: {
    __resolveReference: (reference) => {
      return userBatchLoader.load(reference.id);
    },
  },
};

Relationships

One-to-One

graphql
# Users subgraph
type User @key(fields: "id") {
  id: ID!
  profile: Profile
}

type Profile @key(fields: "userId") {
  userId: ID!
  bio: String!
}

# Profiles subgraph
extend type User @key(fields: "id") {
  id: ID! @external
  profile: Profile
}

type Profile @key(fields: "userId") {
  userId: ID!
  user: User! @provides(fields: "id")
}

One-to-Many

graphql
# Users subgraph
extend type User @key(fields: "id") {
  id: ID! @external
  posts: [Post]
}

# Posts subgraph
type Post @key(fields: "id") {
  id: ID!
  author: User!
}

extend type User @key(fields: "id") {
  id: ID! @external
}

Many-to-Many

graphql
# Posts subgraph
extend type Post @key(fields: "id") {
  id: ID! @external
  tags: [Tag]
}

# Tags subgraph
type Tag @key(fields: "id") {
  id: ID!
  name: String!
  posts: [Post]
}

extend type Post @key(fields: "id") {
  id: ID! @external
}

Best Practices

DOs

  • Design subgraphs around business domains
  • Use entities for shared types
  • Implement proper error handling
  • Use DataLoader for batching
  • Monitor subgraph performance
  • Version your subgraphs
  • Test federated queries

DON'Ts

  • Create circular dependencies
  • Over-fetch across subgraphs
  • Ignore N+1 queries
  • Skip caching
  • Forget about security
  • Mix concerns
  • Create tight coupling

Error Handling

Partial Errors

javascript
const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      try {
        return await dataSources.usersAPI.getUser(id);
      } catch (error) {
        // Return partial data
        return {
          id,
          name: null,
          error: 'Failed to fetch user',
        };
      }
    },
  },
};

Conclusion

GraphQL Federation enables scalable microservices architecture with a unified GraphQL API. Design subgraphs around domains, use entities for shared types, and optimize queries. Your teams can work independently while providing a seamless API!

Enjoyed this essay? Share it.

Related essays

Next essay
Advanced Git Techniques for Power Users