apollographql / apollo-client

:rocket:  A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
https://apollographql.com/client
MIT License
19.34k stars 2.66k forks source link

SOLVED : TypeError: Cannot return null for non-nullable field Subscription: Next.js frontend + FastApi backend #11866

Closed MedAzizHaddad closed 4 months ago

MedAzizHaddad commented 4 months ago

Environment

Description

I'm experiencing a TypeError when attempting to use a Strawberry GraphQL subscription from my Next.js frontend using Apollo Client. The subscription functions correctly when tested via the GraphQL interface, but it fails when invoked from the frontend, resulting in a null value for a non-nullable field.

FastAPI backend Code

@strawberry.type
class com:
    userId: Optional[int]

@strawberry.type
class Subscription:
    @strawberry.subscription
    async def commentAdded(self, userId: str) -> AsyncGenerator[com, None]:
        yield com(userId=11)
        await asyncio.sleep(2)

Strawberry GraphQL Endpoint Configuration

schema = strawberry.Schema(query=Query, mutation=Mutation, subscription=Subscription)
graphql_app = GraphQLRouter(schema, subscription_protocols=[
        GRAPHQL_TRANSPORT_WS_PROTOCOL,
        GRAPHQL_WS_PROTOCOL,
    ],
)
app.include_router(graphql_app, prefix="/graphql")

# CORS Configuration
origins = [
    "http://localhost",
    "http://localhost:3000",  # Replace this with the actual origin of your frontend
]

Next.js Frontend Code

const COMMENTS_SUBSCRIPTION = gql(`
  subscription commentAdded($userId: String!) {
    commentAdded(userId: $userId) {
      userId
    }
  }
`);

const { data: dataNotif, loading: loadingNOtif, error:errorNotif } = useSubscription(
    COMMENTS_SUBSCRIPTION,
    { variables: { userId: "test" } }
);

import {ApolloClient, HttpLink, InMemoryCache, split} from "@apollo/client";
import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc";
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import {getMainDefinition} from "@apollo/client/utilities";

const httpLink = new HttpLink({
    uri: "http://localhost:8000/graphql",
    fetchOptions: { cache: "no-store" },
});

const wsLink = new GraphQLWsLink(createClient({
    url: 'ws://localhost:8000/graphql', // also tried 'ws://localhost:8000/subscriptions',
    connectionParams: {
        authToken: "Bearer I_AM_A_VALID_AUTH_TOKEN",
    },
}));

const splitLink = split(
    ({ query }) => {
        const definition = getMainDefinition(query);
        return (
            definition.kind === 'OperationDefinition' &&
            definition.operation === 'subscription'
        );
    },
    wsLink,
    httpLink,
);

export const { getClient } = registerApolloClient(() => {
    return new ApolloClient({
        cache: new InMemoryCache(),
        link: splitLink,
    });
});

Why am I thinking the bug is in frontend ? :

Because when I execute the following subscription query via the backend GraphQL interface

subscription MySubscription {
  commentAdded(userId: "test") {
    userId
  }
}

The expected result is received:

{
  "data": {
    "commentAdded": {
      "userId": 11
    }
  }
}

When invoking the same subscription from the frontend, the following error is encountered (backend console)

ERROR:strawberry.execution:Cannot return null for non-nullable field Subscription.commentAdded.

GraphQL request:2:3
1 | subscription commentAdded($userId: String!) {
2 |   commentAdded(userId: $userId) {
  |   ^
3 |     userId
Traceback (most recent call last):
  File "/home/databiz32/Videos/WEB/other/grats-backend/venv/lib/python3.11/site-packages/graphql/execution/execute.py", line 540, in execute_field
    completed = self.complete_value(
                ^^^^^^^^^^^^^^^^^^^^
  File "/home/databiz32/Videos/WEB/other/grats-backend/venv/lib/python3.11/site-packages/graphql/execution/execute.py", line 620, in complete_value
    raise TypeError(
TypeError: Cannot return null for non-nullable field Subscription.commentAdded.
MedAzizHaddad commented 4 months ago

While writing this issue ticket, I figured it out... The subscriptions configuration (wsLink ..) should be applied in ApolloWrapper.js rather than ApolloClient.js, as they are being used from a client-side component. (I can't believe that I missed that and I wasted like 5 hours trying to "debug" this error ...)

I’ve decided to submit this ticket and document the solution before closing it, in case it proves helpful to someone in the future. (This is my first time submitting an issue ticket, so I’m not entirely sure if this is ok)

so the ApolloWrapper.js should look like this

"use client";
import {ApolloLink, HttpLink, split} from "@apollo/client";
import {
    ApolloNextAppProvider,
    NextSSRInMemoryCache,
    NextSSRApolloClient,
    SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support/ssr";
import React from "react";
import {GraphQLWsLink} from "@apollo/client/link/subscriptions";
import {createClient} from "graphql-ws";
import {getMainDefinition} from "@apollo/client/utilities";

function makeClient() {
    const httpLink = new HttpLink({
        uri: "http://localhost:8000/graphql",
    });
    const wsLink = new GraphQLWsLink(createClient({
        url: 'ws://localhost:8000/graphql',
        connectionParams: {
            authToken: "Bearer I_AM_A_VALID_AUTH_TOKEN",
        },
    }));

    const splitLink = split(
        ({ query }) => {
            const definition = getMainDefinition(query);
            return (
                definition.kind === 'OperationDefinition' &&
                definition.operation === 'subscription'
            );
        },
        wsLink,
        httpLink,
    );

    return new NextSSRApolloClient({
        cache: new NextSSRInMemoryCache(),
        link:
            typeof window === "undefined"
                ? ApolloLink.from([
                    new SSRMultipartLink({
                        stripDefer: true,
                    }),
                    splitLink,
                ])
                : splitLink,
    });
}

export function ApolloWrapper({ children }) {
    return (
        <ApolloNextAppProvider makeClient={makeClient}>
            {children}
        </ApolloNextAppProvider>
    );
}
github-actions[bot] commented 4 months ago

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.

github-actions[bot] commented 3 months ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. For general questions, we recommend using StackOverflow or our discord server.