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!

2
  • How are you creating the schema? Using buildSchema? Commented Jul 17, 2020 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'->stackoverflow.com/questions/53078554/… Commented Jul 17, 2020 at 17:28

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

6 Comments

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?
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) {
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?
It is usually the first parameter. This has been introduced by the reference implementation and has been adopted by many frameworks and languages. If you can accept and upvote my answer, that would help me a lot :)
Yes, it is always the first value. __typename only needs to be returned by resolvers that resolve to a union type or interface type.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.