GraphQL - 如何响应不同的状态码?

GraphQL - How to respond with different status code?

我在使用 Graphql 和 Apollo Client 时遇到问题。

我在使用 REST 时总是创建不同的响应,例如 401 代码,但在这里我不知道如何做类似的行为。

当我得到响应时,我希望它转到 catch 函数。 我的前端代码示例:

client.query({
  query: gql`
    query TodoApp {
      todos {
        id
        text
        completed
      }
    }
  `,
})
  .then(data => console.log(data))
  .catch(error => console.error(error));

有人能帮帮我吗?

在 GraphQL(至少在 graphql-js)中 return 错误的方法是在 resolve 函数中抛出错误。由于 HTTP 状态代码特定于 HTTP 传输,而 GraphQL 不关心传输,因此您无法在那里设置状态代码。你可以做的是在你的 resolve 函数中抛出一个特定的错误:

age: (person, args) => {
  try {
    return fetchAge(person.id);
  } catch (e) {
    throw new Error("Could not connect to age service");
  }
}

GraphQL 错误在响应中发送到客户端,如下所示:

{
  "data": {
    "name": "John",
    "age": null
  },
  "errors": [
    { "message": "Could not connect to age service" }
  ]
}

如果消息的信息不够,您可以为您的 GraphQL 服务器创建一个特殊错误 class,其中包含一个状态代码。为确保状态代码包含在您的响应中,您必须在创建中间件时指定 formatError 函数:

app.use('/graphql', bodyParser.json(), graphqlExpress({ 
    schema: myGraphQLSchema,
    formatError: (err) => ({ message: err.message, status: err.status }),
}));

spec 最近添加了一个关于错误输出的内容:

GraphQL services may provide an additional entry to errors with key extensions. This entry, if set, must have a map as its value. This entry is reserved for implementors to add additional information to errors however they see fit, and there are no additional restrictions on its contents.

现在使用 extensions 字段,您可以为 errors 条目自定义机器可读信息:

{
  "errors": [
    {
      "message": "Name for character with ID 1002 could not be fetched.",
      "locations": [ { "line": 6, "column": 7 } ],
      "path": [ "hero", "heroFriends", 1, "name" ],
      "extensions": {
        "code": "CAN_NOT_FETCH_BY_ID",
        "timestamp": "Fri Feb 9 14:33:09 UTC 2018"
      }
    }
  ]
}

Apollo-Server is spec-compliant with this feature check it out, Error Handling 的最新版本。

只是为了补充 Glenn 的回答,here 是 Graphql 规范的一部分,它定义了应该如何处理错误。因此,要知道请求是否失败(或部分失败),您可以检查响应根目录中的 "errors" 键。

对此进行了一些试验后,我意识到缺少一些重要的细节。主要是,如果你有一个带有自定义字段的自定义错误对象,上面的例子将允许你读取你的自定义属性,因为看起来自定义错误被转换成一个标准的 Error 对象,只有一条消息 属性 .

这是我的 formatError 函数的样子(注意 originalError 属性):

  app.use('/graphql', auth.verifyAccess, graphqlHTTP((req, res) => {
    return {
      schema: makeExecutableSchema({
        typeDefs: typeDefs,
        resolvers: rootResolver
      }),
      graphiql: true,
      formatError: (err) => ({
        message: err.originalError.message || err.message,
        code: err.originalError.code || 500
      }),
    }
  }));

originalError 属性似乎总是​​被设置,但作为保障,您可以使用 lodash get 属性.

我有一个定义的自定义错误 class 称为 APIError

class APIError extends Error {
  constructor({ code, message }) {
    const fullMsg = `${code}: ${message}`;

    super(fullMsg);
    this.code = code;
    this.message = message;
  }
}

export default APIError;

在我的解析器中,我这样抛出异常:

  const e = new APIError({
    code: 500,
    message: 'Internal server error'
  });

我认为在关于 graphql 和错误的讨论中遗漏了一个问题,即从 http 到 gql 的转换中的错误,这通常是应该出现 401 的地方。

转换请求时,您应该将授权 header(或您正在使用的任何身份验证方法)转换为用户,如果无法通过身份验证,则应该 return HTTP 401 错误- 这不是图表或规范的一部分 api,只是用户是否可以通过验证的问题。您甚至不必检查查询。

另一方面,403 错误最有可能发生在 gql 层(并且可能不会使用 http 状态代码,但这是另一个讨论),因为它可能是非常特定于域的,你必须知道决定是否禁止的查询。

HTTP 403 状态可用于告诉用户他根本无法访问 gql api。

我们在 express/nestjs 中解决了这个问题,方法是在访问 graphql 层之前使用一个中间件来丰富用户(可能未定义)的请求,或者如果用户无法通过身份验证则失败。如果您不提供凭据(或类似信息),我认为 401 不应该 returned。