如何在Apollo Server(TS)中禁止GraphQL字段被外部查询?
解决方案
针对你的需求,有两种可行方案,这里优先推荐最直观、适合新手维护的方式:
方案一:拆分内部/外部类型(推荐)
最稳妥的方式是区分内部使用的数据类型和暴露给客户端的类型,彻底从对外的Schema中移除databaseId字段,让客户端完全看不到它,从根源避免被查询。
步骤1:修改Schema
只保留客户端能访问的字段在对外的类型中,内部类型在TypeScript里定义即可:
type MyObjInput { id: ID! createdDate: String } # 仅暴露给客户端的类型,不含databaseId type PublicMyObj { id: ID! createdDate: String } type Query { """ Retrieves a previously created MyObj """ getMyObj(id: ID!): PublicMyObj! } type Mutation { """ Creates a new MyObj """ createMyObj(initialObj: MyObjInput): PublicMyObj! }
步骤2:TypeScript定义内部类型
在代码里定义内部使用的完整类型,包含databaseId:
// 内部持久化和业务逻辑使用的类型 interface InternalMyObj { id: string; databaseId: string; createdDate?: string; } // 对应Schema中的PublicMyObj interface PublicMyObj { id: string; createdDate?: string; }
步骤3:Resolver中转换类型
查询或创建后,将内部类型转换为对外的公共类型,过滤掉databaseId:
const resolvers = { Query: { getMyObj: async (_: unknown, { id }: { id: string }) => { // 从本地存储获取完整的内部对象 const internalObj: InternalMyObj = await fetchFromStorage(id); // 转换为公共类型,移除databaseId return { id: internalObj.id, createdDate: internalObj.createdDate }; } }, Mutation: { createMyObj: async (_: unknown, { initialObj }: { initialObj: MyObjInput }) => { // 调用外部API获取databaseId const databaseId = await fetchExternalDatabaseId(); // 构建内部对象并持久化 const internalObj: InternalMyObj = { id: initialObj.id, databaseId, createdDate: initialObj.createdDate }; await saveToStorage(internalObj); // 返回公共类型给客户端 return { id: internalObj.id, createdDate: internalObj.createdDate }; } } };
这种方式的优势是清晰无歧义,Schema完全反映客户端的权限,新手也能快速理解和维护,不会出现误暴露字段的情况。
方案二:使用Schema Directive拦截查询
如果你不想拆分类型,可以通过自定义Directive阻止客户端查询databaseId,但客户端仍能在Schema中看到该字段(只是查询会报错)。
步骤1:在Schema中添加Directive
# 定义内部字段指令 directive @internal on FIELD_DEFINITION type MyObj { id: ID! databaseId: String @internal # 标记为内部字段 createdDate: String } # 其余Query、Mutation定义不变
步骤2:实现Directive和验证规则
通过Apollo Server的验证规则,拦截包含内部字段的查询并报错:
import { ApolloServer, GraphQLError, ValidationContext } from 'apollo-server'; // 自定义验证规则:禁止查询带@internal的字段 function internalFieldValidationRule(context: ValidationContext) { return { Field(node) { const fieldDef = context.getFieldDef(); if (fieldDef?.astNode?.directives?.some(d => d.name.value === 'internal')) { throw new GraphQLError(`字段 "${node.name.value}" 为内部字段,不可查询`, { nodes: node }); } } }; } // 注册Directive(这里仅为标记,核心逻辑在验证规则) class InternalDirective { static visitFieldDefinition() {} } // 创建Apollo Server时配置 const server = new ApolloServer({ typeDefs, resolvers, schemaDirectives: { internal: InternalDirective }, validationRules: [internalFieldValidationRule] });
这种方式适合不想修改Schema结构的场景,但客户端仍能看到databaseId字段,只是无法查询,不如拆分类型彻底。
内容的提问来源于stack exchange,提问作者drunkenfist
相关产品推荐
相关产品推荐

