Federation from day one: creating your first federated graph
I had the incredible opportunity to attend a conference held by the Apollo team, where I was fortunate enough to attend an enlightening session titled 'Federation from Day One'. I am eager to share my experience of setting up a locally federated graph with minimal steps for those who are experienced with GraphQL and wish to explore and comprehend the mechanics of federation.
Without further ado, let's dive right in!
Setup
Clone the repo
To save time, we will skip the regular GraphQL implementation as the main focus of this article is on the setup of federation. However, please note that this repository includes two pre-existing graphs, namely subgraph-locations
and subgraph-reviews
. I highly recommend taking a moment to explore and review the schema of these graphs.
git clone https://github.com/mruba/federation-workshop.git
Install dependencies
npm install
Install rover rover docs
Rover is Apollo's command-line interface for managing and maintaining graphs, Rover helps you safely publish and fetch GraphQL schemas (both federated and monolithic ) from the Apollo schema registry.
curl -sSL https://rover.apollo.dev/nix/latest | sh
Install Apollo Router apollo docs
Apollo Router is a high-performance GraphQL router that runs a federated supergraph, which is a single graph composed of multiple underlying services.
curl -sSL https://router.apollo.dev/download/nix/latest | sh
Create Studio account
In this tutorial, we will be using Apollo Studio. Naturally, we will need to create an account, but don't worry - they don't ask for credit card information. We can use a free account for our purposes.
Run subgraphs
Navigate to the directory where you have cloned the federation-workshop repository and run the following command.
npm run start -w subgraph-locations
Now, you should be able to use the subgraph sandbox by accessing the following address in your web browser http://localhost:4001/. To see the mock data contained in the subgraph, use the following query:
query Locations {
locations {
id
name
description
photo
}
}
let's do the same thing for subgraph-reviews
, you should be able to reach it using http://localhost:4002
npm run start -w subgraph-reviews
query LatestReviews {
latestReviews {
id
rating
comment
}
}
Managed Federation
once you create an account on GraphOS Studio, you will be able to create a new graph. To do so, click on the "Create your first Graph" on the home screen.
during the creation of the graph, you will be asked to select a name for the graph. I will be using the name federation-mike
for this tutorial.
then you will be promted with a rover cli command, you can use it as it is just make sure to replace schema
, name
and routing-url
with the appropriate values.
publish location schema
APOLLO_KEY=service:name-graph-project:your-key \
rover subgraph publish name-graph-project@current \
--schema subgraph-locations/locations.graphql \
--name locations \
--routing-url http://localhost:4001
publish reviews schema
APOLLO_KEY=service:name-graph-project:your-key \
rover subgraph publish name-graph-project@current \
--schema subgraph-reviews/reviews.graphql \
--name reviews \
--routing-url http://localhost:4002
spin up router
APOLLO_GRAPH_REF=name-graph-project@current \
APOLLO_KEY=service:name-graph-project:your-key \
./router --config router.yaml
Now you should be able to access the router using http://localhost:4000. Try runing this query.
query GetLatestReviewsAndLocations {
locations {
id
name
description
photo
}
latestReviews {
id
comment
rating
}
}
Entities
Now the fun part, let's add entities to our graph. Entities are the core of federation, they are the glue that binds the subgraphs together. Entities are the objects that are shared between the subgraphs. In our case, we will be using the Location
entity. Entities are defined in the supergraph schema, and they are used to define the relationships between the subgraphs.
But first we need to add an special function to our Apollo Server instance, this function will be used to fetch the entities from the subgraphs. To do so, we will be using the @apollo/subgraph
package. Let's add it to our project.
// subgraph-locations/index.js
// subgraph-reviews/index.js
import { buildSubgraphSchema } from "@apollo/subgraph";
const server = new ApolloServer({
schema: buildSubgraphSchema({
typeDefs,
resolvers,
}),
We also need to add an special directive to our subgraphs.
# subgraph-reviews/reviews.graphql
# subgraph-locations/locations.graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
Now let's create a new type for reviews schema in this case we will be adding Location
since reviews doesn't have any context about what this means, then we should be able to add location to our reviews type.
note that we need to use the @key
directive to define the fields that will be used to fetch the entity from the subgraph.
# subgraph-reviews/reviews.graphql
type Review {
id: ID!
"Written text"
comment: String
"A number from 1 - 5 with 1 being lowest and 5 being highest"
rating: Int
location: Location
}
type Location @key(fields: "id") {
id: ID!
}
Next we need to tell the reviews subgraph how to fetch the location entity, we will be telling the resolver how to get the unique identifier to fetch from location subgraph the location entity.
// subgraph-reviews/resolvers.js
Review: {
location(review) {
return { id: review.locationId };
}
}
Time to tell our locations subgraph how to handle Location entity, we will be using the @key
directive to tell the locations subgraph how to fetch the entity.
# subgraph-locations/locations.graphql
type Location @key(fields: "id") {
At this point we have all the dots connected the last thing is to fetch the location entity from location graph, for this we will be using special notation __resolveReference
and tell our graph to use our id previusly defined in subgraph-reviews/resolvers.js
to fetch the location entity.
// subgraph-locations/resolvers.js
Location: {
__resolveReference(location, { dataSources }) {
return dataSources.locationsAPI.getLocation(location.id);
},
},
Now we can run the following query to see the results.
query _entities($representations: [_Any!]!) {
_entities(representations: $representations) {
... on Location {
id
name
description
photo
}
}
}
As a complementary steps we can do the same thing for the Location
type in the subgraph-locations
schema, but this time we will be using the reviews
field to fetch the reviews from the subgraph-reviews
schema.
# subgraph-reviews/reviews.graphql
type Location @key(fields: "id") {
id: ID!
overallRating: Float
reviews: [Review!]
}
// subgraph-reviews/resolvers.js
Location: {
overallRating: (location, _, { dataSources }) => {
return dataSources.reviewsAPI.getOverallRatingForLocation(location.id);
},
reviews: (location, _, { dataSources }) => {
return dataSources.reviewsAPI.getReviewsForLocation(location.id);
},
},
And now you can run the following query to see the results.
query _entities($representations: [_Any!]!) {
_entities(representations: $representations) {
... on Location {
id
overallRating
reviews {
comment
}
}
}
}
Please let me know if you have any questions or suggestions.