1

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!

yoonvak
  • 303
  • 1
  • 3
  • 15
  • How are you creating the schema? Using `buildSchema`? – Daniel Rearden Jul 17 '20 at 15:52
  • @DanielRearden I am using 'const makeExecutableSchema = require('@graphql-tools/schema').makeExecutableSchema' It is based on this stackoverflow I read, that I found 'makeExecutableSchema'->https://stackoverflow.com/questions/53078554/how-to-implement-istypeof-method – yoonvak Jul 17 '20 at 17:28

1 Answers1

1

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.

Herku
  • 7,198
  • 27
  • 36
  • I changed them exactly the same as your advice, and now it shows different error which says 'Cannot destructure property 'email' of 'undefined' as it is undefined'. Wonder what is causing this undefined? – yoonvak Jul 17 '20 at 15:18
  • Yes, the first parameter of the resolver is the parent value not the arguments. Change the signature of your resolver: `login: async function (parent, { email, password }, context) {` – Herku Jul 17 '20 at 17:07
  • thank you so much, it works! One other question. Is the 'parent' value alaways the first parameter in all the other resolver methods as well? is it a 'graphql-tools' syntax? – yoonvak Jul 17 '20 at 17:39
  • 1
    It is usually the first parameter. This has been introduced by the [reference implementation](https://github.com/graphql/graphql-js) and has been adopted by many frameworks and languages. If you can accept and upvote my answer, that would help me a lot :) – Herku Jul 17 '20 at 17:48
  • Ah ok, so it does not matter if it is only for the methods that has to return __typename etc. Thanks! I upvoted yours! :) – yoonvak Jul 17 '20 at 17:51
  • 1
    Yes, it is always the first value. `__typename` only needs to be returned by resolvers that resolve to a union type or interface type. – Herku Jul 17 '20 at 18:01