Apollo Server Express v4中this.findOneById不是函数问题排查
问题场景
使用 Apollo Server v4 时,编写了如下基于 apollo-datasource-mongodb 的数据源代码:
import { MongoDataSource } from 'apollo-datasource-mongodb' export default class LoaderAsset extends MongoDataSource { async getAsset(assetId) { return this.findOneById(assetId) // 错误发生在此处 } } async function startApolloServer(app) { const httpServer = http.createServer(app); const server = new ApolloServer({ typeDefs, resolvers, plugins: [ApolloServerPluginDrainHttpServer({ httpServer })] }); await server.start(); app.use( '/graphql', cors(), bodyParser.json(), expressMiddleware(server, { context: async ({ req }) => { return { dataSources: { loaderAsset: new LoaderAsset(modelAsset), } } }, }), ); const port = config.get('Port') || 8081; await new Promise(resolve => httpServer.listen({ port }, resolve)); }
调用 getAsset 时出现错误:
this.findOneById is not a function
另外,this包含collection和model对象,但没有任何方法。
用户疑问:是否是 apollo-datasource-mongodb 与 Apollo Server v4 不兼容?v3 中 dataSources 是独立配置项,v4 放在 context 中是否是问题根源?
问题原因
是的,问题根源就是 Apollo Server v4 的架构变更与 apollo-datasource-mongodb 的兼容性冲突。Apollo Server v3 会自动对配置的数据源进行初始化:调用数据源的 initialize 方法,绑定正确的 this 上下文,并注入相关方法;但 v4 移除了专门的 dataSources 配置项,要求开发者在 context 中手动实例化数据源,这跳过了 apollo-datasource-mongodb 依赖的初始化流程,导致 LoaderAsset 实例仅拥有传入的 collection/model 属性,却没有继承自 MongoDataSource 的方法。
解决办法
方案1:手动封装数据源(推荐)
放弃依赖 apollo-datasource-mongodb,直接基于 MongoDB/Mongoose 原生方法封装数据源,适配 v4 架构:
export default class LoaderAsset { constructor(model) { this.model = model; } async getAsset(assetId) { // 直接使用 Mongoose 的 findById 方法,或 MongoDB 原生的 findOne({_id: assetId}) return this.model.findById(assetId); } }
这种方式无兼容性风险,更贴合 v4 的设计思路。
方案2:手动触发数据源初始化(临时方案)
若必须使用 apollo-datasource-mongodb,可手动调用其 initialize 方法模拟 v3 的初始化流程:
context: async ({ req }) => { const loaderAsset = new LoaderAsset(modelAsset); // 手动执行初始化,绑定上下文 await loaderAsset.initialize({ context: { req }, // 传入当前请求上下文 dataSources: {}, // 若有其他数据源可传入 }); return { dataSources: { loaderAsset, } } },
注意:此方法属于临时 hack,后续版本可能出现新的兼容问题,不推荐长期使用。
内容的提问来源于stack exchange,提问作者Mehran Ishanian

