node.jsexpressgraphqlgraphql-tools

how to implement resolveType function / initialise graphQL server in express


I have 2 questions regarding the GraphQL.

  1. I created a schema using makeExecutableSchema so I can create a executableSchema. I exports executableSchema and initialising the server in my app.js like this.
app.use('/graphql', graphqlHTTP({
    schema: executableSchema,
    rootValue: executableSchema,
    graphiql: true,
    customFormatErrorFn: (error) => ({
        message: error.message,
        locations: error.locations,
        stack: error.stack ? error.stack.split('\n') : [],
        path: error.path
    })
}))

but I wonder if it is the right way to pass executableSchema for both schema and rootValue?

  1. How can I implement the resolveType function and where?

I am getting an error saying Error: Abstract type "LoginResult" must resolve to an Object type at runtime for field "Query.login" with value { __typename: "EmailNotFound" }, received "undefined". Either the "LoginResult" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.

In general I created my LoginResult as union so I could receive more specific error message to my front end, to handle different error messages.

This is how my LoginResult in schema looks like

    type AuthData {
        token: String!
        refreshToken: String!
        userId: String!
    }

    type EmailError {
        message: String
    }

    type PasswordError {
        message: String
    }

    type VerificationError {
        message: String
    }

    union LoginResult = AuthData | EmailError | PasswordError | VerificationError

    type Query {
        login(email: String!, password: String!): LoginResult!
    }

and login resolver method looks like this

const resolvers = {
    Query: {
        login: async function ({ email, password }) {
            try {
                const user = await User.findOne({ email: email })
    
                const errors = []
    
                if(!user) {
                    const error = new Error('User not found.')
                    error.code = 404
                    errors.push('404')
                    
                    return {
                        __typename: 'EmailNotFound'
                    }
    
                    // throw error
                }
    
                const isEqual = await bcrypt.compare(password, user.password)
    
                if(!isEqual) {
                    const error = new Error('Password is incorrect.')
                    error.code = 401
                    errors.push('401')
                    // throw error
                    return {
                        __typename: 'PasswordIncorrect'
                    }
                }
    
                if(!user.isVerified) {
                    const error = new Error('Please verify your email address')
                    error.code = 403
                    errors.push('403')
                    // throw error
                    return {
                        __typename: 'NotVerified'
                    }
                }
    
                // if (errors.length > 0) {
                //     const error = new Error('Invalid input.')
                //     error.data = errors
                //     error.code = 422
                //     throw error
                // }
    
                const token = jwt.sign(
                    {
                        userId: user._id.toString(),
                        email: user.email
                    },
                    JWT_SECRET_KEY,
                    { expiresIn: '30min' }
                )
    
                const refreshToken = jwt.sign(
                    {
                        userId: user._id.toString(),
                        email: user.email
                    },
                    JWT_SECRET_KEY,
                    { expiresIn: '1h' }
                )
    
                return {
                    token,
                    refreshToken,
                    userId: user._id.toString(),
                    __typename: 'AuthData'
                }
    
            } catch(err) {
                console.log(err)
            }
        }
    }
}

and this is how I created a query from front end

const graphqlQuery = {
          query: `
            query UserLogin($email: String!, $password: String!){
              login(email: $email, password: $password) {
                __typename
                ...on AuthData {
                    token
                    userId
                }
                ...on EmailError {
                   message
                }
                ...on PasswordError {
                   message
                }
                ...on VerificationError {
                   message
                }

              }
            }
          `,
          variables: {
            email,
            password
          }
        }

Any help is appriciated!


Solution

  • The rootValue is the value that is passed into your query and mutation resolvers (as the parent value). This value is seldomly used but you could use it to pass anything to these resolvers.

    Your type names and the strings you return in __typename need to be exactly the same. E.g. your type seems to be called PasswordError but you return the type name PasswordIncorrect.