Skip to content
Go back

Implementing GraphQL Federation with Apollo Server

Implementing GraphQL Federation with Apollo Server

Introduction

GraphQL Federation allows multiple GraphQL services to be composed into a single unified schema, enabling microservice architectures with a unified API gateway.

Prerequisites

Step 1: Install Dependencies

# For subgraph services
npm install @apollo/server @apollo/subgraph graphql

# For gateway
npm install @apollo/server @apollo/gateway graphql

Step 2: Create User Subgraph Service

Create services/user-service/src/index.ts:

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

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

  type User @key(fields: "id") {
    id: ID!
    username: String!
    email: String!
    profile: UserProfile
  }

  type UserProfile {
    firstName: String
    lastName: String
    avatar: String
  }

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

  type Mutation {
    updateProfile(input: UpdateProfileInput!): User
  }

  input UpdateProfileInput {
    firstName: String
    lastName: String
    avatar: String
  }
`;

// Mock data
const users = [
  {
    id: '1',
    username: 'john_doe',
    email: 'john@example.com',
    profile: { firstName: 'John', lastName: 'Doe', avatar: 'avatar1.jpg' }
  },
  {
    id: '2',
    username: 'jane_smith',
    email: 'jane@example.com',
    profile: { firstName: 'Jane', lastName: 'Smith', avatar: 'avatar2.jpg' }
  }
];

const resolvers = {
  Query: {
    me: () => users[0], // Mock current user
    user: (_: any, { id }: { id: string }) => users.find(user => user.id === id),
    users: () => users
  },

  Mutation: {
    updateProfile: (_: any, { input }: { input: any }) => {
      const user = users[0]; // Mock current user
      user.profile = { ...user.profile, ...input };
      return user;
    }
  },

  User: {
    // Federation resolver - allows other services to reference User by id
    __resolveReference: (reference: { id: string }) => {
      return users.find(user => user.id === reference.id);
    }
  }
};

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

  const { url } = await startStandaloneServer(server, {
    listen: { port: 4001 }
  });

  console.log(`User service ready at ${url}`);
}

startUserService();

Step 3: Create Posts Subgraph Service

Create services/posts-service/src/index.ts:

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

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

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

  type Post @key(fields: "id") {
    id: ID!
    title: String!
    content: String!
    authorId: String!
    author: User!
    createdAt: String!
    tags: [String!]!
  }

  type Query {
    post(id: ID!): Post
    posts: [Post!]!
    postsByUser(userId: ID!): [Post!]!
  }

  type Mutation {
    createPost(input: CreatePostInput!): Post!
    updatePost(id: ID!, input: UpdatePostInput!): Post
    deletePost(id: ID!): Boolean!
  }

  input CreatePostInput {
    title: String!
    content: String!
    tags: [String!] = []
  }

  input UpdatePostInput {
    title: String
    content: String
    tags: [String!]
  }
`;

// Mock data
const posts = [
  {
    id: '1',
    title: 'Getting Started with GraphQL Federation',
    content: 'GraphQL Federation allows you to compose multiple GraphQL services...',
    authorId: '1',
    createdAt: '2025-01-01T00:00:00Z',
    tags: ['graphql', 'federation']
  },
  {
    id: '2',
    title: 'Node.js Best Practices',
    content: 'Here are some best practices for Node.js development...',
    authorId: '2',
    createdAt: '2025-01-02T00:00:00Z',
    tags: ['nodejs', 'javascript']
  }
];

const resolvers = {
  Query: {
    post: (_: any, { id }: { id: string }) => posts.find(post => post.id === id),
    posts: () => posts,
    postsByUser: (_: any, { userId }: { userId: string }) => 
      posts.filter(post => post.authorId === userId)
  },

  Mutation: {
    createPost: (_: any, { input }: { input: any }) => {
      const post = {
        id: Date.now().toString(),
        ...input,
        authorId: '1', // Mock current user
        createdAt: new Date().toISOString()
      };
      posts.push(post);
      return post;
    },

    updatePost: (_: any, { id, input }: { id: string; input: any }) => {
      const postIndex = posts.findIndex(post => post.id === id);
      if (postIndex === -1) return null;
      
      posts[postIndex] = { ...posts[postIndex], ...input };
      return posts[postIndex];
    },

    deletePost: (_: any, { id }: { id: string }) => {
      const postIndex = posts.findIndex(post => post.id === id);
      if (postIndex === -1) return false;
      
      posts.splice(postIndex, 1);
      return true;
    }
  },

  Post: {
    author: (post: any) => ({ __typename: 'User', id: post.authorId }),
    __resolveReference: (reference: { id: string }) => {
      return posts.find(post => post.id === reference.id);
    }
  },

  User: {
    posts: (user: any) => posts.filter(post => post.authorId === user.id)
  }
};

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

  const { url } = await startStandaloneServer(server, {
    listen: { port: 4002 }
  });

  console.log(`Posts service ready at ${url}`);
}

