9. Reference resolvers
10m

Overview

We added the User interface as well as the Host and Guest entities to the accounts , but they need accompanying ! In this lesson, we will:

  • Implement the __resolveReference functions for the entities
  • Migrate over the interface's __resolveType
  • Copy over the relevant s
  • Return a representation of the from the monolith

✏️ Resolving entities

As we covered in Voyage I, that contribute to an use a special called a reference resolver to resolve entities. This function enables the to directly access that each contributes.

We'll implement reference for the Host and Guest entities in the accounts .

  1. Open up the resolvers.js file in the accounts , and delete the default that came with the template, along with its example function.

    const resolvers = {
    - Query: {
    - example: () => 'Hello World!',
    - },
    };
  2. Inside the resolvers object, add a reference for the Host type:

    subgraph-accounts/resolvers.js
    Host: {
    __resolveReference: (user, { dataSources }) => {
    return dataSources.accountsAPI.getUser(user.id);
    },
    },

    We're using the accountsAPI 's getUser method and passing it the id of the user to retrieve that user's information.

    We haven't connected the accountsAPI yet, but we will later on in the lesson!

  3. Let's add a similar reference for the Guest type.

    subgraph-accounts/resolvers.js
    Guest: {
    __resolveReference: (user, { dataSources }) => {
    return dataSources.accountsAPI.getUser(user.id);
    },
    },
  4. The full resolvers map should now look like this:

    subgraph-accounts/resolvers.js
    const resolvers = {
    Host: {
    __resolveReference: (user, { dataSources }) => {
    return dataSources.accountsAPI.getUser(user.id);
    },
    },
    Guest: {
    __resolveReference: (user, { dataSources }) => {
    return dataSources.accountsAPI.getUser(user.id);
    },
    },
    };

Perfect, the entities have their reference ! These resolvers are used when the sends this an entity representation. Now, let's jump over to the monolith and check out where we can return representations of these entities.

Returning entity representations

There are a few in the monolith that will need to reference the Host and Guest entities. For these , we're going to jump to their corresponding and have them return an representation.

If you recall from Voyage I, an representation is an object that the uses to represent a specific entity from another . It contains a __typename property and values for the 's primary key . In our case, the primary key field for both Host and Guest is id.

We'll return an representation for each the following in the monolith :

  • Listing.host, which returns a Host type
  • Booking.guest, which returns a Guest type
  • Review.author, which returns a User type

To get started, open the resolvers.js file in the monolith directory.

✏️ Listing.host

Let's find the Listing.host . We'll remove the body of the function, where it currently calls the accountsAPI . We now want this to return a representation of the Host .

monolith/resolvers.js
Listing: {
host: ({ hostId }) => {
return { id: hostId };
},
}

Here we've destructured the parent (which is a Listing object) to retrieve the hostId property. This hostId is used as the value for the primary key id.

Note: We don't need to include the __typename in this representation. The automatically knows how to get the __typename (because __typename is a meta- that's included on all objects, per the specification).

✏️ Booking.guest

Let's do the same for the Booking.guest . Find the resolver in the resolvers.js file and replace the body of the function so that it returns an object as the representation.

monolith/resolvers.js
Booking: {
// other Booking resolvers
guest: ({ guestId }) => {
return { id: guestId };
},
}

Similar to the Listing.host , here we've destructured the parent (a Booking object) to retrieve the guestId property. This guestId is used as the value for the primary key id.

✏️ Review.author

Finally, we have the Review.author , which we'll handle slightly differently.

In the schema, the Review.author returns a User interface, which is an abstract type. So the Review.author will need to specify whether the returned type will be a Host or a Guest, and include it as the __typename in its representation.

To determine the type, we'll need to look at business-domain-specific logic. We know that only guests can write reviews for listings and hosts. Similarly, only hosts can write reviews for guests. Knowing that, we can investigate the shape of a Review object and determine what property might be able to help us with that logic. Here's what a review looks like:

{
"id": "review-1",
"authorId": "user-2",
"targetId": "listing-1",
"bookingId": "booking-1",
"targetType": "LISTING",
"text": "Wow, what an experience! I've never stayed in a cave before, so I was a little unprepared. Luckily, this listing had all the amenities I needed to feel safe and prepared for anything.",
"rating": 4
}

We can use the review's targetType property (which will either be LISTING, GUEST or HOST) to determine what the review author's type is. If a user is writing a review about a Listing or a Host, we can safely assume that user is a Guest!

