node.jsgraphqlpublish-subscribews

Unable to get initial data using graphql-ws subscription


I am fairly new to using graphql-ws and graphql-yoga server, so forgive me if this is a naive question or mistake from my side.

I went through graphql-ws documentation. It has written the schema as a parameter. Unfortunately, the schema definition used in the documentation is missing a reference.

After adding a new todo (using addTodo) it shows two todo items. So I believe it is unable to return the initial todo list whenever running subscribe on Yoga Graphiql explorer.

It should show the initial todo item as soon as it has been subscribed and published in the schema definition. My understanding is there is something I am missing in the schema definition which is not showing the todo list when tried accessing Yoga Graphiql explorer.

Has anyone had a similar experience and been able to resolve it? What I am missing?

Libraries used

Backend

Frontend

Todo item - declared in schema

{
  id: "1",
  title: "Learn GraphQL + Solidjs",
  completed: false
}

Screenshot

Yoga Graphiql explorer not showing todo item

Code Snippets

Schema definition

import { createPubSub } from 'graphql-yoga';
import { Todo } from "./types";

let todos = [
    {
        id: "1",
        title: "Learn GraphQL + Solidjs",
        completed: false
    }
];

// channel
const TODOS_CHANNEL = "TODOS_CHANNEL";

// pubsub
const pubSub = createPubSub();

const publishToChannel = (data: any) => pubSub.publish(TODOS_CHANNEL, data);

// Type def
const typeDefs = [`
    type Todo {
        id: ID!
        title: String!
        completed: Boolean!
    }

    type Query {
        getTodos: [Todo]!
    }

    type Mutation {
        addTodo(title: String!): Todo!
    }

    type Subscription {
        todos: [Todo!]
    }
`];

// Resolvers
const resolvers = {
    Query: {
        getTodos: () => todos
    },
    Mutation: {
        addTodo: (_: unknown, { title }: Todo) => {
            const newTodo = {
                id: "" + (todos.length + 1),
                title,
                completed: false
            };
            todos.push(newTodo);
            publishToChannel({ todos });
            return newTodo;
        },
    Subscription: {
        todos: {
            subscribe: () => {
                const res = pubSub.subscribe(TODOS_CHANNEL);
                publishToChannel({ todos });
                return res;
            }
        },
    },
};

export const schema = {
    resolvers,
    typeDefs
};

Server backend

import { createServer } from "graphql-yoga";
import { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/lib/use/ws";

import { schema } from "./src/schema";
import { execute, ExecutionArgs, subscribe } from "graphql";

async function main() {
    const yogaApp = createServer({
        schema,
        graphiql: {
            subscriptionsProtocol: 'WS', // use WebSockets instead of SSE
        },
    });

    const server = await yogaApp.start();
    const wsServer = new WebSocketServer({
        server,
        path: yogaApp.getAddressInfo().endpoint
    });

    type EnvelopedExecutionArgs = ExecutionArgs & {
        rootValue: {
            execute: typeof execute;
            subscribe: typeof subscribe;
        };
    };

    useServer(
        {
            execute: (args: any) => (args as EnvelopedExecutionArgs).rootValue.execute(args),
            subscribe: (args: any) => (args as EnvelopedExecutionArgs).rootValue.subscribe(args),
            onSubscribe: async (ctx, msg) => {
                const { schema, execute, subscribe, contextFactory, parse, validate } =
                    yogaApp.getEnveloped(ctx);

                const args: EnvelopedExecutionArgs = {
                    schema,
                    operationName: msg.payload.operationName,
                    document: parse(msg.payload.query),
                    variableValues: msg.payload.variables,
                    contextValue: await contextFactory(),
                    rootValue: {
                        execute,
                        subscribe,
                    },
                };

                const errors = validate(args.schema, args.document);
                if (errors.length) return errors;
                return args;
            },
        },
        wsServer,
    );

}

main().catch((e) => {
    console.error(e);
    process.exit(1);
});

Solution

  • apply these changes

    Mutation: {
        addTodo: (_: unknown, { title }: Todo) => {
            const newTodo = {
                id: "" + (todos.length + 1),
                title,
                completed: false
            };
            todos.push(newTodo);
            publishToChannel({ todos });
            return newTodo;
        },
    Subscription: {
        todos: {
            subscribe: () => {
                return Repeater.merge(
                   [
                       new Repeater(async (push, stop) => {
                push({ todos });
                await stop;
            }),
                       pubSub.subscribe(TODOS_CHANNEL),
                   ]
                )
            }
        },
    },
    

    first, npm i @repeaterjs/repeater then import Repeater