TypeScript集成ApolloServer订阅时applyMiddleware传参报错
报错根因
核心问题是导入的ApolloServer类来源错误:
- 你当前从
apollo-server包导入的ApolloServer是独立服务版本,内置了封装好的HTTP服务,本身不支持挂载到外部自定义的express实例上,它的applyMiddleware方法设计上就不接收任何参数,传入{app}自然会触发参数不合法的报错。 - 凡是需要把Apollo Server挂载到自定义express实例、同时搭配WebSocket实现订阅的场景,都必须使用
apollo-server-express包导出的ApolloServer类。
另外你现有代码还有两个隐藏的运行时问题:
- 创建
subscriptionServer的代码写在await server.start()之前,此时server.graphqlPath还未初始化,拿到的值是undefined,会导致订阅服务路径配置错误。 - 初始化ApolloServer时同时传了
schema和typeDefs两个配置项,二者是互斥的,你已经用makeExecutableSchema生成了完整schema实例,重复传typeDefs会造成配置冲突。
修复步骤
- 安装对应依赖包
npm install apollo-server-express # yarn用户执行 yarn add apollo-server-express
- 修改ApolloServer的导入语句
// 删除原有错误导入 // import { ApolloServer } from "apollo-server"; // 替换为正确的导入源 import { ApolloServer } from "apollo-server-express";
- 调整代码执行顺序、移除冗余配置,修正后的核心代码如下
const run = async () => { dotenv.config(); const pubsub = new PubSub(); const db = await connectDB(); const port = process.env.PORT; const app = express(); const httpServer = createServer(app); const schema = makeExecutableSchema({ typeDefs, resolvers: { Query, Mutation, // 记得补上Subscription对应的resolver,否则订阅逻辑不会生效 // Subscription }, }); const server = new ApolloServer({ schema, // 移除冗余的typeDefs配置 context: ({ req, res }) => ({ req, res, pubsub }), plugins: [ ApolloServerPluginDrainHttpServer({ httpServer }), { async serverWillStart() { return { async drainServer() { subscriptionServer.close(); } }; } } ], }); // 先启动Apollo Server实例 await server.start(); // 再将服务挂载到自定义express实例,此时applyMiddleware可正常接收参数 server.applyMiddleware({ app }); // 最后初始化WebSocket订阅服务,此时server.graphqlPath已经是正确值 const subscriptionServer = SubscriptionServer.create( { schema, execute, subscribe, async onConnect(connectionParams: any, webSocket: any, context: any) { console.log('Connected!'); return { pubsub }; }, onDisconnect() { console.log('Disconnected!'); } }, { server: httpServer, path: server.graphqlPath, } ); httpServer.listen(port, () => { console.log( `Server is now running on http://localhost:${port}${server.graphqlPath}`, ); }); }; // 外层调用run时要加await,否则异步错误无法被catch捕获 try { await run(); } catch (error) { console.log(error); }
额外注意事项
- 补全resolvers中的Subscription相关解析逻辑,否则订阅功能无法正常工作。
- 不要同时启用两套WebSocket订阅实现(比如你代码里注释掉的
graphql-ws方案和当前用的subscriptions-transport-ws方案),不要让两个服务监听同一个路径,否则会产生协议冲突。 - 如果你用的是Apollo Server 3.x版本,记得给express配置对应的CORS规则,否则前端访问会被跨域策略拦截。
内容的提问来源于stack exchange,提问作者CCK
相关产品推荐
相关产品推荐

