3. Building the subgraphs
10m

Overview

In this section, we'll cover:

  • Creating a to expose our REST API

Prerequisites

  • Apollo Rover installed in your local machine
  • Your GCP project id, service account email, and key. If you have not received these, your Apollo host should be able to provide it
  • gcloud, the GCP command line tool installed in your local machine.

Building the subgraphs

To power our , we have three different using Apollo Server, an open-source for Node.js fetching against different .

Orders subgraph

In this section we will cover how to build our Orders which is fetching against a REST API.

Note: is spec-compliant, which means that the fundamentals of how a is processed and resolved will be identical to any spec-compliant GraphQL framework that you may use in your own evaluation in the future. See the full list of compliant libraries.

Orders subgraph (REST datasource)

Currently our client-side teams have implemented the orchestration logic to fetch the associated product and customer IDs from our REST endpoint.

The Order serves all data associated within the domain of an order. In addition, its schema needs to reflect the relationship with the products and customers by providing the IDs for the entities.

Note: in a federated , the GraphOS Router can create a to automatically resolve the details of a product and customer. This makes it incredibly easy for clients to make a single request where the will perform an API join behind the scenes.

✏️ Schema design for an Order

There are two main issues with the current way things are running at the moment at KBT Threads:

  • One one hand, KBT's Orders team is not responsible (ideally) for managing the information for Products and Customers. They just manage the Orders system and store only the IDs of the product and customers associated with an order.

  • On the other hand, our Client teams, historically, would make three round-trip calls to fetch the entire order information:

    1. The IDs of the products and customer of an order
    2. The customer information for the customer ID associated in the order
    3. The product information for the IDs in the order items

And in many instances, these responses were over fetching data, as many of the returned in these calls were not used at all by the client front-end.

Fortunately, will give us that flexibility by defining exactly what we need, and federation will abstract this orchestration logic into one single request. Let's take a look at how we can model this in our Orders :

Open the schema.graphql file found in rest-orders/schema.graphql. You'll immediately notice two types already defined for us: Query and Order:

Query is a special root type that serves as an entry point for your services. In this case, Query allows an order to be retrieved by passing an ID, which returns an Order.

rest-orders/schema.graphql
type Query {
order(id: ID!): Order
}
type Order @key(fields: "id") {
id: ID!
}

Now if we look at our Order type, it currently only has a single attribute defined, id. This isn't very helpful for our client teams, so let's expand this type with more information.

To do this, we first need to add attributes that reflect the buyer and items of an order:

rest-orders/schema.graphql
# unfinished schema
type Order @key(field: "id") {
id: ID!
buyer: ?
items: [?]
}

Given that this is a Federated Graph, let's see if we can reuse what other teams have provided to prevent code duplication and standardize on common types that our clients expect.

We do this by referencing another provided by other , or in other words an Entity.

In , an is an that you define in one and can then reference and extend in other subgraphs, which are the core building block of a federated graph. Learn more: Introduction to Apollo Federation

We know that the Users is returning a User that requires a User ID. That’s great, as we have a User ID. Let’s return the User type for the buyer attribute:

rest-orders/schema.graphql
type Order @keys(field: "id") {
id: ID!
buyer: User!
items: [?]
}

Repeating the same approach for items, we can see that ProductVariant would contain the type within Items.

rest-orders/schema.graphql
type Order @keys(field: "id") {
id: ID!
buyer: User!
items: [ProductVariant!]!
}

If we tried to run this server, we would run into an issue because this isn't a valid . This is because we don't have a User or ProductVariant type defined within our schema.

To fix this, we need to define a stub type for User and ProductVariant, where we provide the keys as their attributes and mark them as resolvable: false:

type User @key(fields: "id", resolvable: false) {
id: ID!
}
type ProductVariant @key(fields: "id", resolvable: false) {
id: ID!
}
Task!

✏️ Datasources

When we develop a , a common pattern is to separate out service logic from the GraphQL logic. This allows us to create separation of concerns as resolver logic will be simpler to rationalize and service orchestration logic can abstract the underlying details that can be complex, such as batching and caching.

First open the directory: rest-orders/src/datasources/orders-api.js:

rest-orders/src/datasources/orders-api.js
const { RESTDataSource } = require("@apollo/datasource-rest");
class OrdersAPI extends RESTDataSource {
// @WORKSHOP 2.2.1: Apply the base URL here
baseURL = "";
async getOrder(id) {
// @WORKSHOP 2.2.2: Make HTTP Get call to endpoint
}
}

Within this file, you'll notice this import statement:

rest-orders/src/datasources/orders-api.js
const { RESTDataSource } = require("@apollo/datasource-rest");

This is importing a module by Apollo called RESTDataSource, a library implementation by Apollo that simplifies fetching data from REST APIs, while including common optimizations like caching and deduplications.

