10. Implementing 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:

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

Creating data sources

Currently, our accounts doesn't have any connected yet. However, we know that it needs the accountsAPI to get access to user and account information.

  1. Let's look at the datasources/accounts.js file in the monolith directory. Copy the 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.

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

    subgraph-accounts/index.js
    const AccountsAPI = require("./datasources/accounts");
  3. 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(),
    },
    };
  4. Let's not forget to include the cache when we initialize the AccountsAPI, enabling the RESTDataSource cache.

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

Great, we can now use this in our !

Resolving the Query and Mutation fields

Let's first tackle the for our root .

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

    subgraph-accounts/resolvers.js
    const resolvers = {
    - Query: {
    - _todo: () => "TODO",
    - },
    };
  2. Find the for Query.user, Query.me and Mutation.updateProfile in the monolith/resolvers.js file (or you can copy them below). We'll paste these into the resolvers map in the accounts .

    subgraph-accounts/resolvers.js
    Query: {
    user: async (_, { id }, { dataSources }) => {
    const user = await dataSources.accountsAPI.getUser(id);
    if (!user) {
    throw new Error("No user found for this Id");
    }
    return user;
    },
    me: async (_, __, { dataSources, userId }) => {
    if (!userId) throw new AuthenticationError(authErrMessage);
    const user = await dataSources.accountsAPI.getUser(userId);
    return user;
    },
    },
    Mutation: {
    updateProfile: async (
    _,
    { updateProfileInput },
    { dataSources, userId }
    ) => {
    if (!userId) throw new AuthenticationError(authErrMessage);
    try {
    const updatedUser = await dataSources.accountsAPI.updateUser({
    userId,
    userInfo: updateProfileInput,
    });
    return {
    code: 200,
    success: true,
    message: "Profile successfully updated!",
    user: updatedUser,
    };
    } catch (err) {
    return {
    code: 400,
    success: false,
    message: err.message,
    };
    }
    },
    },

Resolving the interface

Next let's tackle the User interface, which needs a resolveType. We actually already have one defined in the monolith , so all we need to do is copy that function over to the accounts .

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

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. 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.

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

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

Perfect, the entities have their reference !

We've covered all of our accounts schema with . One last thing before we test out our changes...

The monolith subgraph

In a previous lesson, we mentioned that we could include other in the accounts , specifically: Listing.host, Booking.guest and Review.author. We're going to keep these in the monolith schema for now (eventually they'll migrate their way over to listings, bookings and reviews respectively), but we can update the .

Currently, the for each of these uses the Accounts API. For example:

monolith/resolvers.js
Listing: {
host: ({ hostId }, _, { dataSources }) => {
return dataSources.accountsAPI.getUser(hostId);
},
// other resolvers
}

We want to start removing this dependency on the Accounts service (after all, that's what the accounts is for!). So we'll remove these calls to the accountsAPI and return representations instead.

Returning entity representations

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 up 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, role };
},
},

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!

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. The underlying object for each book contains a hasPictures property, which you can use 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

Whew, that's it for ! In the next lesson, we'll test that our changes are working locally before publishing the new accounts in !

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.