Find the Review.author in the resolvers.js file and replace it with the following:

monolith/resolvers.js
Review: {
author: (review) => {
let role = '';
if (review.targetType === 'LISTING' || review.targetType === 'HOST') {
role = 'Guest';
} else {
role = 'Host';
}
return { __typename: role, id: review.authorId };
},
},

Note: We've renamed the first of the (parent) to review to clarify the 's type.

And that's it for all the that return representations of entities! There's just one more resolver we need: __resolveType.

Resolving the interface

We need a resolveType for the User interface! We actually already have one defined in the monolith , so all we need to do is move that function over to the accounts . This should only belong in the accounts , so delete it from the monolith .

subgraph-accounts/resolvers.js
User: {
__resolveType(user) {
return user.role;
}
},

Note: Make sure you delete the __resolveType function from the monolith .

monolith/resolvers.js
- User: {
- __resolveType(user) {
- return user.role;
- }
- },

That's all the we need for our to handle our schema changes!

Creating data sources

Currently, our accounts doesn't have any connected yet. However, we know that it needs the accountsAPI , because we've been using it in our . Let's tackle that next.

  1. Let's look at the datasources/accounts.js file in the monolith directory. This is the we want to use in the accounts !

  2. Copy the datasources/accounts.js file and paste it into the subgraph-accounts/datasources directory. This lets us access the in the accounts !

    You can also delete the placeholder datasources.js file in the subgraph-accounts/datasources directory while you're here.

  3. Open up subgraph-accounts/index.js and add the import at the top:

    subgraph-accounts/index.js
    const AccountsAPI = require("./datasources/accounts");
  4. Then, find the line where we return the contextValue object for to use. We'll set up the AccountsAPI inside the dataSources object.

    subgraph-accounts/index.js
    return {
    ...userInfo,
    dataSources: {
    accountsAPI: new AccountsAPI(),
    },
    };
  5. Let's not forget to incude the cache when we initialize the AccountsAPI since it's a RESTDataSource class. We'll destructure the cache property from server just before we return the full contextValue object.

    subgraph-accounts/index.js
    const { cache } = server;
    return {
    ...userInfo,
    dataSources: {
    accountsAPI: new AccountsAPI({ cache }),
    },
    };

Testing our changes

Let's test whether our reference our working! Try out this in the Explorer:

query GetListing {
listing(id: "listing-1") {
host {
name
profilePicture
profileDescription
}
reviews {
rating
author {
id
name
}
}
}
}

This retrieves a listing's host information (testing out our Listing.host ) and a listing's reviews. For each review, it asks for the review's rating, author's id and name (testing out our Review.author ).

When you run the query, you should get back real data! Awesome! 🎉

One more thing, let's try out the from last lesson:

query GetMyProfile {
me {
id
name
profilePicture
}
}

Make sure your Headers are set to:

Authorization: Bearer user-1

When you run the ... uh-oh! We still get an error, but it's a new one: Abstract type "User" must resolve to an Object type at runtime for field "Query.me".

Hmm, you might be thinking: "Didn't we implement a __resolveType resolver to take care of this?"

Well, we did, but that belonged to the accounts . And right now, the me in our GetMyProfile above still belongs to the monolith ! The is asking the monolith to resolve the , when really it should be asking the accounts .

We'll fix this in the next lesson.

Practice

Use this schema for the following code challenge:

interface Book {
isbn: ID!
title: String!
genre: String!
}
type PictureBook implements Book @key(fields: "isbn") {
isbn: ID!
title: String!
genre: String!
numberOfPictures: Int
isInColor: Boolean
}
type YoungAdultNovel implements Book {
isbn: ID!
title: String!
genre: String!
wordCount: Int
numberOfChapters: Int
}
type LibraryMember @key(fields: "id") {
id: ID! @external
faveBook: Book!
}
Code Challenge!

Write a resolver for LibraryMember.faveBook to return a representation of the entity. You can use the book's hasPictures property to determine if it's a PictureBook or a YoungAdultNovel.

Key takeaways

  • Any that contributes to an needs to define a __resolveReference function for that .
  • An representation is an object that includes the entity's __typename and @key .
  • An interface needs a __resolveType .

Up next

Let's finish up the accounts by moving over the remaining Query and Mutation it's responsible for.

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.