In this workshop, we've provided a hosted REST endpoint, https://rest-api-j3nprurqka-uc.a.run.app/api. We can from our browser to see what it returns. Let's try https://rest-api-j3nprurqka-uc.a.run.app/api/orders/1. Examining the JSON response, we notice that it contains the ID of the order, the customer ID and the product IDs.

{
"id": 1, # Order ID
"customerId": 10,
"variantIds": [1, 2, 3] # Product IDs
}

That's exactly what we need to allow the to resolve the customer and product details.

✏️ Let's start by overriding the baseURL to the Orders URI. (you can search "@WORKSHOP 2.2.1" comment), and then configure getOrder to make a HTTP GET request to the /orders/ endpoint.

baseURL = "https://rest-api-j3nprurqka-uc.a.run.app/api";

Next, let's use the built in HTTP GET Method from our REST Datasource Class, this.get, to call the endpoint passing along the parameter:

return this.get(`orders/${encodeURIComponent(id)}`);
rest-orders/src/datasources/orders-api.js
const { RESTDataSource } = require("@apollo/datasource-rest");
class OrdersAPI extends RESTDataSource {
baseURL = "https://rest-api-j3nprurqka-uc.a.run.app/api";
async getOrder(id) {
return this.get(`orders/\${encodeURIComponent(id)}`);
}
}

Typically, we would need to instantiate this class upon every request, however, a common pattern is to instantiate the service once, and pass it through something called a context object.

To do this, go to rest-orders/src/index.js and locate the @WORKSHOP: 2.2.3:

Here you'll see a method called, startStandaloneServer, which receives the server instance, and another parameter containing configuration information.

Within the configuration, we can pass along a context, which we can define a property for our Orders API service that we can reference throughout the application.

Typically, it's best practice to namespace our as "dataSource" and define the sources under it.

The final context should look like this:

rest-orders/src/index.js
const { url } = await startStandaloneServer(server, {
context: async ({ req }) => ({
dataSources: {
ordersAPI: new OrdersAPI(),
},
}),
listen: { port },
});

Now that we have an instance of the Orders service available in our context object, all that is left is to call our Orders API and fetch an order by its ID when a client requests it. This is mapped through our .

Resolvers

are how you inform the on how to populate the data for each or type in your schema – the orchestration logic.

No matter the programming language, all spec-complaint and frameworks provide the following four :

  • root (sometimes also referred to as Parent) — The data of the parent type of that
  • args — The parameters passed to the
  • context — A shared object that is passed through all the . Useful for sharing per- state (, caching, API keys, etc)
  • info — Additional information pertaining to the ( name, path, etc)

Now let's write our . You can follow along using the more in-depth or quick walkthrough below:

Task!

Now that we have finished building the Orders , let's run it locally and verify that it works successfully. In the rest-orders/ directory run the following commands to start the :

rest-orders/
npm install
npm start

Orders subgraph starting subgraph server

Once your terminal shows "Subgraph ready at http://localhost:4002/" we have successfully started our .

Let's validate that the resolver and datasource code is valid. Open http://localhost:4002/ on your browser and copy and execute the below with orderId set to 1.

Note: don't forget to enter your for orderId in the Variables panel. It should read { "orderId": 1 }

The should return a response with the order ID, if it returns null or an error, we have an issue with our or datasource code that we need to fix before deploying.

query Order($orderId: ID!) {
order(id: $orderId) {
id
}
}
http://localhost:4002

Orders subgraph starting subgraph server

If it returns the data we expect, exit the process and move on to the next step.

Task!

What about User and ProductVariant?

You may have noticed that the User and Product types are missing. As you probably discovered, that is correct!

It's important to remember that the Orders is not responsible for knowing how to call the underlying services for Users or ProductVariants, all it has available in its services is the ID of the User / ProductVariant in question.

That wraps up our for the Orders , if you are running into issues with your code, you can compare it against the final/rest-orders/ directory.

✏️ Deploy the Orders subgraph

To prepare for deployment of the orders and the , some environment variables need to be set. The following variables need to be pulled from GraphOS management plane.

IMPORTANT: The personal API key will not work here. You need to get a new service key!

  • APOLLO_KEY
  • APOLLO_GRAPH_REF

Let's get these values and note them down before continuing. On the Readme page, we can easily copy the graph ref by clicking on the Name in the title, as shown below:

https://studio.apollographql.com

Copy Graph Reference

To create an Apollo Key, go to Settings Tab > This Graph Tab > API Keys on the sidebar:

https://studio.apollographql.com

Create Apollo Key

From here, create a new API key and save it for reference during our deployment.

Now that we have the environment variables, we need to set them in our environment:

  1. In the main folder, rename example.env to .env
  2. Change the values for APOLLO_KEY and APOLLO_GRAPH_REF to the values you captured above.
APOLLO_KEY=service:key_here
APOLLO_GRAPH_REF=ref@variant

IMPORTANT NOTE: The APOLLO_KEY should be in the form of service:XXX:XXXX and NOT user:XXX:XXX

With our environment variables properly set, let's deploy the Orders .

