Overview
In this section, we'll cover how to add the Orders REST API to our Supergraph without using a subgraph.
Prerequisites
- Our supergraph running in the cloud
Apollo Connectors for REST APIs
Connectors are a new declarative programming model for GraphQL, allowing you to plug your existing REST services directly into your graph. Once integrated, client developers gain all the benefits of GraphQL, and API owners gain all the benefits of GraphOS, including incorporation into a supergraph for a comprehensive, unified view of your organization's data and services.
For more information, refer to the Apollo Connectors documentation.
Apollo Connectors: KBT Threads and the Orders API
Because our Orders API is a REST-based service, is it a great candidate for Apollo Connectors, but it's always best to check whether an API is a good fit or not.
Using Apollo Connectors with the Orders API
Let's revisit the Orders
schema once again. If you need to reset it, you can copy the schema from the code below (comments have been removed for brevity). We have also intentionally left the Authentication and Authorization directives in place.
extend schema@link(url: "https://specs.apollo.dev/federation/v2.8", import:["@key","@tag","@shareable""@authenticated","@requiresScopes",])type Query {order(id: ID!): Order @authenticated}type Order @key(fields: "id") {id: ID!buyer: User! @requiresScopes(scopes: [["order:buyer"]])items: [ProductVariant!]! @requiresScopes(scopes: [["order:items"]])}type User @key(fields: "id") {id: ID!}type ProductVariant @key(fields: "id", resolvable: false) {id: ID!}
Using the Proposals editor to change the Orders schema
The latest release of the Proposals editor composes your supergraph in real-time as you make changes to your schema, making it ideal for developing in a safe environment. For this lab, we will use this editor to make changes to the orders.graphql
file, which will then be committed to the repository.
Note: if you don't want to use the Proposals editor, you can make changes directly in the Github editor.
1. ✏️ Create a new proposal
Start by creating a new proposal and give it a name, such as "Connect the Orders REST API to the supergraph":
Then, choose the current
variant that was created for your account. You can optionally enter a description. Finally click on the Create Proposal
button.
Once the Proposal editor is open, select the orders
on the right-hand side to get started:
2. ✏️ Make sure "Compose on Change" is enabled in the editor
This feature allows you to see how your changes will affect the supergraph, and avoid any potential issues. To enable this feature, simply check the "Compose on Change" box in the editor toolbar, as shown below:
3. ✏️ Federation version and directives for Apollo Connectors
With the editor open, let's start by changing the Federation version - which is currently set to 2.8
- to 2.10
. This will allow us to use the latest features of Apollo Federation. Update the @link
directive as follows:
-@link(url: "https://specs.apollo.dev/federation/v2.8", import:+@link(url: "https://specs.apollo.dev/federation/v2.10", import:
Next, in order to be able to make use of the Apollo Connector directives, we need to import them using a new @link
directive. Add the following highlighted code directly below the first @link
directive, as shown below:
extend schema@link(url: "https://specs.apollo.dev/federation/v2.10"import: ["@key", "@tag", "@shareable", "@authenticated", "@requiresScopes"])@link(url: "https://specs.apollo.dev/connect/v0.1"import: ["@connect", "@source"])
Heads up! As soon as you add the new @link
directive, the editor will automatically start to compose the supergraph, and this will result in a lot of errors! Don't worry though, we will fix them in the next steps.
As you can see from the directive above, we can now make use of the @connect
and @source
directives. These directives are used to define the connection between the schema and the REST API. Check the Apollo Connectors documentation for more information on these directives.
4. Add the @source
directive to the schema
The Apollo Connector directives allow us to define the connection between the schema and the REST API. The @source
directive is used to define the REST API endpoint that the schema will connect to - so let's make use of it.
Add the @source
directive directly below our last @link
directive as shown below:
# ...@link(url: "https://specs.apollo.dev/connect/v0.1"import: ["@connect", "@source"])@source(name: "api"http: { baseURL: "https://rest-api-j3nprurqka-uc.a.run.app/api" })
If you'd like to check the REST API endpoint, you can use the following URL: https://rest-api-j3nprurqka-uc.a.run.app/api/orders/1
. Opening this URL in your browser should return a JSON response with the order details:
{"id": 1,"customerId": 10,"variantIds": ["27", "11", "347"]}
Now that we have a source API defined and we know the result of invoking our REST API, we are ready to start mapping our schema to the REST API.
5. Add the @connect
directive to the schema
We will add the @connect
directive right after the @authenticated
directive in our order(id: ID!)
operation. Note that as soon as you type @conn
the editor will suggest to autocomplete it for you - just press Enter
to accept the suggestion. You can also press Ctrl + Space
to see the available suggestions.
The editor will place the caret in the source: ""
field. This is where you will define the connection between the schema and the source REST API. The source
field should match the name
field in the @source
directive, which is "api"
in this case. Go ahead and set the source to "api"
.
Hint: you can use ➡️ TAB
to cycle between the various fields that need to be filled in the directive.
Next, we'll define what type of verb to use for our REST endpoint - in this case, it's a GET
operation. Pressing TAB
will take us to the next field, the relative path.
Let's take a look once again at the test URL: https://rest-api-j3nprurqka-uc.a.run.app/api/orders/1
- we have set the base URL to
https://rest-api-j3nprurqka-uc.a.run.app/api
in our@source
directive, therefore - the relative path for this operation is
/orders/1
After making these changes, our operation should look like this:
type Query {order(id: ID!): Order@authenticated@connect(source: "api"http: { GET: "/orders/{$args.id}" }selection: """""")}
6. Define the selection set
The selection
field is where we map the fields returned by the REST API to our GraphQL schema. From our JSON response above, we know that the API returns id
, customerId
, and variantIds
. In turn, our GraphQL schema defines that the Order
type has three fields: id
of type String
, buyer
of type User
, and items
of type ProductVariant
.
Mapping the id
field is quite straightforward since we have the exact same field. Let's start by adding the id
field to our selection
set. Add the id
field directly into the selection
field as shown below:
type Query {order(id: ID!): Order@authenticated@connect(source: "api"http: { GET: "/orders/{$args.id}" }selection: """id""")}
Note: immediately after adding the id
field, the buyer
and items
fields will display red squiggly lines beneath them. This occurs because we have not yet defined these fields in the selection set:
What about buyer
and items
then? In any REST scenario, this would involve making additional requests to the API to retrieve the User
and ProductVariant
details. However, thanks to Federation and the Apollo Router, all we need to do is return the ID
s of both the User
and ProductVariant
, and the Apollo Router will handle the rest. It does that by using the @key
directive on the Entity types to resolve the ID
s to the actual objects using the other subgraphs in a supergraph.
From the above, for the Apollo Router to resolve an Entity using its ID
we need to return a value of the form:
{ "id": "the-id-of-the-entity" }
For more information on Apollo Connectors and Federation, check the Reference entities section of the Apollo Connectors documentation.
Let's start by asking the Apollo Router to resolve the buyer
. To do this, we need to add this line in the selection set:
type Query {order(id: ID!): Order@authenticated@connect(source: "api"http: { GET: "/orders/{$args.id}" }selection: """idbuyer: { id: $.customerId }""")}
Note: the $
is a special symbol used to hold the value enclosed by the parent {...}
selection, or the root value at the top level. See the Variables section in the Apollo Connectors documentation for more details.
The { ... }
syntax allows us to create a new object in the selection set. In this case, we are creating a new object with the id
field set to the customerId
field returned by the REST API.
Conversely, the items
field is an array of ProductVariant
entities. We can use the same syntax to create an array of objects in the selection set. The variantIds
field returned by the REST API is an array of String
values, so in this case we can use a special syntax that allows to map these values as an array of key-value pairs.
Let's map the items
in our order to the JSON payload in the selection set:
type Query {order(id: ID!): Order@authenticated@connect(source: "api"http: { GET: "/orders/{$args.id}" }selection: """idbuyer: { id: customerId }items: $.variantIds { id: $ }""")}
The $.variantIds { ... }
syntax allows us to iterate over the variantIds
array and return an array of objects with the id
field set to the value of each element in the array. See the Wrapping fields section for more details on this syntax.
Panic!!😱😱 Composition is still returning an error!
Yes indeed - we are still missing one crucial piece of information! In our schema design, the order(id: ID!)
operation is designed also as an Entity resolver! Typically with a subgraph, you would create a special method to resolve this, in the form of __resolveReference
. However, with Apollo Connectors, we can mark our operation as an entity resolver by adding the entity: true
at the end of our @connect
directive, like so:
type Query {order(id: ID!): Order@authenticated@connect(source: "api"http: { GET: "/orders/{$args.id}" }selection: """idbuyer: { id: $.customerId }items: $.variantIds { id: $ }"""entity: true)}
When entity: true
is set on a connector, its field provides an entity resolver for query planning. Check the rules for entity: true
in the Apollo Connectors documentation.
And that's it! 🎉🎉🎉 Check the composition status in your editor. If there are no errors, we should be ready to go:
Now copy the contents of the Proposals editor and head to the Github page. Open the orders-schema.graphql
file and paste the contents of the Proposals editor into the file. Once you have pasted the contents, commit the changes.
Once the file has been commmited, the changes will be automatically applied to the supergraph. You can check the status of the publish job in the Actions
tab of the repository. If the workflow workflow has completed successfully, we can now head over to Apollo Studio to test our new schema.
Check your work: testing the new schema
Let's make a request to retrieve an order's buyer and items.
Create a new tab and copy the operation below:
query order($orderId: ID!) {order(id: $orderId) {idbuyer {emailfirstName}items {pricesizecolorwayid}}}In the Variables panel, copy and paste the following JSON payload:
{ "orderId": "1" }Add an the
Authorization
header (if not present already) and set the value toBearer {{authorizedToken}}
.
Bearer {{authorizedToken}}
Send the request - we should see that data has successfully been returned!
But wait, how do we actually know that the data is coming from the REST API and not the Order
subgraph? 🤔
Head over to Subgraphs in Apollo Studio. You should see that the entry for Order
is different: this is because we have used Apollo Connectors to connect the Order
schema to the REST API directly:
Conclusion
🎉 Congratulations on completing this lab! 🎉 By using Apollo Connectors you have now created a server-free implementation of a GraphQL subgraph - still part of a Supergraph, saving resources by avoiding additional deployments and development time.
With Apollo Connectors,
- you no longer need a specific service and resolver code to integrate REST services into your graph. This simplifies integrating the many GraphQL APIs that are nothing more than passthrough services to REST APIs.
- you can leverage the power of Apollo Federation to efficiently orchestrate calls to multiple services and compose the results into a single response.
- you can define your REST integration in your schema and let the GraphOS Router handle the rest.
Workshop conclusion
Congratulations on successfully completing this workshop! 🎉🎉🎉
Throughout this journey, we have now added:
- subscriptions,
- authentication and authorization,
- a coprocessor,
- operation limits,
- persisted queries,
- safelisting, and
- Apollo Connectors!
We did all of this with either schema changes or yaml
configuration. With those simple changes, we were able to create a graph that serves more use cases and does so in a more secure way.
Beyond that, we have also begun the process of detailing how we can manage schema changes for a supergraph across multiple stakeholders to ensure our graph remains highly available.
KBT Threads can now confidently deploy their supergraph to support their new omni-channel presence!