startPostsService();

Step 4: Create Gateway Service

Create gateway/src/index.ts:

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway';

async function startGateway() {
  const gateway = new ApolloGateway({
    supergraphSdl: new IntrospectAndCompose({
      subgraphs: [
        { name: 'users', url: 'http://localhost:4001' },
        { name: 'posts', url: 'http://localhost:4002' }
      ]
    }),
    // Optional: Add custom directives or plugins
    buildService: ({ url }) => {
      return new (require('@apollo/datasource-rest').RESTDataSource)({
        baseURL: url,
      });
    }
  });

  const server = new ApolloServer({
    gateway,
    // Disable subscription support for now
    subscriptions: false,
    plugins: [
      // Add custom plugins for logging, caching, etc.
      {
        requestDidStart() {
          return {
            didResolveOperation(context) {
              console.log('Query:', context.request.query);
            }
          };
        }
      }
    ]
  });

  const { url } = await startStandaloneServer(server, {
    listen: { port: 4000 },
    context: async ({ req }) => {
      // Add authentication context
      const token = req.headers.authorization?.replace('Bearer ', '');
      return {
        user: token ? { id: '1' } : null // Mock user from token
      };
    }
  });

  console.log(`Gateway ready at ${url}`);
  console.log('Query the federated schema with:');
  console.log(`curl -X POST ${url} -H "Content-Type: application/json" -d '{"query":"{users{id username posts{title}}}"}'`);
}

startGateway();

Step 5: Schema Composition with Rover CLI

Install Apollo Rover:

npm install -g @apollo/rover

Create supergraph configuration:

federation_version: =2.3.0
subgraphs:
  users:
    routing_url: http://localhost:4001
    schema:
      subgraph_url: http://localhost:4001
  posts:
    routing_url: http://localhost:4002
    schema:
      subgraph_url: http://localhost:4002

Generate supergraph schema:

rover supergraph compose --config ./supergraph-config.yaml > supergraph-schema.graphql

Step 6: Advanced Federation Features

Entity relationships and computed fields:

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

  extend type Post @key(fields: "id") {
    id: ID! @external
    title: String! @external
    viewCount: Int! @requires(fields: "id")
    popularity: Float! @requires(fields: "id title")
  }

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

  type UserAnalytics {
    totalViews: Int!
    totalPosts: Int!
    averageEngagement: Float!
  }
`;

const resolvers = {
  Post: {
    viewCount: async (post: any) => {
      // Fetch view count from analytics service
      return Math.floor(Math.random() * 1000);
    },
    popularity: async (post: any) => {
      // Calculate popularity based on views and title keywords
      const baseScore = post.title.length * 0.1;
      const viewBonus = Math.random() * 10;
      return Number((baseScore + viewBonus).toFixed(2));
    }
  },

  User: {
    analytics: async (user: any) => {
      return {
        totalViews: Math.floor(Math.random() * 10000),
        totalPosts: Math.floor(Math.random() * 50),
        averageEngagement: Number((Math.random() * 100).toFixed(2))
      };
    }
  }
};

Step 7: Error Handling and Monitoring

export const errorHandlingPlugin = {
  requestDidStart() {
    return {
      didEncounterErrors(context: any) {
        context.errors.forEach((error: any) => {
          console.error('GraphQL Error:', {
            message: error.message,
            path: error.path,
            service: error.extensions?.serviceName,
            timestamp: new Date().toISOString()
          });
        });
      }
    };
  }
};

Step 8: Example Federated Queries

# Query spanning multiple services
query GetUserWithPosts {
  user(id: "1") {
    id
    username
    email
    profile {
      firstName
      lastName
    }
    posts {
      id
      title
      content
      createdAt
      tags
    }
  }
}

# Query with computed fields
query GetPostAnalytics {
  posts {
    id
    title
    viewCount
    popularity
    author {
      username
      analytics {
        totalViews
        averageEngagement
      }
    }
  }
}

Summary

GraphQL Federation with Apollo Server enables microservice composition through a unified schema. Each service owns its domain while the gateway provides a single entry point, supporting entity relationships and computed fields across service boundaries.


Share this post on:

Previous Post
Optimizing Docker Build Performance and Image Size
Next Post
Scaling WebSocket Connections with Redis and Clustering