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 object type's fields
- Learn how to access an object type's fields to use inside a connector using
$this
A listing's amenities
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.
[{"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 router takes care of the orchestration for them!
Adding to the schema
Let's get to it!
Back in our schema, we'll add a new object type called
Amenity
. We'll give it those three fields, using the same names as in the response object, with descriptions for each.listings.graphqltype Amenity {id: ID!"The amenity category the amenity belongs to"category: String!"The amenity's name"name: String!}A listing has amenities, so we'll define that relationship by adding a new field in the
Listing
type calledamenities
, which will return a list ofAmenity
types.listings.graphqltype Listing {# ...other Listing fields"The amenities available for this listing"amenities: [Amenity!]!}When we save our changes,
rover dev
will restart automatically. And of course, we get errors.Rover errorserror[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 field on an object type. In this case, the Listing.amenities
field!
Let's add the
@connect
directive and hit Enter to auto-complete our parameters.listings.graphqltype Listing {# ...other Listing fieldsamenities: [Amenity!]!@connect(source: ""http: { GET: "" }selection: """""")}source
stays the same as always.listings.graphqlsource: "listings"Next up, the endpoint:
listings/:id/amenities
.listings.graphqlhttp: { GET: "/listings/:id???/amenities" }But
id
needs to be a dynamic value. Specifically, it needs to be the value of this particularListing
'sid
field.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 fields, which means they share the same parent object type: Listing
. To access that parent object type, we use a variable called $this
.
Accessing an object type's fields in connectors
In this schema, for example:
type SpaceCat {id: ID!name: String!code: String!}
We can access all of the object's fields using $this.id
, $this.name
and $this.code
.
And just like before, we can wrap the field we want in curly braces ({ }
) to interpolate it into the path in our connector.
Let's give it a spin!
Using $this
Back where we left off with our
amenities
connector.listings.graphqlamenities: [Amenity!]!@connect(source: "listings"http: { GET: "/listings/:id???/amenities" } # ⬅️ Here's where we need to replace :id???selection: """""")We can use
$this.id
, to refer to theListing
type'sid
field. Remember, keep the dollar sign inside the curly braces when we're interpolating it into the path.listings.graphqltype Listing {id: ID!# ... other Listing fieldsamenities: [Amenity!]!@connect(source: "listings"http: { GET: "/listings/{$this.id}/amenities" }selection: """""")}Last thing—we have to fill in our
selection
mapping. We decided to name our fields the same as the response properties, so the mapping here should be one-to-one.listings.graphqlselection: """idnamecategory"""
Checking our work
Let's save our changes and jump back to Sandbox.
Let's add on to this operation to retrieve a specific listing, with the new amenities
field. For each amenity, we want all the fields available. We'll rename this operation: GetListingWithAmenities
.
query GetListingWithAmenities($listingId: ID!) {listing(id: $listingId) {idtitlenumOfBedscostPerNightclosedForBookingamenities {idcategoryname}}}
Don't forget we still need a listingId
in the Variables section.
{"listingId": "listing-1"}
Go ahead and run it... nice! We've got the same listing from before, but this time, with its amenities!
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 router 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 API orchestration will be handled smoothly by the router.
Practice
Answer the next question using the schema below:
type Booking {id: ID!checkInDate: String!checkOutDate: String!status: BookingStatus!hostReview: Review@connect(source: "listings"http: { GET: "/review?bookingId={???}" }selection: """idtextrating""")}
???
in the schema?@connect
directive? (Select all that apply)Key takeaways
- A single client request can retrieve data from multiple endpoints with the use of Apollo Connectors.
- The
@connect
directive can be applied to fields on root types or object types. - To access an object type's fields 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 entities.
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.