We will use a shared Google Cloud environment to deploy our applications for this workshop. To prepare for deployment, we first have to authenticate using gcloud, the GCP command line tool.

At this point in the workshop, you should have received your GCP project id, service account email, and key. If not, your Apollo host should be able to provide it.

With that information, it's time to authenticate our service account.

gcloud auth activate-service-account <service account email> --key-file=<path to json key file> --project=<project_id>
https://studio.apollographql.com

Create Apollo Key

With that done, the build files need to be edited.

In your terminal, go to deploy/. From here, we need to modify orders.yaml to reflect the proper project name and unique service name. We need to find and replace two values in the orders.yaml file, you can refer to the table below:

Default ValueNew Value
federation-workshop<your-workshop-name> (This will be provided by Apollo)
orders-api<your-name-orders>

There are many ways to find and replace text in this file, one command-line option is sed. Here are the commands to modify the file using sed in unix.

deploy/
sed -i '' 's/federation-workshop/<your-workshop-name>/g' orders.yaml
sed -i '' 's/orders-api/<your-name>-orders/g' orders.yaml

With those changes saved, we need to deploy the Orders . To run the deploy, go back up the root directory, ../. From here, we want to kick off the build by running the following command:

In the root folder:
make deploy-orders
Task!

✏️ Publish the Orders API schema

Now that the is deployed, we need to publish this subgraph to the schema registry so it gets added to the . In order to do the publish, we will need the URL. To get the URL for the service we have just deployed, run the following command:

gcloud run services describe <your-name>-orders --region us-east1 --format 'value(status.url)'
Task!

Now that we have the URL, we can run the following command to publish the Orders to . Go to rest-orders/ again and run the following command:

rest-orders/
rover subgraph publish <GRAPH_REF> \
--schema ./schema.graphql \
--name orders \
--routing-url <ORDERS_SUBGRAPH_DOMAIN_URL>

Replace <GRAPH_REF> with your Apollo , which should be in the form of <your-graph-name>@current and replace the <ORDERS_SUBGRAPH_DOMAIN_URL> with the one you pulled from the gcloud run services describe command above.

You can head to Studio and check in the Subgraphs section to see the newly published Orders .

https://studio.apollographql.com

Studio Explorer Run query

However, we can't execute queries yet. If you try, Studio will prompt for a URL to . This is where Router comes in: it will be the entry point that receives a request, plans its execution, collects and returns the results. Therefore, our next step is to deploy it in our Google Cloud environment.

✏️ Deploy the Router

To begin, we need to edit the cloudbuild.yaml file to reflect the proper project id. Go to router/. From here, we need to modify cloudbuild.yaml to reflect the proper project name and unique service name. We need to find and replace two values in the cloudbuild.yaml file. We will replace two values:

Default ValueNew Value
federation-workshop<your-workshop-name> (This will be provided by Apollo)
router-api<your-name-router>

There are many ways to find and replace text in this file, one command-line option is sed. Here are the commands to modify the file using sed in unix:

In ./router/
sed -i '' 's/federation-workshop/<your-workshop-name>/g' cloudbuild.yaml
sed -i '' 's/router-api/<your-name>-router/g' cloudbuild.yaml

With the proper in place, it's time to deploy the . Go back up to the root folder (../) and run:

In the root folder:
make deploy-router
Task!

Once that command is finished, we once again need to get the URL for this endpoint we have just deployed. To do that run the following command:

gcloud run services describe <your-name>-router --region us-east1 --format 'value(status.url)'
Task!

Now that we have the URL, We need to add it into so Explorer can this endpoint.

In at the Readme page, your should say,

This graph doesn't have an endpoint yet - add one in Connection Settings

https://studio.apollographql.com

Apollo Studio - Connection Settings dialog

Click on "Connection Settings". In here, paste the domain that you received from GCP.

https://studio.apollographql.com

Create Apollo Key

Then click Save. Once you have saved your URL, you are ready to test out this in Explorer. Go to the Explorer tab and let's build a .

https://studio.apollographql.com

Studio Explorer Run query

In Explorer, let's run the following :

query ExampleQuery($userId: ID!) {
user(id: $userId) {
firstName
lastName
email
address
activeCart {
items {
parent {
name
}
}
}
orders {
items {
parent {
name
}
}
}
}
}

And in the tab, set the userId to 10.

{
"userId": "10"
}

This gives us a that will hit all three as it includes customer, order, and product information. Run this to see the results, that we successfully pull data from all three ! To see this visually, we can select the drop down menu in the response pane, and select Query Plan Preview:

https://studio.apollographql.com

Studio Query Plan

Task!

🎉 Congratulations! Your supergraph is working! 🎉

Up next

Now that we have a working , it's time to work on the front-end! In the next section, we'll see how to:

  • set up the Apollo Client in our React app
  • use Hooks to fetch data from our to get a list of Products and view the details of a Product
  • how to optimise queries using deferred responses
Previous