使用 Graphql 在 NestJS 上订阅时,“不能为不可为空的字段返回 null”

Posted

技术标签:

【中文标题】使用 Graphql 在 NestJS 上订阅时,“不能为不可为空的字段返回 null”【英文标题】:"Cannot return null for non-nullable field" when subscribing on NestJS with Graphql 【发布时间】:2019-12-11 15:51:32 【问题描述】:

我有一个使用Nestjs 完成的nodejs 后端,我正在使用Graphql。我的前端是 Ionic/Angular,使用 Apollo-angular 处理 graphql 的东西。 我在订阅数据添加/更改时遇到问题。 Playground(由 Nestjs 提供)运行良好,这提示我问题出在前端。

我的数据模型中有gamescores,每个分数都属于一个游戏。在前端,我正在尝试收听添加到特定游戏的新分数。

后端

这是来自我的resolver 的sn-p:

@Mutation(returns => Score)
async addScore(@Args('data') data: ScoreInput): Promise<IScore> 
  return await this.scoresService.createScore(data);


@Subscription(returns => Score, 
  filter: (payload, variables) => payload.scoreAdded.game + '' === variables.gameId + '',
)
scoreAdded(@Args('gameId') gameId: string) 
  return this.pubSub.asyncIterator('scoreAdded');

这是service 方法:

async createScore(data: any): Promise<IScore> 
  const score = await this.scoreModel.create(data);
  this.pubSub.publish('scoreAdded',  scoreAdded: score );

这些在我的 schema.gql 中:

type Score 
  id: String
  game: String
  result: Int


type Subscription 
  scoreAdded(gameId: String!): Score!

前端

根据Apollo-angular 的文档,在我的前端我有这样的服务:

import  Injectable  from '@angular/core';
import  Subscription  from 'apollo-angular';
import  SCORE_ADDED  from './graphql.queries';

@Injectable(
  providedIn: 'root',
)
export class ScoreListenerService extends Subscription 
  document = SCORE_ADDED;

这是在前端的 graphql.queries 中:

export const SCORE_ADDED = gql`
  subscription scoreAdded($gameId: String!) 
    scoreAdded(gameId: $gameId) 
      id
      game
      result
    
  
`;

我在我的组件中使用这样的服务:

this.scoreListener.subscribe( gameId: this.gameId ).subscribe(( data ) => 
  const score = data.scoreAdded;
  console.log(score);
);

问题

有了这一切,我的前端给了我一个错误ERROR Error: GraphQL error: Cannot return null for non-nullable field Subscription.scoreAdded.

在 Playground 中进行这样的订阅工作,完全没有问题。

subscription 
  scoreAdded(gameId: "5d24ad2c4cf6d3151ad31e3d") 
    id
    game
    result
  

不同的问题

我注意到,如果我在后端的解析器中使用 resolve,如下所示:

  @Subscription(returns => Score, 
    resolve: value => value,
    filter: (payload, variables) => payload.scoreAdded.game + '' === variables.gameId + '',
  )
  scoreAdded(@Args('gameId') gameId: string) 
    return this.pubSub.asyncIterator('scoreAdded');
  

前端的错误消失了,但是它搞砸了订阅中的数据,游乐场在每个属性中获得了带有 null 的附加分数,并且根本没有触发前端中的订阅。

任何帮助,我在这里做错了什么? 在我看来,我的前端不正确,但我不确定这是我的错误还是 Apollo-angular 中的错误...

【问题讨论】:

score 是否在服务方法中定义(即 create 实际上返回创建的模型)? 是的,服务方法正确返回实际保存的模型(在所有这些情况下)。没有在后端订阅部分定义任何 resolve,一切都在操场上运行良好,但前端 Apollo 客户端获得 ERROR Error: GraphQL error: Cannot return null for non-nullable field Subscription.scoreAdded. 【参考方案1】:

好的,我的问题解决了。正如我所怀疑的,问题出在前端代码中。所以我在后端实现nestjs东西的方式没有错。原来是我犯的一个愚蠢的错误,没有为订阅初始化 WS,这在https://www.apollographql.com/docs/angular/features/subscriptions/ 有明确的解释。

所以,我改变了这个

const graphqlUri = 'http://localhost:3000/graphql';

export function createApollo(httpLink: HttpLink) 
  return 
    link: httpLink.create( graphqlUri ),
    cache: new InMemoryCache(),
    defaultOptions: 
      query: 
        fetchPolicy: 'network-only',
        errorPolicy: 'all',
      ,
    ,
  ;

到这里

const graphqlUri = 'http://localhost:3000/graphql';
const wsUrl = 'ws://localhost:3000/graphql';

export function createApollo(httpLink: HttpLink) 
  const link = split(
    // split based on operation type
    ( query ) => 
      const  kind, operation  = getMainDefinition(query);
      return kind === 'OperationDefinition' && operation === 'subscription';
    ,
    new WebSocketLink(
      uri: wsUrl,
      options: 
        reconnect: true,
      ,
    ),
    httpLink.create(
      uri: graphqlUri,
    )
  );
  return 
    link,
    cache: new InMemoryCache(),
    defaultOptions: 
      query: 
        fetchPolicy: 'network-only',
        errorPolicy: 'all',
      ,
    ,
  ;

【讨论】:

你从哪里导入 getMainDefinition ? 立即找到您的问题和答案。我怀疑你至少拯救了我的一天。谢谢。【参考方案2】:

上面提供的答案是正确的,但是对于那些想要查看使用的包版本和导入的文件的人来说,请检查以下解决方案:

package.json 依赖项


   "dependencies": 
      "@apollo/client": "^3.2.5",
      "@apollo/link-ws": "^2.0.0-beta.3",
      "apollo-angular": "^2.0.4",
      "subscriptions-transport-ws": "^0.9.18",
    

graphql.module.ts 代码

import  WebSocketLink  from '@apollo/link-ws';
import  NgModule  from '@angular/core';
import  APOLLO_OPTIONS  from 'apollo-angular';
import  InMemoryCache, split  from '@apollo/client/core';
import  getMainDefinition  from '@apollo/client/utilities';
import  HttpLink  from 'apollo-angular/http';

const uri = 'http://localhost:3000/graphql';
const wsUrl = 'http://localhost:3000/graphql';

export function createApollo(hLink: HttpLink) 
    
    const ws = new WebSocketLink(
       uri: wsUrl,
       options: 
           reconnect: true
       
    );

    const http = hLink.create(uri);

    const newLink = split(
       ( query ) => 
          const def = getMainDefinition(query);
          return def.kind === 'OperationDefinition' && def.operation === 'subscription';
       ,
       ws,
       http
    );
    
    return 
        link: newLink,
        cache: new InMemoryCache(),
        defaultOptions: 
           watchQuery: 
              fetchPolicy: 'network-only',
              errorPolicy: 'all'
           
        
    ;


@NgModule(
   providers: [
     
        provide: APOLLO_OPTIONS,
        useFactory: createApollo,
        deps: [HttpLink],
     ,
   ],
)
export class GraphQLModule 

【讨论】:

以上是关于使用 Graphql 在 NestJS 上订阅时,“不能为不可为空的字段返回 null”的主要内容,如果未能解决你的问题,请参考以下文章

错误:使用 Nestjs + Graphql + Typeorm 时无法确定 GraphQL 输入类型

GraphQL Schema 未使用 NestJS 更新(代码优先方法)

在 Nestjs/GraphQL 中使用接口时避免循环依赖

Nestjs - Mongoose - 虚拟字段 - 无法在 graphql 操场上查询

在 Nestjs 中使用 graphql 进行授权

使用 nestjs 角度通用设置时找不到 api/graphql