You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 08:50:29