A Quick One on GraphQL federation

Fredrick Mbugua
7 min readFeb 15, 2023

GraphQL is a popular query language used for building APIs. It has several advantages over traditional REST APIs, including the ability to fetch data from multiple endpoints with a single request, and it allows clients to specify exactly what data they need. However, as APIs become more complex, managing the data from different endpoints becomes more difficult. This is where GraphQL federation by Apollo comes in. GraphQL federation is a way to split a single GraphQL schema across multiple services, allowing you to scale and manage large APIs more easily.

What is GraphQL federation?

GraphQL federation is an approach to building GraphQL APIs that allows you to split a large schema into smaller, more manageable schemas. This is useful when you have a large API with many different data sources, or when you need to scale your API beyond a single server. By splitting the schema, you can manage each data source separately and scale them independently.

The core concept behind GraphQL federation is that instead of having a single schema that describes all the data in your API, you have a set of smaller schemas, each describing a specific part of your data. These smaller schemas are then federated together to form a larger schema that can be used by clients to query the entire API.

One of the most popular implementations of GraphQL federation is by Apollo, which provides a set of tools and libraries to help you build and manage federated schemas.

Getting Started with GraphQL Federation by Apollo

To get started with GraphQL federation by Apollo, you first need to install the required dependencies. This includes the Apollo Server and the Apollo Gateway. You can install them using the following command:

npm install apollo-server apollo-server-express apollo-gateway

Once you have installed the required dependencies, you can create a gateway server that will manage your federated schemas. The gateway server is responsible for routing requests to the correct services and aggregating the responses.

const { ApolloGateway } = require("@apollo/gateway");

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

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

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

In this example, we create a new ApolloGateway instance and specify a list of services. Each service is identified by a unique name and a URL that points to the location of the service. We then create a new ApolloServer instance and pass the gateway as the argument. Finally, we start the server and print the URL to the console.

Each service should have its own GraphQL schema and resolver functions. In a real-world scenario, you would have multiple services with their own schemas and resolvers. Here’s an example of a simple schema for a user service:

type User {
id: ID!
name: String!
email: String!
}

type Query {
user(id: ID!): User
}

And here’s an example of a simple resolver function for the user service:

const { DataSource } = require("apollo-datasource");

class UserAPI extends DataSource {
async getUserById(userId) {
return this.get(`users/${userId}`);
}
}

const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
return dataSources.userAPI.getUserById(id);
},
},
};

In this example, we create a new UserAPI class that extends the DataSource class provided by Apollo. We then define a getUserById method that returns the user with the specified ID.

Define Subgraph Services

Now that we have the gateway setup, let’s define our subgraph services.

accounts-service

The accounts-service provides the account information for each user.

// accounts-service.js
const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
type Query {
account(id: ID!): Account
}

type Account {
id: ID!
email: String!
name: String!
}
`
;

const accounts = [
{ id: '1', email: 'johndoe@example.com', name: 'John Doe' },
{ id: '2', email: 'janedoe@example.com', name: 'Jane Doe' },
];

const resolvers = {
Query: {
account: (parent, { id }) => {
return accounts.find(account => account.id === id);
},
},
};

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

server.listen().then(({ url }) => {
console.log(`Accounts service running at ${url}`);
});

reviews-service

The reviews-service provides reviews for each product.

// reviews-service.js
const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
type Query {
reviews(productId: ID!): [Review]
}

type Review {
id: ID!
productId: ID!
body: String!
authorId: ID!
author: Account
}
`
;

const reviews = [
{
id: '1',
productId: '1',
body: 'Great product!',
authorId: '1',
},
{
id: '2',
productId: '1',
body: 'Could be better.',
authorId: '2',
},
{
id: '3',
productId: '2',
body: 'Not what I expected.',
authorId: '2',
},
];

const resolvers = {
Query: {
reviews: (parent, { productId }) => {
return reviews.filter(review => review.productId === productId);
},
},
Review: {
author: (review) => {
return { __typename: 'Account', id: review.authorId };
},
},
};

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

server.listen().then(({ url }) => {
console.log(`Reviews service running at ${url}`);
});

products-service

The products-service provides product information and aggregates the reviews for each product.

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

