10. Finishing up the subgraph


With our User, Host, and Guest types all squared away, let's make sure we have entry points into the accounts . These are the under Query and Mutation that are related to user accounts. In this lesson, we will:

  • Migrate over the user-related Query and Mutation to the accounts .
  • Move the relevant over to the accounts .
  • Wrap up the course!

✏️ Entry points to the accounts subgraph

There are under the root Query we'll want the accounts to handle: user and me.

To do this, we'll cut and paste those two definitions from the monolith , over to the accounts .

Note: If you chose to duplicate the subgraph-template files, you can also remove the placeholder example .

type Query {
user(id: ID!): User
"Currently logged-in user"
me: User!

You've read this instruction multiple times by now: make sure these are removed from the monolith . (Remember that leaving a defined in more than one subgraph without specifiying the @shareable can result in an INVALID_FIELD_SHARING error!)

type Query {
- user(id: ID!): User
- "Currently logged-in user"
- me: User!
# ... other query fields
Diagram of the types and fields that belong in the accounts subgraph. The types and fields for User, Host, Guest, and Query have been checked off to indicate that they are completed.

The last chunk left to move over to the accounts is the updateProfile and all its corresponding types and .

✏️ Moving over the updateProfile mutation

  1. Let's move the updateProfile to the accounts .

    type Mutation {
    "Updates the logged-in user's profile information"
    updateProfileInput: UpdateProfileInput
    ): UpdateProfileResponse!

    Remove the updateProfile from the Mutation type from the monolith . The rest of the can stay!

  2. The updateProfile references two other types: the UpdateProfileInput type and the UpdateProfileResponse type, so we'll need to move those over as well.

    input UpdateProfileInput {
    "The user's first and last name"
    name: String
    "The user's profile photo URL"
    profilePicture: String
    "The host's profile bio description, will be shown in the listing"
    profileDescription: String
    type UpdateProfileResponse implements MutationResponse {
    "Similar to HTTP status code, represents the status of the mutation"
    code: Int!
    "Indicates whether the mutation was successful"
    success: Boolean!
    "Human-readable message for the UI"
    message: String!
    "Updated user"
    user: User

    Make sure these types are removed from the monolith schema.

  3. One last thing! The UpdateProfileResponse type implements an interface: MutationResponse. We'll want to copy over that interface definition to the accounts . This interface still needs to be defined in the monolith because all the responses implement this interface.

    interface MutationResponse {
    "Similar to HTTP status code, represents the status of the mutation"
    code: Int!
    "Indicates whether the mutation was successful"
    success: Boolean!
    "Human-readable message for the UI"
    message: String!

    We can keep both MutationResponse interface definitions identical because both will need all of those .

✏️ The rest of the resolvers

Next, make sure that the accompanying for these are also moved over!

  • Query.user
  • Query.me
  • Mutation.updateProfile
const resolvers = {
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({
userInfo: updateProfileInput,
return {
code: 200,
success: true,
message: "Profile successfully updated!",
user: updatedUser,
} catch (err) {
return {
code: 400,
success: false,
message: err.message,
// more resolvers
User: {
// ...

Those three functions should be deleted from the monolith because they only belong in the accounts .


Publishing our subgraph schemas

We made schema changes to both , so let's make sure to publish them. You know the drill!

In a terminal window, from the root directory, run the following:

rover subgraph publish <APOLLO_GRAPH_REF> \
--schema ./monolith/schema.graphql \
--name monolith
rover subgraph publish <APOLLO_GRAPH_REF> \
--schema ./subgraph-accounts/schema.graphql \
--name accounts

Remember to replace the <APOLLO_GRAPH_REF> value with your own!

Testing our changes

Let's get back to testing the from last lesson. Head over to the Explorer in Apollo Studio and run the following:

query GetMyProfile {
me {

Make sure your Headers are set to:

Authorization: Bearer user-1

When you run the ... uh-oh! Just kidding, we get real data back, finally! 🥳

And if you don't, try refreshing the page and running the again - the might still be composing!

"data": {
"me": {
"id": "user-1",
"name": "Eves",
"profilePicture": "https://res.cloudinary.com/apollographql/image/upload/odyssey/airlock/user-1.png"

We're finally done with the accounts . We've made great progress, so take a moment to celebrate!

Diagram of the types and fields that belong in the accounts subgraph. All of the types and fields have been checked off to indicate the migration is complete.

Returning to the client

We should still have our client application running on port 3000. If not, navigate to the client directory and run npm start.

Returning to the app, however, we see an error on the homepage!

The homepage of Airlock's client app, displaying a failed to fetch error

When we check out the errors in the console, we'll find a message that might be familiar from the first course in this series.

Access to fetch at 'http://localhost:4000' from origin 'http://localhost:3000'
has been blocked by CORS policy

In Voyage I, we saw how to modify our 's config.yaml file to allow requests from a client to reach our . Let's add the CORS options to our config.yaml file now to permit our client app at http://localhost:3000 to talk to our .

CORS configuration

Open up config.yaml in the router directory. At the bottom of the file, we'll add the following configuration:

- propagate:
named: "Authorization"
all: true # Propagate errors from all subgraphs
- http://localhost:3000 # Allows any locally-running client to run against your Router
- https://studio.apollographql.com # Allows Apollo Studio to still run queries against your Router

These rules will continue to allow requests from Studio to reach our , and will now also permit requests from our frontend app.

Navigate to the terminal where your is currently running. Stop the server process, and then restart it. When we refresh http://localhost:3000, we'll see that we're getting data back!

The homepage of Airlock's client app, now displaying data about listings

Key takeaways

  • We don't need to transfer over all of the types and at once. We can work in small chunks at a time.
  • Splitting off incrementally enables teams to reap the benefits sooner.
  • The configuration file takes an option to define a CORS policy, which allows the origins you specify to communicate with your router.

Checking in with the Airlock team

Congratulations, we're on our way to a complete ! Remember our monolithic monster at the beginning of the course that teams were intimidated by and struggling with? Let's see what they have to say about it now:

  • 👩🏽‍🚀 The Accounts Team says:

    "It's great being able to focus on our own responsibilities! We're much faster at making changes and pushing updates compared to before. Excited for the new features incoming!"

  • 👩🏽‍🏫 The Listings Team says:

    "With the accounts completed, the monolith schema is a little more manageable. We're excited to get our team's domain responsibilities on our own subgraph too! The heavy lifting was already done for us with the setup and the first subgraph out of the way, so we can use that as a blueprint to get our own listings started."

An illustration of the monolith monster transforming into a supergraph.

Amazing, we've made big leaps and improvements to the developer experience that will set us up for great things to come in Airlock's future!


In this course, we learned how to convert a monolith app into a , using the Strangler Fig approach. We first converted the original graph into one large , which we published to the schema registry. Then, we installed and ran a using the same port as the original graph, so that the client wouldn't need any changes.

We started with the accounts , learning how to handle interfaces as value types and moving the necessary types, , , and over.

We've still got four more to go before our is complete, and you have all the tools you need to tackle those! If you'd like to get more (optional) practice with splitting off subgraphs from a monolith, head on over to Voyage II Lab (coming soon!) to continue the journey.

In the next course of the Voyage series, we'll take a look at how to bring Airlock to production with , and how to use Apollo Studio features like , , and observability. See you in Voyage III: Federation in Production!


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.