I’ve been thinking a lot about how modern applications are evolving into complex ecosystems of microservices. Recently, I worked on a project where we had separate teams building user management, product catalog, and order processing systems. The challenge was creating a unified API that could serve client applications without forcing them to understand our internal service boundaries. That’s when GraphQL Federation with Apollo Server and TypeScript became our go-to solution.
GraphQL Federation lets you combine multiple GraphQL services into a single cohesive graph. Each service maintains its independence while contributing to a larger unified schema. This approach eliminates the need for clients to make multiple requests to different endpoints. Instead, they interact with one gateway that intelligently routes queries to the appropriate services.
Have you ever wondered how large companies manage to keep their APIs consistent across dozens of microservices? Federation provides the answer through schema composition. The gateway automatically merges type definitions from all services while preserving ownership boundaries. Services declare which types they can resolve and how they extend types defined elsewhere.
Let me show you how we set up our federated architecture. We started with a monorepo containing separate packages for each service and the gateway. Using TypeScript ensured type safety across all our services. Here’s a basic structure:
// In users-service/src/schema.ts
import { gql } from 'apollo-server-express';
export const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3",
import: ["@key", "@shareable"])
type User @key(fields: "id") {
id: ID!
email: String!
username: String!
}
`;
// In products-service/src/schema.ts
export const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3",
import: ["@key"])
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
}
extend type User @key(fields: "id") {
id: ID! @external
favoriteProducts: [Product!]!
}
`;
What happens when a client requests a user’s favorite products? The gateway knows to fetch user data from the users service and product data from the products service. Entity resolution handles this seamlessly through reference resolvers. Each service defines how to resolve entities it owns when other services reference them.
Building the gateway was surprisingly straightforward. We used Apollo Gateway to compose the supergraph from our running services. The gateway automatically handles query planning and execution across services. Here’s how we configured it:
// In gateway/src/index.ts
import { ApolloGateway } from '@apollo/gateway';
import { ApolloServer } from 'apollo-server-express';
const gateway = new ApolloGateway({
serviceList: [
{ 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 });
Authentication posed an interesting challenge. We implemented a shared authentication context that propagates user information across services. The gateway validates JWT tokens and adds user context to requests. Each service can then enforce authorization based on this context. Did you consider how to maintain security boundaries in a federated system?
Caching became crucial for performance. We implemented response caching at the gateway level and data loader patterns within services to avoid N+1 queries. Error handling required special attention too. We standardized error formats across services and implemented circuit breakers to prevent cascade failures.
Testing our federated graph involved both unit tests for individual services and integration tests for the complete system. We used Apollo Studio to monitor query performance and identify bottlenecks. Deployment required careful coordination, but Docker and Kubernetes made it manageable.
One lesson I learned is to start with clear entity ownership rules. Services should own specific types and extend others cautiously. Version control for schemas became essential as our graph evolved. We used schema registries to track changes and ensure compatibility.
What strategies would you use to handle breaking changes in a federated graph? We found that gradual rollouts and feature flags worked well. Monitoring query patterns helped us anticipate performance issues before they affected users.
Building with federation transformed how our teams collaborate. Frontend developers get a single endpoint with full type safety. Backend teams can deploy independently while maintaining schema consistency. The unified graph makes data relationships visible and manageable.
I hope this guide helps you approach microservices integration with confidence. The combination of GraphQL Federation, Apollo Server, and TypeScript creates a robust foundation for scalable applications. If you found this useful, please like and share this article. I’d love to hear about your experiences in the comments—what challenges have you faced with microservices integration?