js

GraphQL Federation with Apollo Server & TypeScript: Complete Microservices Development Guide

Learn to build a complete GraphQL Federation gateway with Apollo Server & TypeScript. Master microservices architecture, cross-service relationships, and production deployment. Start building today!

GraphQL Federation with Apollo Server & TypeScript: Complete Microservices Development Guide

I’ve been thinking a lot lately about how we build modern applications. The shift toward microservices has brought incredible flexibility, but it often comes with complexity in data aggregation. That’s where GraphQL federation caught my attention—it lets you combine multiple specialized services into a unified API without sacrificing independence.

Why does this matter? Imagine having separate teams working on user management, product catalog, and order processing. Each team can own their domain, deploy independently, and still present a single, cohesive graph to your clients. It’s about giving structure to distributed systems.

Let’s start with the basics. You’ll need Node.js and TypeScript set up. Create a new project directory and initialize it with the necessary packages. I prefer using a monorepo structure for clarity, but you can organize it based on your team’s workflow.

Here’s a minimal package.json to get started:

{
  "name": "federation-gateway",
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0"
  },
  "dependencies": {
    "@apollo/server": "^4.9.0",
    "@apollo/subgraph": "^2.5.0",
    "@apollo/gateway": "^2.5.0",
    "graphql": "^16.8.0"
  }
}

Now, how do you actually build a federated service? Let’s create a user service. Each service must define its schema with federation directives and provide a way to resolve references from other services.

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

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

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

  type Query {
    getUser(id: ID!): User
  }
`;

const resolvers = {
  User: {
    __resolveReference(user: { id: string }) {
      return findUserById(user.id);
    }
  },
  Query: {
    getUser: (_: any, { id }: { id: string }) => findUserById(id)
  }
};

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

Notice the @key directive? This tells the gateway how to uniquely identify types across services. The __resolveReference method is crucial—it allows other services to fetch user data by ID.

But what happens when you need to combine data from multiple services? That’s where the gateway comes in. It aggregates all your service schemas and routes queries appropriately.

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

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'users', url: 'http://localhost:4001' },
    { name: 'products', url: 'http://localhost:4002' }
  ]
});

const server = new ApolloServer({ gateway });

The gateway doesn’t just merge schemas—it intelligently plans queries across services. When a client requests a user and their recent orders, the gateway splits the query and combines results seamlessly.

How do you handle relationships between services? Consider a product service that needs to show which user created a product. You’d extend the User type in the product service:

const typeDefs = gql`
  extend type User @key(fields: "id") {
    id: ID! @external
    products: [Product!]!
  }

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

const resolvers = {
  Product: {
    creator: (product: any) => {
      return { __typename: "User", id: product.creatorId };
    }
  }
};

The @external directive indicates that the id field is defined in another service. The gateway uses this information to resolve the user data from the appropriate service.

What about authentication? You can implement it at the gateway level or within individual services. I typically start with gateway-level authentication for common checks, then add service-specific authorization.

const gateway = new ApolloGateway({
  serviceList: [...],
  buildService({ url }) {
    return new RemoteGraphQLDataSource({
      url,
      willSendRequest({ request }) {
        request.http.headers.set('authorization', context.authToken);
      }
    });
  }
});

Error handling requires careful consideration. Each service might throw different errors, but the gateway should present consistent error messages to clients. I implement custom error formatting and logging at both levels.

Monitoring is equally important. Apollo Studio provides excellent observability tools, but you can also integrate with existing monitoring solutions. Track query performance, error rates, and service health.

When deploying to production, consider how you’ll manage schema updates. The gateway needs to validate schema changes and handle version compatibility. Automated testing becomes critical here.

Have you thought about how you’ll test your federated architecture? I recommend testing each service independently, then running integration tests against the full gateway. Mock services can help during development.

Performance optimization often involves caching strategies. The gateway can cache responses, and individual services might implement their own caching layers. Remember to consider data freshness requirements.

Common challenges include circular dependencies between types and managing breaking schema changes. How would you handle a situation where two services need to extend each other’s types?

The beauty of this approach is its flexibility. You can start small with two services and grow as needed. Each new service integrates into the existing graph without disrupting clients.

I find that federation encourages better domain modeling. Teams must think carefully about their boundaries and relationships. This often leads to cleaner, more maintainable codebases.

Remember that federation isn’t always the right choice. For simpler applications, a monolithic GraphQL server might be sufficient. But for complex systems with multiple teams, it’s hard to beat.

What questions do you have about implementing this in your own projects? I’d love to hear about your experiences and challenges.

If you found this helpful, please share it with others who might benefit. Leave a comment below with your thoughts or questions—I read and respond to every one.

Keywords: GraphQL Federation tutorial, Apollo Server TypeScript guide, microservices GraphQL architecture, federated GraphQL services, Apollo Gateway implementation, GraphQL schema composition, TypeScript microservices development, GraphQL Federation deployment, cross-service GraphQL relationships, Apollo Server Federation setup



Similar Posts
Blog Image
Build High-Performance GraphQL API with NestJS, Prisma, and Redis Caching - Complete Tutorial

Build high-performance GraphQL API with NestJS, Prisma, and Redis. Learn DataLoader patterns, caching strategies, authentication, and real-time subscriptions. Complete tutorial inside.

Blog Image
Complete Event-Driven Microservices Architecture with NestJS, RabbitMQ, and MongoDB Guide

Learn to build scalable event-driven microservices with NestJS, RabbitMQ & MongoDB. Master CQRS, event sourcing, distributed transactions & deployment strategies.

Blog Image
Complete Guide to Building Multi-Tenant SaaS APIs with NestJS, Prisma, and PostgreSQL RLS

Learn to build secure multi-tenant SaaS APIs with NestJS, Prisma & PostgreSQL RLS. Complete guide with authentication, tenant isolation, migrations & best practices.

Blog Image
Complete Next.js Prisma Integration Guide: Build Type-Safe Full-Stack Applications with Modern Database Operations

Learn how to integrate Next.js with Prisma for seamless full-stack development with type-safe database operations and modern React features.

Blog Image
Master Event-Driven Architecture with NestJS: Redis Streams and Bull Queue Implementation Guide

Learn to build scalable event-driven architecture using NestJS, Redis Streams, and Bull Queue. Master microservices, error handling, and production monitoring.

Blog Image
How to Integrate Next.js with Prisma ORM: Complete Type-Safe Database Setup Guide

Learn how to integrate Next.js with Prisma ORM for type-safe, scalable web apps. Master database management, API routes, and SSR with our complete guide.