GraphQL Federation with Express.js

GraphQL Federation is a powerful technique for building a unified GraphQL API across multiple microservices, allowing them to work seamlessly together as a single data graph. Express.js, as a flexible web server framework, is well-suited for setting up and managing GraphQL federated services.

Introduction to GraphQL Federation

GraphQL Federation allows developers to build multiple independent GraphQL services and then combine them into a single unified graph. Instead of managing a large, monolithic GraphQL schema, you can create schemas specific to each microservice and then stitch them together to form one API. This approach is especially helpful when managing complex applications where different teams are responsible for different domains or services.

Key Features:

  • Unified Schema: Combines multiple service schemas into a single GraphQL endpoint.
  • Service Boundaries: Each microservice independently defines its schema, creating clear boundaries.
  • Automatic Query Resolution: Federation automatically understands relationships between entities.

Benefits of GraphQL Federation in Microservices

Integrating GraphQL Federation in a microservice architecture offers several advantages:

  • Scalability: Each microservice can be scaled independently.
  • Clear Ownership: Each team can manage its own GraphQL schema, simplifying development and deployment.
  • Optimized Queries: Clients can request only the data they need, reducing network overhead.
  • Extensibility: New services and fields can be added to the schema without affecting other services.

Core Components of GraphQL Federation

GraphQL Federation uses several components to unify schemas across services:

  • Gateway: Acts as the main GraphQL endpoint, responsible for merging schemas from federated services.
  • Subgraphs: Individual services that define their GraphQL schemas and entities.
  • Entities: Objects or types that are shared across services (e.g., User, Product).
  • Resolvers: Functions that retrieve data for each field in a query, handling how entities are connected across services.

Setting Up Express.js for GraphQL Federation

To implement GraphQL Federation with Express.js, you’ll need to set up the server, configure federated services, and use the Apollo Federation library.

Step 1: Install Required Packages

You’ll need express, apollo-server-express, and @apollo/gateway to set up the GraphQL server and gateway.

				
					npm install express apollo-server-express @apollo/gateway graphql

				
			

Step 2: Create an Express Server

Initialize an Express.js server that will host the federated GraphQL API.

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

async function startServer() {
  const app = express();

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

  const server = new ApolloServer({
    gateway,
    subscriptions: false,
    introspection: true,
    playground: true,
  });

  await server.start();
  server.applyMiddleware({ app, path: '/graphql' });

  app.listen({ port: 4000 }, () => {
    console.log(`🚀 Gateway ready at http://localhost:4000/graphql`);
  });
}

startServer();

				
			

Building Federated Services with Express.js and Apollo Server

Each microservice needs to have its own GraphQL schema and should be set up to support federation.

Step 1: Create the User Service

The User service will handle user-related queries and expose an entity for federation.

1. Install Dependencies:

				
					npm install express apollo-server-express @apollo/federation graphql

				
			

2. Set Up User Service:

				
					// user-service.js
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
const { buildFederatedSchema } = require('@apollo/federation');

const typeDefs = gql`
  extend type Query {
    getUser(id: ID!): User
  }

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

const resolvers = {
  Query: {
    getUser: (_, { id }) => ({ id, name: "Alice", email: "alice@example.com" }),
  },
  User: {
    __resolveReference(user) {
      return { id: user.id, name: "Alice", email: "alice@example.com" };
    },
  },
};

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

server.applyMiddleware({ app, path: '/graphql' });

app.listen({ port: 4001 }, () => {
  console.log(`User service running at http://localhost:4001/graphql`);
});

				
			

3. Test User Service:

  • Visit http://localhost:4001/graphql and query:
				
					query {
  getUser(id: "1") {
    id
    name
    email
  }
}

				
			

Step 2: Create the Product Service

The Product service will handle product-related queries and connect to the User entity.

1. Set Up Product Service

				
					// product-service.js
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
const { buildFederatedSchema } = require('@apollo/federation');

const typeDefs = gql`
  extend type Query {
    getProduct(id: ID!): Product
  }

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

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

const resolvers = {
  Query: {
    getProduct: (_, { id }) => ({ id, name: "Laptop", owner: { id: "1" } }),
  },
};

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

server.applyMiddleware({ app, path: '/graphql' });

app.listen({ port: 4002 }, () => {
  console.log(`Product service running at http://localhost:4002/graphql`);
});

				
			

2. Test Product Service:

  • Visit http://localhost:4002/graphql and query
				
					query {
  getProduct(id: "101") {
    id
    name
    owner {
      id
    }
  }
}

				
			

Managing Relationships Across Services

In a federated setup, services can reference each other by defining shared entities. For example, the Product service references the User entity by using an @key directive and @external fields.

Advanced Federation Techniques

  • Custom Directives: Use custom directives to handle authentication and authorization.
  • Data Caching: Implement caching to reduce response times and improve performance.

Best Practices and Common Challenges

  1. Entity Design: Define clear boundaries for shared entities to avoid tight coupling.
  2. Error Handling: Implement standardized error handling for consistent responses.
  3. Performance Optimization: Monitor performance and optimize queries for complex relationships.

GraphQL Federation with Express.js enables the creation of highly modular and scalable architectures. By splitting services based on domains and connecting them through a federated gateway, teams can develop and deploy independently. Happy Coding!❤️

Table of Contents