const typeDefs = gql`
type Query {
product(id: ID!): Product
topProducts(first: Int = 5): [Product]
}

type Product @key(fields: "id") {
id: ID!
name: String!
price: Int!
reviews: [Review]
}

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

const products = [
{ id: '1', name: 'Product 1', price: 99 },
{ id: '2', name: 'Product 2', price: 199 },
];

const resolvers = {
Query: {
product: (parent, { id }) => {
return products.find(product => product.id === id);
},
topProducts: (parent, { first }) => {
return products.slice(0, first);
},
},
Product: {
__resolveReference(product) {
return products.find(({ id }) => id === product.id);
},
reviews(product) {
return reviews.filter(review => review.productId === product.id);
},
},
};

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

server.listen(4001).then(({ url }) => {
console.log(🚀 Server ready at ${url});
});

In the above code, we have defined the resolvers for our Product type and for the Query type.

For the Product type, we have defined two resolvers. The first resolver is the __resolveReference function, which returns the product based on the given reference. The second resolver is the reviews function, which returns the reviews associated with the product.

For the Query type, we have defined two resolvers as well. The first resolver is the product function, which returns the product based on the given ID. The second resolver is the topProducts function, which returns the top N products based on the given argument.

Now, let’s move on to the reviews-service.js file.

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

const typeDefs = gql`
type Query {
review(id: ID!): Review
reviews: [Review]
}

type Review @key(fields: "id") {
id: ID!
productId: ID!
rating: Int!
}

extend type Product @key(fields: "id") {
id: ID! @external
reviews: [Review]
}
`
;

const reviews = [
{ id: '1', productId: '1', rating: 4 },
{ id: '2', productId: '1', rating: 5 },
{ id: '3', productId: '2', rating: 3 },
{ id: '4', productId: '2', rating: 4 },
];

const resolvers = {
Query: {
review: (parent, { id }) => {
return reviews.find(review => review.id === id);
},
reviews: () => {
return reviews;
},
},
Review: {
__resolveReference(review) {
return reviews.find(({ id }) => id === review.id);
},
},
};

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

server.listen(4002).then(({ url }) => {
console.log(🚀 Server ready at ${url});
});

In the above code, we have defined the resolvers for our Review type and for the Query type.

For the Review type, we have defined one resolver, which is the __resolveReference function. This function returns the review based on the given reference.

For the Query type, we have defined two resolvers. The first resolver is the review function, which returns the review based on the given ID. The second resolver is the reviews function, which returns all the reviews.

Finally, we create an instance of ApolloServer for both services, passing in the schema built by buildFederatedSchema, which combines both schemas into a single schema.

Now, we can run both services and start querying the federated schema. We can run the following command in the terminal to start the products service:

node products-service.js

We can then run the following command in another terminal window to start the reviews service:

node reviews-service.js

Once you have set up and run the services, you can navigate to the Apollo Studio web interface at https://studio.apollo.dev/. From there, you can check out the schema and see the federated schema that has been created by stitching together the schemas from the different services.

You can also test the federated schema using the built-in playground in Apollo Server. In the playground, you can run queries against the federated schema as if it were a single API.

Here’s an example query that retrieves a product by its ID and includes its associated reviews:

query {
product(id: "1") {
id
name
price
reviews {
id
body
}
}
}

This query will return the following result:

{
"data": {
"product": {
"id": "1",
"name": "Product 1",
"price": 99,
"reviews": [
{
"id": "1",
"body": "Great product!"
},
{
"id": "2",
"body": "Could be better"
}
]
}
}
}

As you can see, the federated schema is able to combine data from both services to provide a unified API.

In conclusion, GraphQL federation is a powerful tool for creating a unified API from multiple services. With Apollo Federation, it’s easy to set up a federated schema and stitch together schemas from different services. With this approach, you can leverage the strengths of microservices while providing a consistent API to your clients.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Fredrick Mbugua
Fredrick Mbugua

Written by Fredrick Mbugua

Software Developer @Safaricom. Experienced in Web, Mobile (Flutter & Kotlin), CrytpoCurrency, Smart Contracts, API and Software Design, Interaction Design

No responses yet

Write a response