I have 2 questions regarding the GraphQL.
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
?
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!
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
.