GraphQL为createPost新增Subscription订阅无法连接WS服务问题
问题排查与修复方案
当前订阅连接失败由4个明确问题导致,按以下步骤修复即可:
1. 修复Schema语法错误
你的类型定义中Subscription部分存在语法错误:误将返回值的字段写为订阅参数,且缺失闭合大括号。替换为以下正确定义:
type Subscription { newPost: Post! }
注意:newPost订阅的返回值是已定义的Post类型,不需要在订阅字段上重复声明Post的属性。
2. 修复WebSocket服务上下文配置
你之前仅给HTTP请求的上下文传入了pubsub实例,WebSocket连接的上下文未传入,会导致订阅resolver无法拿到pubsub对象触发事件。同时修正数据库连接的异步逻辑,避免服务启动时数据库未连接完成。
以下是修复后的完整index.js代码:
const { ApolloServer } = require ('apollo-server-express'); const { createServer } = require('http'); const express = require('express'); const { ApolloServerPluginDrainHttpServer } = require('apollo-server-core'); const { makeExecutableSchema } = require('@graphql-tools/schema'); const { WebSocketServer } = require('ws'); const { useServer } = require('graphql-ws/lib/use/ws'); const { PubSub } = require ('graphql-subscriptions'); const resolvers = require('./graphql/resolvers'); const typeDefs = require('./graphql/typeDefs'); const { MONGODB } = require('./config.js'); const mongoose = require('mongoose'); const pubsub = new PubSub(); (async function(){ const schema = makeExecutableSchema({ typeDefs, resolvers }); const app = express(); const httpServer = createServer(app); const wsServer = new WebSocketServer({ server: httpServer, path: '/graphql' }); // 给WS连接上下文注入pubsub实例 const serverCleanup = useServer({ schema, context: () => ({ pubsub }) }, wsServer); const server = new ApolloServer({ schema, csrfPrevention: true, cache: 'bounded', context: ({ req }) => ({ req, pubsub }), plugins: [ ApolloServerPluginDrainHttpServer({ httpServer }), { async serverWillStart() { return { async drainServer() { await serverCleanup.dispose(); }, }; }, }, ], }); await server.start(); server.applyMiddleware({ app }); // 等待数据库连接完成再启动服务 await mongoose.connect(MONGODB, {useNewUrlParser: true}); console.log('MongoDB Connected'); const PORT = 5002; httpServer.listen(PORT, () => { console.log(`HTTP endpoint: http://localhost:${PORT}${server.graphqlPath}`); console.log(`WS subscription endpoint: ws://localhost:${PORT}${server.graphqlPath}`); }); })();
3. 修复posts resolver语法错误
你之前的deletePost方法未写完整,缺失方法体和闭合大括号,会导致resolver模块加载失败。补全后代码如下:
const { AuthenticationError } = require('apollo-server'); const Post = require('../../models/Post'); const checkAuth = require('../../util/check-auth'); module.exports = { Mutation: { async createPost(_, { body }, context) { const user = checkAuth(context); // 可在此处补充你之前的帖子内容校验逻辑 const newPost = new Post({ body, user: user.id, username: user.username, createdAt: new Date().toISOString() }); const post = await newPost.save(); // 触发订阅事件 context.pubsub.publish('NEW_POST', { newPost: post }); console.log("New Post Created, Subscription Triggered!") return post; }, async deletePost(_, { postId }, context) { const user = checkAuth(context); const post = await Post.findById(postId); if (!post) throw new Error('Post not found'); if (post.username !== user.username) throw new AuthenticationError('No permission to delete this post'); await post.delete(); return 'Post deleted successfully'; } }, Subscription: { newPost: { subscribe: (_, __, { pubsub }) => pubsub.asyncIterator('NEW_POST') } } };
4. 修正测试时的WS连接地址
你之前连接WS时用的是根路径ws://localhost:5002,正确的订阅地址是和HTTP端点一致的ws://localhost:5002/graphql,在Apollo Sandbox/Playground中配置订阅地址时填完整路径即可。
验证方法
- 启动服务后,在 playground 中先运行以下订阅语句,保持连接:
subscription { newPost { id body createdAt username } }
- 新开一个标签页运行创建帖子的变更:
mutation { createPost(body: "test subscription post") { id body } }
- 订阅标签页会自动收到新创建的帖子数据,即为配置成功。
内容的提问来源于stack exchange,提问作者Kelum
相关产品推荐
相关产品推荐

