Apollo Client前端校验GraphQL输入对象额外字段方案
问题根因
你当前遇到的问题来自两个核心原因:
- 输入类型定义错误:GraphQL 中输入参数类型必须使用
GraphQLInputObjectType,你代码里用的GraphQLObjectType是输出类型,不能用于变量定义,且 fields 字段的写法也不符合规范,需要显式指定字段的 type 属性。 - Apollo Client 默认遵循 GraphQL 官方规范,规范明确允许输入对象携带未定义的额外字段,默认校验逻辑只会检查必填项、基础类型匹配度,不会拦截多余字段,多余字段会直接透传给服务端。
前置修正:输入类型定义
先把错误的类型定义改成符合规范的写法:
import { GraphQLInputObjectType, GraphQLString } from 'graphql'; const mutationInput = new GraphQLInputObjectType({ name: 'mutationInput', fields: { Id: { type: GraphQLString }, statusCode: { type: GraphQLString }, } });
实现方案
以下两种方案均为纯前端实现,不需要服务端做任何修改即可实现多余字段拦截。
方案1:单请求自定义校验规则
适合仅需要对特定查询/变更做校验的场景,利用 Apollo Client 内置的 validationRules 配置项注入自定义校验逻辑:
- 编写禁止额外输入字段的校验规则:
import { GraphQLError } from 'graphql'; const noExtraInputFieldsRule = (context) => ({ ObjectValue(node) { const currentInputType = context.getInputType(); if (!currentInputType?.getFields) return; const allowedFieldNames = Object.keys(currentInputType.getFields()); node.fields.forEach(fieldNode => { const fieldName = fieldNode.name.value; if (!allowedFieldNames.includes(fieldName)) { context.reportError( new GraphQLError( `字段"${fieldName}"未在输入类型"${currentInputType.name}"中定义,禁止传入`, [fieldNode] ) ); } }); } });
- 在对应的 hook 中注入规则,触发请求时如果存在多余字段会直接抛出校验错误,终止请求:
const [getData, { error, loading }] = useMutation( GET_DATA, { validationRules: [noExtraInputFieldsRule], // 其余原有配置 } );
方案2:全局链路统一校验
如果需要对项目内所有 GraphQL 请求统一做多余字段校验,可通过 Apollo Link 实现全局拦截,不需要每个请求单独配置:
import { ApolloLink } from '@apollo/client'; import { GraphQLError, TypeInfo, visit, visitWithTypeInfo } from 'graphql'; // 传入项目的完整schema(可通过内省获取,或构建时直接引入本地schema文件) const inputValidationLink = (schema) => new ApolloLink((operation, forward) => { const { query } = operation; const typeInfo = new TypeInfo(schema); const validationErrors = []; const visitor = { ObjectValue(node) { const currentInputType = typeInfo.getInputType(); if (!currentInputType?.getFields) return; const allowedFieldNames = Object.keys(currentInputType.getFields()); node.fields.forEach(fieldNode => { const fieldName = fieldNode.name.value; if (!allowedFieldNames.includes(fieldName)) { validationErrors.push( new GraphQLError(`非法输入字段"${fieldName}",不属于输入类型"${currentInputType.name}"`) ); } }); } }; visit(query, visitWithTypeInfo(typeInfo, visitor)); if (validationErrors.length > 0) { throw new Error(`请求变量校验失败:${validationErrors.map(err => err.message).join(';')}`); } return forward(operation); }); // 初始化Apollo Client时将该链路放在链路最前面 const client = new ApolloClient({ link: inputValidationLink(projectSchema).concat(httpLink), cache: new InMemoryCache(), // 其余原有配置 });
注意事项
- 字段名大小写敏感:你示例中定义的字段是
Id,传入的是ID,开启校验后该字段也会被判定为非法字段,需要保证传入字段名和类型定义完全一致。 - 上述两种方案默认支持递归校验嵌套输入对象,不需要额外编写递归处理逻辑。
内容的提问来源于stack exchange,提问作者Akarsh
相关产品推荐
相关产品推荐

