提问人:Matt 提问时间:5/10/2019 更新时间:12/29/2021 访问量:5336
在发送之前修改 Graphql 的响应
Modifying Response of Graphql before sent out
问:
我正在寻找一种方法来在发送之前修改 graphql 查询或突变的响应对象。
基本上除了数据对象之外,我还希望有额外的字段,如代码和消息。
目前,我正在通过将字段直接添加到我的 GQL 模式中来解决这个问题,例如,以以下类型定义为例:
type Query {
myItems: myItemResponse
}
type myItemResponse {
myItem: Item
code: String!
success: Boolean!
message: String!
}
响应本身是这样的:
{
data: {
myItems: {
myItem: [ ... fancy Items ... ],
message: 'successfully retrieved fancy Items',
code: <CODE_FOR_SUCCESSFUL_QUERY>
}
}
}
我发现这个解决方案不太好,因为它使我的前端过于复杂。
我更喜欢将消息代码和其他元数据与实际数据分开的解决方案,因此如下所示:
{
data: {
myItems: [ ... fancy Items ... ],
},
message: 'successfully retrieved fancy Items',
code: <CODE_FOR_SUCCESSFUL_QUERY>
}
使用apollo-server,我已经尝试了构造函数中的formatResponse对象:
const server = new ApolloServer({
...
formatResponse({ data }) {
return {
data,
test: 'Property to test if shown in the FrontEnd',
}
}
...
}
不幸的是,这并没有达到预期的效果。在我使用快速中间件之前,我想问一下是否有可能通过开箱即用的 apollo-server 执行此操作,或者我是否可能只是在 formatResponse 函数中缺少一些东西。
答:
在做了大量研究之后,我发现 graphql 响应中唯一允许的顶级属性是数据、错误、扩展。在这里,您可以在 GitHub 中找到相关问题
出于我的目的,我可能会使用扩展字段。
从 graphql.org:对 GraphQL 操作的响应必须是映射。
如果操作遇到任何错误,则响应映射必须包含包含键错误的条目。此项的值在“错误”一节中进行了描述。如果操作完成时未遇到任何错误,则此条目不得存在。
如果操作包括执行,则响应映射必须包含包含关键数据的条目。此项的值在“数据”一节中进行了描述。如果操作在执行之前由于语法错误、缺少信息或验证错误而失败,则此条目不得存在。
响应映射还可能包含具有键扩展的条目。如果设置了此条目,则必须将映射作为其值。此条目保留给实现者以他们认为合适的方式扩展协议,因此对其内容没有额外的限制。
为确保将来对协议的更改不会破坏现有服务器和客户端,顶级响应映射不得包含除上述三个条目以外的任何条目。
示例数据修饰符
此函数将在输出对象中的每个字符串上连接“:OK”后缀
// Data/output modifier - concat ":OK" after each string
function outputModifier(input: any): any {
const inputType = typeof input;
if (inputType === 'string') {
return input + ':OK';
} else if (Array.isArray(input)) {
const inputLength = input.length;
for (let i = 0; i < inputLength; i += 1) {
input[i] = outputModifier(input[i]);
}
} else if (inputType === 'object') {
for (const key in input) {
if (input.hasOwnProperty(key)) {
input[key] = outputModifier(input[key]);
}
}
}
return input;
}
解决方案 1 - 覆盖 GraphQL 解析器
长话短说:您有 3 种主要类型(查询、变更和订阅)。 每个主类型都有带有解析程序的字段。 解析器返回输出数据。
因此,如果覆盖解析器,您将能够修改输出。
示例转换器
import { GraphQLSchema } from 'graphql';
export const exampleTransformer = (schema: GraphQLSchema): GraphQLSchema => {
// Collect all main types & override the resolvers
[
schema?.getQueryType()?.getFields(),
schema?.getMutationType()?.getFields(),
schema?.getSubscriptionType()?.getFields()
].forEach(fields => {
// Resolvers override
Object.values(fields ?? {}).forEach(field => {
// Check is there any resolver at all
if (typeof field.resolve !== 'function') {
return;
}
// Save the original resolver
const originalResolve = field.resolve;
// Override the current resolver
field.resolve = async (source, inputData, context, info) => {
// Get the original output
const outputData: any = await originalResolve.apply(originalResolve.prototype, [source, inputData, context, info]);
// Modify and return the output
return outputModifier(outputData);
};
});
});
return schema;
};
如何使用它:
// Attach it to the GraphQLSchema > https://graphql.org/graphql-js/type/
let schema = makeExecutableSchema({...});
schema = exampleTransformer(schema);
const server = new ApolloServer({schema});
server.listen(serverConfig.port);
该解决方案适用于任何 GraphQL-JS 服务(apollo、express-graphql、graphql-tools 等)。
保持最小值使用此解决方案,您也可以操作。inputData
解决方案 2 - 修改响应
这种解决方案更优雅,但是在指令和标量类型实现之后实现的,不能操作输入数据。
输出对象的具体情况是数据是对象(没有像 .hasOwnProperty()、.toString() 等实例方法),错误是锁定对象(只读)。
在示例中,我正在解锁错误对象...请注意这一点,不要更改对象的结构。null-prototype
示例转换器
import { Translator } from '@helpers/translations';
import type { GraphQLResponse, GraphQLRequestContext } from 'apollo-server-types';
import type { GraphQLFormattedError } from 'graphql';
export const exampleResponseFormatter = () => (response: GraphQLResponse, requestContext: GraphQLRequestContext) => {
// Parse locked error fields
response?.errors?.forEach(error => {
(error['message'] as GraphQLFormattedError['message']) = exampleTransformer(error['message']);
(error['extensions'] as GraphQLFormattedError['extensions']) = exampleTransformer(error['extensions']);
});
// Parse response data
response.data = exampleTransformer(response.data);
// Response
return response;
};
如何使用它:
// Provide the schema to the ApolloServer constructor
const server = new ApolloServer({
schema,
formatResponse: exampleResponseFormatter()
});
结论
我在我的项目中同时使用了这两种解决方案。使用第一个,您可以根据代码中的特定访问指令控制输入和输出,或者验证整个数据流(在任何 graphql 类型上)。 其次,根据用户提供的上下文标头翻译所有字符串,而不会弄乱解析器和带有语言变量的代码。
这些示例在 TS 4+ 以及 GraphQL 15 和 16 上进行了测试
下一个:iOS 调用前置摄像头失败
评论
formatResponse
是添加这些字段的正确方法。说“没有达到预期的效果”并不能描述您遇到的问题。请具体说明您预期的行为以及您遇到的意外行为。formatResponse