Overview
There's an important aspect of GraphQL we haven't yet covered in our journey through the connectors world: the relationships between types! Let's set our sights on bringing a listing's amenities into the equation.
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
A listing's amenities
In the listing details page, we're still missing a big chunk of data about a listing's amenities.
This data will be provided by the listings/:id/amenities
endpoint from our Listings REST API. We can examine the response from that endpoint here: https://rt-airlock-services-listing.herokuapp.com/listings/listing-9/amenities
Adding to the schema
To implement this in the schema, we'll first need to add a new object type called Amenity
. We'll give it three fields:
id
of typeID!
category
of typeString!
name
of typeString!
Open up the
listings.graphql
schema file and add theAmenity
type.Add the three new fields along with descriptions.
listings.graphqltype Amenity {id: ID!"The amenity category the amenity belongs to"category: String!"The amenity's name"name: String!}Next, we'll add the
amenities
field to theListing
type, 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! But don't worry, they should look pretty familiar...error[E029]: Encountered 4 build errors while trying to build a supergraph.Caused by:CONNECTORS_UNRESOLVED_FIELD: No connector resolves field `Listing.amenities`. It must have a `@connect` directive or appear in `@connect(selection:)`.CONNECTORS_UNRESOLVED_FIELD: No connector resolves field `Amenity.id`. It must have a `@connect` directive or appear in `@connect(selection:)`.CONNECTORS_UNRESOLVED_FIELD: No connector resolves field `Amenity.category`. It must have a `@connect` directive or appear in `@connect(selection:)`.CONNECTORS_UNRESOLVED_FIELD: No connector resolves field `Amenity.name`. It must have a `@connect` directive or appear in `@connect(selection:)`.
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!
In the schema, let's jump back to where we added the
amenities
field on theListing
type. We'll add the@connect
directive.listings.graphqltype Listing {# ...other Listing fieldsamenities: [Amenity!]!@connect(source: ""http: { GET: "" }selection: """""")}We can define the
source
the same as before:v1
.listings.graphqlsource: "v1"Next up,
http.GET
. We have an endpoint dedicated to retrieving a listing's amenities details, given a listing ID, so let's start with that!listings.graphqlhttp: { GET: "/listings/{???}/amenities" }
Now all we need is the listing's id
field to complete the endpoint path—but unlike the connector we defined for the listing(id: ID!)
field in the last lesson, we won't get the listing's id
on the field's arguments.
Instead, we'll need to access the id
on the actual Listing
type that we're resolving amenities
for! Let's look at how we do this.
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)"closed: Boolean"The amenities available for this listing"amenities: [Amenity!]!@connect(source: "v1"http: { GET: "/listings/{???}/amenities" } # ⬅️ Here's where we need it to end up!)}
Accessing an object type's fields
We can access an object's field values using the $this
variable.
type SpaceCat {id: ID!name: String!code: String!}
For example, in the schema above, we can access the object type's field values in a connector using $this.id
, $this.name
and $this.code
.
Just like before, we can wrap it in curly brackets { }
to interpolate it into the HTTP path in our connector.
Let's give it a spin!
Jumping back into the
listings.graphql
file where we left off with ouramenities
connector.listings.graphqlamenities: [Amenity!]!@connect(source: "v1"http: { GET: "/listings/{???}/amenities" }selection: """""")We can replace the
???
with$this.id
referring to theListing
type'sid
field.listings.graphqltype Listing {id: ID!# ... other Listing fieldsamenities: [Amenity!]!@connect(source: "v1"http: { GET: "/listings/{$this.id}/amenities" }selection: """""")Amazing! Last thing—we have to fill in our
selection
mapping. We can visit the endpoint to examine the response and determine that the mapping is one-to-one: the properties of the response are named the same way as the fields we've defined in the GraphQL schema.listings.graphqlselection: """idnamecategory"""
We're good to go!
Check your work
The exciting part! Let's jump back to http://localhost:4000 where the local router is running.
We'll build an operation to retrieve a specific listing, its id
, title
and a list of amenities. For each amenity, we want the id
, name
and category
.
query GetListingAmenities($listingId: ID!) {listing(id: $listingId) {idtitleamenities {idnamecategory}}}
Don't forget to define a listingId
in the Variables section:
{"listingId": "listing-9"}
Let's run the query... awesome! We've got the same listing from before, but this time, with amenity data!
Practice
Answer the next question using the schema below:
type Booking {id: ID!checkInDate: String!checkOutDate: String!status: BookingStatus!hostReview: Review@connect(source: "v1"http: { GET: "/review?bookingId={???}" }selection: """idtextrating""")}
???
in the schema?@connect
directive? (Select all that apply)Key takeaways
- When a field of one object type returns another object type, we can apply the
@connect
directive to define the specific method to retrieve that data from our source. - When a particular field's connector requires the value of a different field (on the same object type), we can access it within the connector using
$this
.
Up next
In the next lesson, we'll dive into the query plan and see how we can optimize our schema to make one less network call.
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.