8. Orchestration in action
4m

Overview

There's a big chunk of data missing: a listing's amenities.

In this lesson, we will:

  • Learn how to use connectors on an 's s
  • Learn how to access an 's to use inside a connector using $this

A listing's amenities

Get listing amenities mockup

For the listing's amenities, we can use the GET /listings/:id/amenities endpoint: https://airlock-listings.demo-api.apollo.dev/listings/listing-1/amenities.

This endpoint returns an array of amenity objects, each with its own id, category and name.

https://airlock-listings.demo-api.apollo.dev/listings/listing-1/amenities

Get listing amenities endpoint

REST API response
[
{
"id": "am-2",
"category": "Accommodation Details",
"name": "Towel"
},
{
"id": "am-10",
"category": "Space Survival",
"name": "Oxygen"
},
{
"id": "am-11",
"category": "Space Survival",
"name": "Prepackaged meals"
}
// more amenities
]

We're introducing a new endpoint here, to supplement the data we need for the same page. Normally, clients would have to manage that orchestration themselves, making one call for the listing details, another for amenities, and stitching that all together.

With connectors, the client can make one request, asking for everything they need, and the takes care of the orchestration for them!

Adding to the schema

Let's get to it!

  1. Back in our schema, we'll add a new called Amenity. We'll give it those three , using the same names as in the response object, with descriptions for each.

    listings.graphql
    type Amenity {
    id: ID!
    "The amenity category the amenity belongs to"
    category: String!
    "The amenity's name"
    name: String!
    }
  2. A listing has amenities, so we'll define that relationship by adding a new in the Listing type called amenities, which will return a list of Amenity types.

    listings.graphql
    type Listing {
    # ...other Listing fields
    "The amenities available for this listing"
    amenities: [Amenity!]!
    }
  3. When we save our changes, rover dev will restart automatically. And of course, we get errors.

    Rover errors
    error[E029]: Encountered 4 build errors while trying to build a supergraph.
    Caused by:
    CONNECTORS_UNRESOLVED_FIELD: [listings] No connector resolves field `Listing.amenities`.
    It must have a `@connect` directive or appear in `@connect(selection:)`.
    CONNECTORS_UNRESOLVED_FIELD: [listings] No connector resolves field `Amenity.id`.
    It must have a `@connect` directive or appear in `@connect(selection:)`.
    CONNECTORS_UNRESOLVED_FIELD: [listings] No connector resolves field `Amenity.category`.
    It must have a `@connect` directive or appear in `@connect(selection:)`.
    CONNECTORS_UNRESOLVED_FIELD: [listings] No connector resolves field `Amenity.name`.
    It must have a `@connect` directive or appear in `@connect(selection:)`.

But don't worry, we've got this by now–it's connector time!

Adding a connector on an object type's fields

This time, we're not adding a connector to a root type like Query. Instead, we'll add it to a on an . In this case, the Listing.amenities !

  1. Let's add the @connect and hit Enter to auto-complete our parameters.

    listings.graphql
    type Listing {
    # ...other Listing fields
    amenities: [Amenity!]!
    @connect(
    source: ""
    http: { GET: "" }
    selection: """
    """
    )
    }
  2. source stays the same as always.

    listings.graphql
    source: "listings"
  3. Next up, the endpoint: listings/:id/amenities.

    listings.graphql
    http: { GET: "/listings/:id???/amenities" }
  4. But id needs to be a dynamic value. Specifically, it needs to be the value of this particular Listing's id .

    type Listing {
    id: ID! # ⬅️ Here's the value we actually need!
    "The listing's title"
    title: String!
    "The number of beds available"
    numOfBeds: Int
    "The cost per night"
    costPerNight: Float
    "Indicates whether listing is closed for bookings (on hiatus)"
    closedForBooking: Boolean
    "The amenities available for this listing"
    amenities: [Amenity!]!
    @connect(
    source: "listings"
    http: { GET: "/listings/:id???/amenities" } # ⬅️ Here's where we need it to end up!
    )
    }

Here, amenities and id are considered sibling , which means they share the same parent : Listing. To access that parent , we use a called $this.

Accessing an object type's fields in connectors

In this schema, for example:

Example schema
type SpaceCat {
id: ID!
name: String!
code: String!
}

We can access all of the object's using $this.id, $this.name and $this.code.

Syntaxt for $this

And just like before, we can wrap the we want in curly braces ({ }) to interpolate it into the path in our connector.

Let's give it a spin!

Using $this

  1. Back where we left off with our amenities connector.

    listings.graphql
    amenities: [Amenity!]!
    @connect(
    source: "listings"
    http: { GET: "/listings/:id???/amenities" } # ⬅️ Here's where we need to replace :id???
    selection: """
    """
    )
  2. We can use $this.id, to refer to the Listing type's id . Remember, keep the dollar sign inside the curly braces when we're interpolating it into the path.

    listings.graphql
    type Listing {
    id: ID!
    # ... other Listing fields
    amenities: [Amenity!]!
    @connect(
    source: "listings"
    http: { GET: "/listings/{$this.id}/amenities" }
    selection: """
    """
    )
    }
  3. Last thing—we have to fill in our selection mapping. We decided to name our the same as the response properties, so the mapping here should be one-to-one.

    listings.graphql
    selection: """
    id
    name
    category
    """

Checking our work

Let's save our changes and jump back to Sandbox.

Let's add on to this to retrieve a specific listing, with the new amenities . For each amenity, we want all the fields available. We'll rename this : GetListingWithAmenities.

GetListingWithAmenities operation
query GetListingWithAmenities($listingId: ID!) {
listing(id: $listingId) {
id
title
numOfBeds
costPerNight
closedForBooking
amenities {
id
category
name
}
}
}

Don't forget we still need a listingId in the Variables section.

Variables
{
"listingId": "listing-1"
}

Go ahead and run it... nice! We've got the same listing from before, but this time, with its amenities!

http://localhost:4000

GetListingWithAmenities response

Task!

Orchestration in action

This is one request from the client, but with data coming from more than one endpoint! And it's all handled on the side, so our clients don't need to worry about managing multiple API calls.

We've started small, using only 2 endpoints, but imagine when we start to add more features to the listing details page, like host and guest information, ratings, reviews, bookings, activities.

More domains, more connectors to build, and all that will be handled smoothly by the .

Practice

Answer the next question using the schema below:

Example schema
type Booking {
id: ID!
checkInDate: String!
checkOutDate: String!
status: BookingStatus!
hostReview: Review
@connect(
source: "listings"
http: { GET: "/review?bookingId={???}" }
selection: """
id
text
rating
"""
)
}
Which of the following should replace ??? in the schema?
Which of the following are valid locations to apply the @connect directive? (Select all that apply)

Key takeaways

  • A single client request can retrieve data from multiple endpoints with the use of .
  • The @connect can be applied to on root types or .
  • To access an 's from within its connector, we can use $this.

Up next

In the next lesson, we'll continue to expand our schema and tap into the power of .

Previous

Share your questions and comments about this lesson

Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.