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

使用graphql-ws订阅无法获取初始Todo数据求助

GraphQL-Yoga + graphql-ws 订阅无法获取初始Todo列表问题

刚接触graphql-ws和graphql-yoga服务器,若问题浅显还请见谅。查阅了graphql-ws文档,其中将schema作为参数,但文档里的schema定义缺少参考。使用addTodo添加新待办项后,能显示两个Todo,但在Yoga Graphiql explorer中订阅时无法获取初始Todo列表。按预期订阅后应立即显示初始Todo项,认为是schema定义中遗漏了某些内容导致该问题,请问是否有人遇到过类似问题并解决?遗漏了什么?

使用的库

  • 后端:graphql-yoga、ws、graphql-ws
  • 前端:solid-js、wonka

Schema中声明的初始Todo项

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

截图

Yoga Graphiql explorer未显示Todo项

代码片段

Schema定义

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后端

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);
});

问题原因及修复方案

  1. 语法错误导致订阅Resolver未被正确注册
    你的Schema定义代码中,Mutation对象没有正确闭合,导致Subscription被错误嵌套在Mutation内部,GraphQL无法识别到订阅的Resolver,这是核心问题。

修复方法:在Mutation的addTodo resolver后添加闭合的}和逗号:

// 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;
        },
    }, // 新增该行,闭合Mutation对象
    Subscription: {
        todos: {
            subscribe: () => {
                const res = pubSub.subscribe(TODOS_CHANNEL);
                publishToChannel({ todos });
                return res;
            }
        },
    },
};
  1. 订阅字段类型优化(可选)
    你的Subscription类型定义中,todos: [Todo!]可以改为[Todo!]!,明确表示返回的Todo列表非空,更符合业务逻辑:
type Subscription {
    todos: [Todo!]!
}
  1. 验证发布的数据结构
    你当前发布的{ todos }结构是正确的,因为订阅字段名为todos,GraphQL会自动从payload中提取对应字段的值,无需额外修改。

修复后重启服务器,订阅todos时就能立即收到初始的Todo列表,后续添加新Todo时也能实时更新。


内容的提问来源于stack exchange,提问作者adityajava6

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 00:10:51