Propagate errors to parent resolver in GraphQL

31 Views Asked by At

Hi everyone I have an issue in my schema + resolver implementation. The as is situation is:

A schema like this

"User Response either success or failure"
union UserResponse = UserSuccessfulResponse | GenericResponse

type UserSuccessfulResponse {
  "Set true false for the response of the API"
  success: Boolean!
  "User Info"
  userInfo: User
  "Currency Info"
  currency: Currency!
}

type User {
  userId: String!
}

type Currency {
  currencySymbol: String!
}

type Query {
  "Get User Info"
  getUser: UserResponse!
}

And a resolver like this:

Query: {
getUser: async (_, __, context) => {
          try {
            return await (context.datasourceApiMap[AccountAPI.apiName] as AccountAPI).getUser()
          } catch (error: any) {
            return {
              success: false,
              errorCode: error.extensions?.code ?? "USER_NOT_FOUND",
              errorMessage: error.message,
            }
          }
        },
      }
....

UserResponse: {
        __resolveType(response) {
          if (response.success) {
            return "UserSuccessfulResponse"
          }
          if (!response.success) {
            return "GenericResponse"
          }
          return null
        },
      },

where the getUser function in the datasource is returning a UserResponse or throwing an error that I handle in the catch clause.

public abstract getUser(): Promise<UserResponse>

This API call contains two different calls to REST APIs:

  • one to retrieve the user information
  • one to retrieve the currency information

This is terribly wrong! If the client (frontend in my case) only require either the user information in the schema or the currency we still call the two APIs, reducing performances and leveraging the power of GraphQL resolvers.

What then I tried to do was to create a resolver for the UserSuccessfulResponse:

UserSuccessfulResponse: {
        userInfo: async (_, __, context) => {
          return await (context.datasourceApiMap[AccountAPI.apiName] as AccountAPI).getUserInfo()
        },
        currency: async (_, __, context) => {
          return await (context.datasourceApiMap[AccountAPI.apiName] as AccountAPI).getCurrency()
        },
      }

that calls two different datasources to resolve user or currency information.

public abstract getUserInfo(): Promise<User>
public abstract getCurrency(): Promise<Currency>

The getUser resolver would become something like:

getUser: async (_, __, context) => {
          try {
            return {
            success: true,
            userInfo: this.getResolvers().UserSuccessfulResponse?.userInfo,
            currency: this.getResolvers().UserSuccessfulResponse?.currency,
          }
          } catch (error: any) {
            return {
              success: false,
              errorCode: error.extensions?.code ?? "USER_NOT_FOUND",
              errorMessage: error.message,
            }
          }
        },

The point is that in this way if one of the two calls fails I'm not able to catch the error anymore as it is not thrown and propagated to the parent resolver anymore (I was expecting UserSuccessfulResponse resolver to propagate the error to the getUser resolver, but it's clearly not).

I can't set the success field to true or false anymore, accordingly to the presence or not of an error.

I don't know how to solve this issue. Frontend is already implemented and I can't change the schema anymore without a lot of pain.

How can I still split the resolver in two different calls without affecting the schema? Thanks for your help!!!

1

There are 1 best solutions below

0
Michel Floyd On

I suggest that you implement two queries, getUser and getCurrency and that the front-end gang these together in a single network request.

query startMeUp {
  getUser {
    …user fields
  }
  getCurrency {
    …currency fields
  }
}

Each query could hit one of your back-end APIs and report standard GraphQL errors.