如何在 GraphQL 中处理错误并发送响应

Posted

技术标签:

【中文标题】如何在 GraphQL 中处理错误并发送响应【英文标题】:How to handle error and send response in GraphQL 【发布时间】:2019-12-12 00:20:56 【问题描述】:

我从 GraphQL 开始,我无法理解如何在 GraphQL 中抛出错误

我浏览了网络上的几篇文章,但几乎所有文章都使用 Apollo,而且代码结构看起来与我的工作方式大不相同。

考虑这段代码,我在这里进行突变,现在如何发送带有错误的响应消息并在出现错误时更改标头状态消息?

  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)
      
    
  

【问题讨论】:

【参考方案1】:

我在我的一个项目中遇到过,很难设置响应的状态码。因此,我使用express-graphql 进行了一些自定义错误响应来识别正确的statusCode

以下是示例(我在我的一个项目中使用的):

--------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函数实现--------

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

const getErrorCode = errorName => 
  return errorType[errorName]


module.exports = getErrorCode

--------Constant.js文件--------

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
  


现在,我们可以使用我们的设置了。

从您的查询或突变中,您需要需要常量文件并返回自定义错误:

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: [
    "statusCode": 403,
    "message": "User is already exists."
  ],
  data: null

我们也只需要从 FS 端编写自定义错误处理。

注意:- formatError: 已弃用并由 customFormatErrorFn 取代。它将在版本 1.0.0 中删除。可以参考customFormatErrorFn。

【讨论】:

【参考方案2】:

graphql 应该是一个不应该的应用层(参见最后一段为什么不应该而不是)需要http 工作。尽管在 99% 的情况下它运行在 http 之上,但由于这样做非常方便,graphql 本身就是一个第 7 层协议。

在你的情况下这意味着什么?嗯,这意味着你不应该将 HTTP/REST 的概念与 graphql 的概念混为一谈,而应该关注后者。 headers 错误代码是一个 HTTP/REST 概念,graphql 在响应的errors 字段中发送错误,nodejs 实现已经捕获了所有错误并将它们添加到列表中。 HTTP 状态将始终为 200,您的客户不应该关心和使用您的 graphql api,而不是 REST 与 graphql 的混合。

话虽如此,REST over HTTP 在一些方面做得更好。所以人们,包括 Apollo 的开发者,也有点混合概念,主要是因为 graphql 标准不完整(也就是说,它没有解决你在构建 API 时可能遇到的所有问题的标准/规则),所以人们即兴创作。对于任何严肃的项目,我都不推荐 graphql。

Reference

【讨论】:

【参考方案3】:

您可以像这样在 graphqlHTTP 中指定一个错误函数:

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;
            
        
    ));

err.message 可能包含 JSON 对象或字符串。

您可以使用这些函数来生成特定的客户端和服务器错误函数:

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 函数在您遇到 mongodb 验证错误时很有用。

这样你就可以像这样在解析函数中使用它:

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

响应将是


  "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
  

如果你想抛出一个clientError,你把它扔到外面try catch。

希望这段代码可以帮助某人在 graphql 中发送动态错误消息。

【讨论】:

以上是关于如何在 GraphQL 中处理错误并发送响应的主要内容,如果未能解决你的问题,请参考以下文章

颤振中用于处理错误的graphql拦截器

Apollo GraphQL 本地和全局错误处理

如何在 express/node js 中发送错误 http 响应?

如何在我的 Vue Graphql 组件中使用错误并让其他错误在全局范围内处理?

来自 GraphQL 突变的动态响应

如何发送文件以响应 graphql 查询? (从服务器向客户端发送一个 csv 文件)