This article assumes you have a decent knowledge of building basic GraphQL servers and fundamental GraphQL concepts. If you are new to GraphQL, we recommend starting with this article.
#Introduction to Schema Stitching
Schema stitching is one of the ways in which we can combine multiple GraphQL services and expose them collectively via a single endpoint to the clients. In schema stitching, there are many GraphQL microservices
, and we have a Gateway
in front of these microservices that interacts with the client. The way this technique works is by importing individual schemas from different services on the gateway. The schemas are then stitched
by the gateway, wherein the gateway combines all types, queries, and mutations from different services into a new executable schema which is then exposed to the clients.
To understand Schema Stitching better, let us take an example, suppose we have a User Microservice
and a Contact Microservice
. The relationship is one user can have many contacts.
This is our User service schema
type User {id: ID!name: String!email: String!}type Query {user(id: ID!): Userusers: [User!]!}
The GraphQL schema defines User
type and exposes two queries - user
to retrieve a single user and users​​
to fetch a list of users.
This is our Contact service schema
type Contact {id: ID!name: String!phoneNumber: String!userId: ID!}type Query {contact(id: ID!): Contactcontacts: [Contact!]!}
This GraphQL schema defines a Contact
type and exposes two queries - contact
to retrieve a single contact and contacts
to fetch a list of contacts.
Let us say these two schemas are served by two different GraphQL endpoints, now in order to query everything under a single endpoint, we need to set up a gateway service that will combine these schemas and stitch them like this.
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import { getGatewaySchema } from './schema.js';import { buildHTTPExecutor } from '@graphql-tools/executor-http';import { schemaFromExecutor } from '@graphql-tools/wrap';import { stitchSchemas } from '@graphql-tools/stitch'const USER_SERVICE_ENDPOINT = 'http://localhost:4001/graphql';const CONTACT_SERVICE_ENDPOINT = 'http://localhost:4002/graphql';const PORT = 4000;async function getExecutableSubSchema(endpoint) {const executor = buildHTTPExecutor({endpoint});const schema = await schemaFromExecutor(executor);return {schema,executor}}async function getGatewaySchema() {const userSubSchema = await getExecutableSubSchema(USER_SERVICE_ENDPOINT);const contactSubSchema = await getExecutableSubSchema(CONTACT_SERVICE_ENDPOINT);const gatewaySchema = stitchSchemas({subschemas: [userSubSchema, contactSubSchema]});return gatewaySchema;}async function startApolloServer() {const gatewaySchema = await getGatewaySchema();const server = new ApolloServer({schema: gatewaySchema,});const { url } = await startStandaloneServer(server, { listen: { port: PORT } })console.log(`🚀 Gateway started at: ${url}`);}startApolloServer();
The provided gateway code sets up an Apollo server that acts as a gateway to multiple GraphQL services. The getGatewaySchema
function stitches the schemas of the user service and contacts service together using the stitchSchemas
function from @graphql-tools
. This code enables the gateway to handle incoming GraphQL queries and distribute them to the respective services, aggregating and returning the results to the client.
We can also query types that have relations and configure how to resolve data across our microservices. For instance, in the above example, you may want to query all contacts for a user, or while querying contacts, you may want to see what contact a user belongs to.
In schema stitching, to resolve these situations, there are three approaches Schema Extensions Type Merging Directive-based Type Merging
Schema Extensions and Type Merging are techniques wherein we write some code at the gateway level to accommodate type merging. The Gateway will have a bird’s-eye view of its all microservices and their respective types, it will extend these types and delegate resolving fields of different types to respective microservices. We have to manually provide and maintain this configuration in the Gateway code. Directive-based type merging is a technique that is similar to Federation, wherein the microservices will have the configuration on how to resolve fields and not the Gateway.
Schema stitching is a quick solution to provide you with a unified GraphQL API. It is appropriate for use cases where there are not many shared types among GraphQL services, and we just want to combine different GraphQL services under a single endpoint. For example, if we have a software product with a backend GraphQL API and we have another separate PDF GraphQL microservice. Now we want the frontend systems to access these two backend services via a unified interface then, schema stitching will be a good approach as it requires less effort. Also, the Product and PDF microservice will not have so many shared types, so maintenance on the gateway is likely to be low. Schema stitching provides a quick and straightforward way to combine schemas.
Given that we can manage shared types in schema stitching and resolve fields via the Gateway, it is likely that gateway code can quickly get messy if numerous teams are managing a large number of microservices with many common types. Stitching might not be the most optimal choice for building very large-scale systems. If you have many microservices that use shared types and different microservices are responsible for different fields of a shared type, then it's probably better to take a look at GraphQL Federation.
#Introduction To GraphQL Federation
Similar to Schema Stitching, GraphQL Federation also allows you to bring multiple GraphQL services under a single endpoint. Federation is a long-term scalable solution that allows you to build large systems and scale them as per your needs with low maintenance efforts. It is harder to implement a federated graph compared to stitching two existing GraphQL microservices and has a steeper learning curve as developers need to understand all federation ecosystem concepts and implement all microservices accordingly, whereas schema stitching is more like integrating a library over your existing services.
In federation, particularly talking about Apollo federation, we call individual GraphQL services with their individual schemas and resolvers as SubGraphs
. There is also a Router
that builds a SuperGraph
from existing subgraphs and is exposed to the client. The high-level architecture might look similar to schema stitching, but the code-level implementation and, most importantly the GraphQL schema design of subgraphs differ greatly compared to schema stitching.
Federation highly promotes the separation of concerns principle. Designing schemas that are separated by concerns is essential for implementing federated graphs. For example, in the schema stitching example above, we saw User
and Contact
types individually existing in User and Contact services, respectively. The Gateway can have the connection logic implemented and type merging if needed. On the other side, the federation promotes building schemas that are separated by concerns
. This means each subgraph should define types and fields that it is responsible for populating from its backend data store. To build a federated graph for the same use case, this is how our schemas would look like.
User service schema
type User @key(fields:"id") {id: ID!name: String!email: String!}type Query {user(id: ID!): Userusers: [User!]!}
Contact service schema
type User @key(fields: "id"){id: ID!userContacts: [Contact!]!}type Contact {id: ID!name: StringphoneNumber: Stringuser: User}type Query {contact(id: ID!): Contactcontacts: [Contact!]!}
Carefully check the User
type. It is defined in both the User and Contact subgraph. This is the separation of concerns principle in effect. Even though userContacts
is actually a field of User
type, it is not defined in the user service but in the contact service because it has more to do with getting all contacts, so we shared the User type between contact and user subgraphs. These shared types are known as entities
in federation, and different subgraphs might be responsible for populating different fields of an entity. The @key
directive is used to define the primary key (in this case - id
) among all subgraphs, which denotes how to uniquely identify a User type across subgraphs. From the perspective of the User type, when you query from the unified gateway, the fields name
and email
will be resolved by the User subgraph, and the field userContacts
will be resolved by the Contact subgraph.
In Federation, the router will build the supergraph automatically and also handle the type merging for you, all you need to do is provide subgraph URLs to the router, and it will build the supergraph for you by a process known as composition
. Once your router is up, it will expose all fields under a unified User type, and you can directly make a nested query below, and it will be handled by respective subgraphs.
query {users {nameuserContacts {namephoneNumber}}}
GraphQL Federation is the ideal choice when there are multiple teams maintaining different services with their own data and functionality, there are many shared types. You must carefully design your schemas in your subgraphs based on the design principle of separation by concerns. It is good to use federation when designing your services from scratch that can scale well.
#Introduction to Content Federation
Same / Similar Graphic from this section can be put here
Content federation is again the ability to bring data together from multiple sources and backends via API instead of actually migrating the data itself. It sounds a lot like what we have seen so far in this article, but it is directed more towards a CMS context, whereas schema stitching and GraphQL federation are directed more towards building a backend GraphQL server.
If you are using a CMS service or have your own CMS set up, and you want to integrate some additional data that is provided by some other external API, then you can use content federation if it is supported by your CMS. HyGraph supports content federation by Remote Sources
. With Remote Sources, you can seamlessly configure any external data source and query it from the Playground. A Remote Source refers to a system or product that contains content that needs to be integrated with the content in Hygraph, creating a unified API. Setting up a Remote Source is a straightforward process through our user-friendly low-code interface. We provide support for both RESTful and GraphQL APIs. Take a deeper look at Content Federation in this article if you want to.
#Key Differences between Schema Stitching vs. GraphQL Federation vs. Content Federation
Schema Stitching | GraphQL Federation | Content Federation |
---|---|---|
Enables you to combine multiple GraphQL schemas into one. | Enables a distributed architecture for a large-scale GraphQL backend. | Integrates data from multiple remote sources into a CMS platform. |
Two techniques of schema require manual update and maintenance of the gateway. | The router automatically merges federated schema from subgraphs. | Integrates data sources using remote configuration. |
Separation of concerns is good to have but is not a strict requirement to be followed. | The separation of concerns principle is mandatory and very important while designing the schema of subgraphs. | Content Federation does not directly involve the separation of concerns principle. |
Good for use cases when the services are already built independently, and we just need a way to bring them under a single endpoint. | Good for use cases where we need to design large-scale GraphQL systems from scratch. Federation facilitates scalability, maintainability, and independent development of subgraphs. | Good for use cases when there is already a CMS in our architecture, and we want to aggregate and manage content from various systems without migrating or duplicating the data. |
The learning curve is low | The learning curve is high | The learning curve is low |
The GraphQL Report 2024
Statistics and best practices from prominent GraphQL users.
Check out the report