简体   繁体   中英

How to handle error and send response in GraphQL

I was starting with GraphQL and I was unable to comprehend how we can throw errors in GraphQL

I went through a couple of articles on the web but almost all of them use Apollo and the code-structure looks very different than how I work.

Consider this piece of code, here where I am making a mutation, now how can send a response message with error and change headers status message in case of error?

  AddNewPersonalInfo: {
  type: userDashboardType,
  args: { 
    parameter: {
      type: userCreationlInputType
    }
  }, 
  resolve: async (parent, args, context) => {
    args.parameter.userId = context.req.headers.userId
    //Check if user info already exsist
    const checkIfUserInformationExsist = await getSelectedThingFromTable('CatsWork_personal', 'userId', `${userId}`)
    if (checkIfUserInformationExsist[0]) {
      const error = {
        code: 403, 
        message: 'User info Already exsist'
      }
      throw new Error(error)
    } else {
      try {
      const addLinkedinUser = await insertIntheTable('personal', payload)
      return true
      } catch (err) {
        console.error(err)
        throw new Error(err)
      }
    }
  }
}

What I have faced in one of my projects, it is hard to set the status code of the response. So, I made some custom error response to identify correct statusCode using express-graphql

Below is the example ( What I have used in one of my projects ):

-------- app.js file --------

const graphqlHTTP = require('express-graphql')

app.use('/graphql', (req, res) => {
  graphqlHTTP({
    schema: GraphQLSchema, //A GraphQLSchema instance from GraphQL.js. A schema must be provided.
    graphiql: true,
    context: { req },
    formatError: (err) => {
      const error = getErrorCode(err.message)
      return ({ message: error.message, statusCode: error.statusCode })
    }
  })(req, res)
})

-------- getErrorCode function implementation--------

const { errorType } = require('../constants')

const getErrorCode = errorName => {
  return errorType[errorName]
}

module.exports = getErrorCode

-------- Constant.js file--------

exports.errorName = {
  USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS',
  SERVER_ERROR: 'SERVER_ERROR'
}

exports.errorType = {
  USER_ALREADY_EXISTS: {
    message: 'User is already exists.',
    statusCode: 403
  },
  SERVER_ERROR: {
    message: 'Server error.',
    statusCode: 500
  }
}

Now, we are ready to use our setup.

From your query or mutation, you need to require constant file and return custom error:

const { errorName } = require('../constant')

AddNewPersonalInfo: {
  type: userDashboardType,
  args: { 
    parameter: {
      type: userCreationlInputType
    }
  }, 
  resolve: async (parent, args, context) => {
    args.parameter.userId = context.req.headers.userId
    //Check if user info already exsist
    const checkIfUserInformationExsist = await getSelectedThingFromTable('CatsWork_personal', 'userId', `${userId}`)
    if (checkIfUserInformationExsist[0]) {
      const error = {
        code: 403, 
        message: 'User info Already exsist'
      }
      throw new Error(errorName.USER_ALREADY_EXISTS) // Here you can use error from constatnt file
    } else {
      try {
      const addLinkedinUser = await insertIntheTable('personal', payload)
      return true
      } catch (err) {
        console.error(err)
        throw new Error(errorName.SERVER_ERROR) // Here you can use error from constatnt file
      }
    }
  }
}

--------Error response--------

{
  error: [{
    "statusCode": 403,
    "message": "User is already exists."
  }],
  data: null
}

We just need to write custom error handling from FS side too.

Note:- formatError: is deprecated and replaced by customFormatErrorFn . It will be removed in version 1.0.0. You can refer customFormatErrorFn .

graphql should be an application level layer that shouldn't (see last paragraph why shouldn't and not doesn't ) require http to work. Although in 99% of cases it runs on top of http, because of how convenient it is to do so, graphql is itself a layer 7 protocol.

What does that mean in your case? Well, it means you should not mix concepts from HTTP/REST with concepts from graphql and focus on the latter. The headers error code is a HTTP/REST concept, graphql sends errors in the errors field of the response and the nodejs implementation already catches all your errors and adds them to the list. The HTTP status will be always 200, and your clients shouldn't care and consume your graphql api and not a mix of REST with graphql.

Now, that being said, there are couple of things that REST over HTTP does better. So people, including the developers of Apollo, kinda mixed concepts too, mainly because the graphql standard is not complete (aka, it doesn't have a standard/rule for solving all the problems you might encounter while building an API), so people improvised. Honestly, I wouldn't recommend graphql yet for any serious project. Been there, done that, not worth it. Just stick with REST over HTTP.

Reference

You can specify an error function inside graphqlHTTP like this:

app.use("/graphql", graphqlHTTP({
        schema,
        graphiql: true,
        customFormatErrorFn: err => { 
            try { 
                err.details = JSON.parse(err.message);
                err.message = Array.isArray(err.details.error) ? err.details.error.join(",") : err.details.error;
                return err;
            } catch {
                return err;
            }
        }
    }));

where err.message might contain a JSON object or a string.

you can use those function to generate specific client and server error functions:

const clientError = error => new Error(JSON.stringify({
    success: false,
    code: 400,
    error
}));

const serverError = ({ name, message, stack }) => new Error(JSON.stringify({
    success: false,
    error: "Server Error",
    code: 500,
    name,
    message,
    stack
}));

const userValidationError = err => { 
    if (err.name === "ValidationError") return clientError(Object.values(err.errors).map(({ message }) => message));
    return serverError(err);
}

module.exports = {
    clientError,
    serverError,
    userValidationError
};

userValidationError function is useful if you have a mongodb validation error.

so that you would use it inside resolve function like this:

try { 
    const createdDocument = await MongooseDoc.create(data);
    return createdDocument;
} catch (err) { 
    throw userValidationError(err);
}

the response would be

{
  "errors": [
    {
      "message": "error details 1,error details 2",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "document"
      ],
      "details": {
        "success": false,
        "code": 400,
        "error": [
          "error details 1",
          "error details 2"
        ]
      }
    }
  ],
  "data": {
    "document": null
  }
}

if you want to throw a clientError you throw it outside try catch.

Hopefully this code helps someone send dynamic error messages in graphql.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM