Federation quickstart
Part 1 - Local schema composition
In addition to completing this tutorial, you can clone our example gateway on GitHub. The GitHub example is not identical to the gateway you create in this tutorial, but it demonstrates many of the same concepts.
Hello! This tutorial gets you up and running with Apollo Federation. It currently requires Node.js version 12 or 14.
Federation concepts
In a federated architecture, multiple GraphQL APIs are composed into a single federated graph. The individual APIs are called subgraphs, and they're composed into a supergraph:
Usually, each subgraph corresponds to a different service in your backend. The supergraph is then represented by a gateway, which routes each incoming query to the appropriate combination of subgraphs and returns the combined result.
The supergraph's schema is the combination of each subgraph's schema, plus some special federation-specific directives. Each subgraph's schema can even reference and extend types that originate in a different subgraph.
This architecture enables clients to query data from multiple services simultaneously, just by querying the gateway.
Now that we have a high-level understanding of federation concepts, let's jump in.
Example subgraphs
This tutorial uses two Apollo-hosted subgraphs for an example application: a Products subgraph and a Reviews subgraph. Here are their schemas for reference:
Products
1enum CURRENCY_CODE {
2 USD
3}
4
5type Department {
6 category: ProductCategory
7 url: String
8}
9
10type Money {
11 amount: Float
12 currencyCode: CURRENCY_CODE
13}
14
15"""Here are some helpful details about your type"""
16type Price {
17 cost: Money
18
19 """A number between 0 and 1 signifying the % discount"""
20 deal: Float
21 dealSavings: Money
22}
23
24"""
25This is an Entity, docs:https://www.apollographql.com/docs/federation/entities/
26You will need to define a __resolveReference resolver for the type you define, docs: https://www.apollographql.com/docs/federation/entities/#resolving
27"""
28type Product @key(fields: "id") {
29 id: ID!
30 title: String
31 url: String
32 description: String
33 price: Price
34 salesRank(category: ProductCategory = ALL): Int
35 salesRankOverall: Int
36 salesRankInCategory: Int
37 category: ProductCategory
38 images(size: Int = 1000): [String]
39 primaryImage(size: Int = 1000): String
40}
41
42enum ProductCategory {
43 ALL
44 GIFT_CARDS
45 ELECTRONICS
46 CAMERA_N_PHOTO
47 VIDEO_GAMES
48 BOOKS
49 CLOTHING
50}
51
52extend type Query {
53 bestSellers(category: ProductCategory = ALL): [Product]
54 categories: [Department]
55 product(id: ID!): Product
56}
Reviews
1extend type Product @key(fields: "id") {
2 id: ID! @external
3 reviews: [Review]
4 reviewSummary: ReviewSummary
5}
6
7"""
8This is an Entity, docs:https://www.apollographql.com/docs/federation/entities/
9You will need to define a __resolveReference resolver for the type you define, docs: https://www.apollographql.com/docs/federation/entities/#resolving
10"""
11type Review @key(fields: "id") {
12 id: ID!
13 rating: Float
14 content: String
15}
16
17type ReviewSummary {
18 totalReviews: Int
19 averageRating: Float
20}
1. Install the Rover CLI
Rover is Apollo's CLI for managing graphs, including federated ones.
Install it like so:
1npm install -g @apollo/rover
After installing, run rover
in your terminal with no arguments to confirm that it installed successfully. We'll use various Rover commands in later steps.
2. Create a gateway
You can also clone our example gateway on GitHub. The GitHub example is not identical to the gateway you create in this tutorial, but it demonstrates the same concepts.
As mentioned above, your federated supergraph is represented by a gateway that routes queries to various subgraphs. For this tutorial, we'll use some Apollo-hosted example services as our subgraphs, and we'll set up our own gateway in front of them.
With the help of the @apollo/gateway
library, Apollo Server can act as our federated gateway.
On your development machine, first create a new project directory for your Node.js gateway. Then inside that directory, run the following to create a package.json
file:
1npm init
Next, install the following required libraries:
1npm install apollo-server @apollo/gateway
Finally, create a file named index.js
and paste the following into it as a minimal (not-yet-functional) gateway implementation:
1const { ApolloServer } = require('apollo-server');
2const { ApolloGateway } = require('@apollo/gateway');
3
4const supergraphSdl = ''; // TODO!
5
6const gateway = new ApolloGateway({
7 supergraphSdl
8});
9
10const server = new ApolloServer({
11 gateway,
12});
13
14server.listen().then(({ url }) => {
15 console.log(`🚀 Gateway ready at ${url}`);
16}).catch(err => {console.error(err)});
This code demonstrates the basic flow for creating a gateway:
We initialize an
ApolloGateway
instance and pass it the complete composed SDL for our supergraph.Note the
TODO
. We'll obtain the composed schema in the next section.
We initialize an
ApolloServer
instance and pass it our gateway via thegateway
option.We call
listen
on our server instance to begin listening for incoming requests.
If we run this code as-is with node index.js
, we get an error:
1GraphQLError [Object]: Syntax Error: Unexpected <EOF>.
That's because our supergraphSdl
is currently empty! Next, we'll compose that schema.
3. Compose the supergraph schema
As a best practice, your gateway should not be responsible for composing its own supergraph schema. Instead, a separate process should compose the schema and provide it to the gateway. This helps improve reliability and reduce downtime when you make changes to a subgraph.
There are multiple ways to compose a supergraph schema from our subgraph schemas:
On our local machine using the Rover CLI (we'll start with this)
Via managed federation in Apollo Studio (we'll switch to this in Part 2)
Using managed federation is strongly recommended for production environments. We'll start with local composition to get up and running.
Providing subgraph details
To compose our supergraph schema, the Rover CLI needs the following information about each of our subgraphs:
The subgraph's schema
The URL of the subgraph's GraphQL endpoint (which must be accessible by the gateway)
Because we're using Apollo-hosted example subgraphs, we know their endpoint URLs. And Rover can use those URLs to fetch their schemas via a special introspection query.
Configuration file
We provide subgraph details to the Rover CLI in a YAML file. In your project directory, create a file called supergraph-config.yaml
and paste the following into it:
1subgraphs:
2 products:
3 routing_url: https://rover.apollo.dev/quickstart/products/graphql
4 schema:
5 subgraph_url: https://rover.apollo.dev/quickstart/products/graphql
6 reviews:
7 routing_url: https://rover.apollo.dev/quickstart/reviews/graphql
8 schema:
9 subgraph_url: https://rover.apollo.dev/quickstart/reviews/graphql
As you can see, we're providing the same URL as the value of two different fields. These fields serve different purposes:
routing_url
is the URL the gateway will use to send GraphQL operations to the subgraph at runtime.schema.subgraph_url
is the URL that Rover will use to fetch the subgraph schema during composition.
These URLs might theoretically differ. The YAML file also supports providing a subgraph's schema as a local file path, or as a registered graph ref that Rover can fetch from Apollo (for details, see the Rover docs).
Performing composition
Now that our configuration file is ready, we can compose our supergraph schema. To do that, we'll use Rover's supergraph compose
command.
From your project directory, run the following command in your terminal:
1rover supergraph compose --config ./supergraph-config.yaml
Rover outputs the following schema:
Click to expand
1schema
2 @core(feature: "https://specs.apollo.dev/core/v0.2"),
3 @core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION)
4{
5 query: Query
6}
7
8directive @core(feature: String!, as: String, for: core__Purpose) repeatable on SCHEMA
9
10directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet) on FIELD_DEFINITION
11
12directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on OBJECT | INTERFACE
13
14directive @join__owner(graph: join__Graph!) on OBJECT | INTERFACE
15
16directive @join__graph(name: String!, url: String!) on ENUM_VALUE
17
18enum core__Purpose {
19 EXECUTION
20 SECURITY
21}
22
23enum CURRENCY_CODE {
24 USD
25}
26
27type Department {
28 category: ProductCategory
29 url: String
30}
31
32scalar join__FieldSet
33
34enum join__Graph {
35 PRODUCTS @join__graph(name: "products" url: "https://rover.apollo.dev/quickstart/products/graphql")
36 REVIEWS @join__graph(name: "reviews" url: "https://rover.apollo.dev/quickstart/reviews/graphql")
37}
38
39type Money {
40 amount: Float
41 currencyCode: CURRENCY_CODE
42}
43
44"""Here are some helpful details about your type"""
45type Price {
46 cost: Money
47
48 """A number between 0 and 1 signifying the % discount"""
49 deal: Float
50 dealSavings: Money
51}
52
53"""
54This is an Entity, docs:https://www.apollographql.com/docs/federation/entities/
55You will need to define a __resolveReference resolver for the type you define, docs: https://www.apollographql.com/docs/federation/entities/#resolving
56"""
57type Product
58 @join__owner(graph: PRODUCTS)
59 @join__type(graph: PRODUCTS, key: "id")
60 @join__type(graph: REVIEWS, key: "id")
61{
62 category: ProductCategory @join__field(graph: PRODUCTS)
63 description: String @join__field(graph: PRODUCTS)
64 id: ID! @join__field(graph: PRODUCTS)
65 images(size: Int = 1000): [String] @join__field(graph: PRODUCTS)
66 price: Price @join__field(graph: PRODUCTS)
67 primaryImage(size: Int = 1000): String @join__field(graph: PRODUCTS)
68 reviewSummary: ReviewSummary @join__field(graph: REVIEWS)
69 reviews: [Review] @join__field(graph: REVIEWS)
70 salesRank(category: ProductCategory = ALL): Int @join__field(graph: PRODUCTS)
71 salesRankInCategory: Int @join__field(graph: PRODUCTS)
72 salesRankOverall: Int @join__field(graph: PRODUCTS)
73 title: String @join__field(graph: PRODUCTS)
74 url: String @join__field(graph: PRODUCTS)
75}
76
77enum ProductCategory {
78 ALL
79 BOOKS
80 CAMERA_N_PHOTO
81 CLOTHING
82 ELECTRONICS
83 GIFT_CARDS
84 VIDEO_GAMES
85}
86
87type Query {
88 bestSellers(category: ProductCategory = ALL): [Product] @join__field(graph: PRODUCTS)
89 categories: [Department] @join__field(graph: PRODUCTS)
90 product(id: ID!): Product @join__field(graph: PRODUCTS)
91}
92
93"""
94This is an Entity, docs:https://www.apollographql.com/docs/federation/entities/
95You will need to define a __resolveReference resolver for the type you define, docs: https://www.apollographql.com/docs/federation/entities/#resolving
96"""
97type Review
98 @join__owner(graph: REVIEWS)
99 @join__type(graph: REVIEWS, key: "id")
100{
101 content: String @join__field(graph: REVIEWS)
102 id: ID! @join__field(graph: REVIEWS)
103 rating: Float @join__field(graph: REVIEWS)
104}
105
106type ReviewSummary {
107 averageRating: Float
108 totalReviews: Int
109}
As you can see, this composed schema includes all of the types and fields from our subgraph schemas, along with many additional directives that the gateway uses to support our federated architecture.
Now, append > supergraph.graphql
to the above command to write the composed schema to a file:
1rover supergraph compose --config ./supergraph-config.yaml > supergraph.graphql
4. Start the gateway
We can now edit our index.js
file to pull in our composed schema. Replace the file's contents with the following:
1const { ApolloServer } = require('apollo-server');
2const { ApolloGateway } = require('@apollo/gateway');
3const { readFileSync } = require('fs');
4
5const supergraphSdl = readFileSync('./supergraph.graphql').toString();
6
7const gateway = new ApolloGateway({
8 supergraphSdl
9});
10
11const server = new ApolloServer({
12 gateway,
13});
14
15server.listen().then(({ url }) => {
16 console.log(`🚀 Gateway ready at ${url}`);
17}).catch(err => {console.error(err)});
Now with our supergraphSdl
properly populated, let's start up the gateway again with node index.js
. This time, there's no error!
We can quickly open our browser to studio.apollographql.com/sandbox to explore our composed schema in Apollo Sandbox:
While we're here, you can even execute some test queries against the supergraph.
Nice job! Our supergraph gateway is running locally and communicating with our Apollo-hosted subgraphs.
Next, we'll move our supergraph composition into Apollo Studio so our gateway can pull schema changes dynamically during runtime. On to part 2!