Skip to content
All essays
CraftMarch 29, 202411 min

Apollo Federation for Microservices

Build federated GraphQL APIs across microservices

Ü
Ümit Uz
Mobile & Full Stack Developer

Apollo Federation enables you to compose multiple GraphQL services into a single unified graph. It's the standard for building GraphQL APIs with microservices architecture.

What is Federation?

Federation allows you to:

  • Split GraphQL schema across services
  • Extend types from other services
  • Reference entities across services
  • Maintain service boundaries

Monolith vs Federation

Monolith GraphQL:

┌─────────────────────┐
│     GraphQL API     │
│  ┌───────────────┐  │
│  │  All Types    │  │
│  └───────────────┘  │
└─────────────────────┘

Federated GraphQL:

┌────────────┐   ┌────────────┐   ┌────────────┐
│  Users     │   │  Products  │   │  Orders    │
│  Service   │   │  Service   │   │  Service   │
└─────┬──────┘   └─────┬──────┘   └─────┬──────┘
      │                │                │
      └────────┬───────┴────────┬───────┘
               │                │
        ┌──────▼────────────────▼──────┐
        │      Apollo Gateway          │
        └─────────────────────────────┘

Setting Up Federated Services

User Service

javascript
import { ApolloServer } from '@apollo/server';
import { buildSubgraphSchema } from '@apollo/subgraph';

const typeDefs = `#graphql
  extend type Query {
    me: User
    user(id: ID!): User
  }

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

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

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

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

Product Service

javascript
const typeDefs = `#graphql
  extend type Query {
    products: [Product]
    product(id: ID!): Product
  }

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

const resolvers = {
  Query: {
    products: () => products,
    product: (_, { id }) => products.find(p => p.id === id)
  },
  Product: {
    __resolveReference: (product) => {
      return products.find(p => p.id === product.id);
    }
  }
};

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

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

Order Service (Extending User)

javascript
const typeDefs = `#graphql
  extend type Query {
    orders: [Order]
    order(id: ID!): Order
  }

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

  type Order @key(fields: "id") {
    id: ID!
    total: Float!
    items: [OrderItem]
  }

  type OrderItem {
    product: Product
    quantity: Int
  }
`;

const resolvers = {
  User: {
    orders: (user) => {
      return orders.filter(o => o.userId === user.id);
    }
  },
  Order: {
    __resolveReference: (order) => {
      return orders.find(o => o.id === order.id);
    }
  }
};

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

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

Apollo Gateway

Setting Up Gateway

javascript
import { ApolloGateway } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';

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

const server = new ApolloServer({
  gateway,
  subgraph: {
    delay: 5000
  }
});

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

Managed Federation

javascript
import { ApolloGateway } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
import { readFileSync } from 'fs';

const gateway = new ApolloGateway({
  supergraphSdl: readFileSync('./supergraph.graphql', 'utf-8')
});

const server = new ApolloServer({
  gateway
});

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

Entity Extensions

Extending Types

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

type Review {
  id: ID!
  rating: Int
  comment: String
  author: User
}

Multiple Keys

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

Query Planning

Efficient Queries

graphql
query GetUserWithOrders($userId: ID!) {
  user(id: $userId) {
    id
    name
    email
    orders {
      id
      total
      items {
        product {
          id
          name
          price
        }
      }
    }
  }
}

Query Plans

Gateway automatically splits queries:

javascript
# Service 1: Users
{ user(id: "1") { id name email } }

# Service 2: Orders
{ _entities(representations: [{ __typename: "User", id: "1" }]) {
  ... on User { orders { id total } } } }

# Service 3: Products
{ _entities(representations: [{ __typename: "Product", id: "101" }]) {
  ... on Product { id name price } } }

Best Practices

Service Boundaries

javascript
// Good: Domain-driven boundaries
const services = [
  { name: 'users', domain: 'identity' },
  { name: 'products', domain: 'catalog' },
  { name: 'orders', domain: 'commerce' },
  { name: 'payments', domain: 'billing' }
];

// Bad: Technical boundaries
const services = [
  { name: 'auth', domain: 'security' },
  { name: 'database', domain: 'data' }
];

Error Handling

javascript
const gateway = new ApolloGateway({
  supergraphSdl,
  buildService: ({ name, url }) => {
    return new RemoteGraphQLDataSource({
      url,
      didEncounterError(error) {
        console.error(`${name} error:`, error);
      }
    });
  }
});

Performance Monitoring

javascript
const gateway = new ApolloGateway({
  supergraphSdl,
  buildService: ({ url }) => {
    return new RemoteGraphQLDataSource({
      url,
      willSendRequest(request) {
        request.http.headers.set('x-trace-id', generateTraceId());
      }
    });
  }
});

Conclusion

Apollo Federation enables scalable microservices architecture while maintaining a unified GraphQL API. It's the modern approach to building distributed GraphQL systems.

Enjoyed this essay? Share it.

Related essays

Next essay
Apache Kafka: Building Streaming Data Pipelines