Node.js GraphQL查询MongoDB嵌套文档数组的Resolver方案
GraphQL嵌套数组关联字段查询报错解决方案
问题背景
GraphQL初学者边学习边搭建应用时,需要编写Resolver适配Schema查询逻辑,核心需求为解析feedformula: [feeditemSchema]数组中存储的关联引用对象,正确获取feeding字段对应的Farmintake集合关联记录。
当前故障现象:
- 仅查询
feeding._id时GraphQL请求可正常返回结果 - 查询
feeding下的name属性时,系统抛出Cannot return null for non-nullable field Farmintake.name错误,返回结果中feedformula数组项全部为null
相关代码与报错信息
可正常执行的查询语句
query{ feeds{ _id feedname feedtime feedformula{ feeding{ _id } qty mesunit description remarks } creator{ email } } }
触发报错的查询语句及返回
query{ feeds{ _id feedname feedtime feedformula{ feeding{ _id name } qty mesunit description remarks } creator{ email } } }
报错返回内容:
{ "errors": [ { "message": "Cannot return null for non-nullable field Farmintake.name.", "locations": [ { "line": 81, "column": 8 } ], "path": [ "feeds", 0, "feedformula", 0, "feeding", "name" ] }, { "message": "Cannot return null for non-nullable field Farmintake.name.", "locations": [ { "line": 81, "column": 8 } ], "path": [ "feeds", 0, "feedformula", 1, "feeding", "name" ] }, { "message": "Cannot return null for non-nullable field Farmintake.name.", "locations": [ { "line": 81, "column": 8 } ], "path": [ "feeds", 0, "feedformula", 2, "feeding", "name" ] } ], "data": { "feeds": [ { "_id": "62b417a91797e5053c2d58a3", "feedname": "Dana", "feedtime": "Evening", "feedformula": [ null, null, null ], "creator": { "email": "auzrimfa@gmail.com" } } ] } }
MongoDB Schema定义
const feeditemSchema = new Schema({ feeding: {type: Schema.Types.ObjectId,ref: 'Farmintake'}, mesunit: {type: String}, qty: { type: Number }, description:{ type: String }, remarks:{ type: String } }); const feedSchema = new Schema({ feedname:{ type: String }, feedtime:{ type: String }, feedformula:[ feeditemSchema ], description:{ type: String }, creator: {type: Schema.Types.ObjectId,ref: 'User'} },{ timestamps: true }); const farmintakeSchema = new Schema({ name:{type: String,required: true}, itmtype:{type: String,required: true}, madein:{type: String,required: true}, mesunit:{type: String}, usage:{type: String}, remarks:{type: String}, creator: {type: Schema.Types.ObjectId,ref: 'User'} },{ timestamps: true} ); module.exports = mongoose.model('Feed', feedSchema ); module.exports = mongoose.model('Farmintake',farmintakeSchema);
现有错误Resolver代码
const transformFeed = feed =>{ return { ...feed._doc, _id: feed.id, feeding: farmintake.bind(this, feed.feedformula.feeding), creator: user.bind(this,feed.creator) }; }; const farmintake = async farmintakeId => { try{ const farmintake = await Farmintake.findById(farmintakeId); return { ...farmintake._doc, _id:farmintake.id }; }catch (err) { throw err; } };
问题根因
- 现有Resolver错误在
feed根层级定义feeding字段解析逻辑,但feeding实际是feedformula数组下每个子项的字段,不存在于feed根节点 feed.feedformula是数组类型,直接访问feed.feedformula.feeding得到的值为undefined,传入Farmintake.findById后无法查询到对应记录,返回null。而Farmintake.name为非空字段,直接触发空值报错- 未针对嵌套数组
feedformula的每个子项编写字段解析逻辑,导致数组项无法正确返回关联数据
正确Resolver实现方案
严格按照GraphQL Schema的字段层级编写解析逻辑,跨层级绑定字段是这类嵌套关联报错的核心诱因。
// 单条Feed数据转换 const transformFeed = feed => { return { ...feed._doc, _id: feed.id, // 遍历处理feedformula数组每一项,根层级无需定义feeding字段 feedformula: feed.feedformula.map(item => transformFeedItem(item)), creator: user.bind(this, feed.creator) }; }; // 新增feedformula子项转换方法 const transformFeedItem = feedItem => { return { ...feedItem, _id: feedItem._id.toString(), // feeding字段属于feedformula子项,在此处绑定对应ObjectId feeding: farmintake.bind(this, feedItem.feeding) }; }; // Farmintake关联查询方法,新增空值判断避免无匹配数据时报错 const farmintake = async farmintakeId => { try{ if (!farmintakeId) return null; const res = await Farmintake.findById(farmintakeId); if (!res) return null; return { ...res._doc, _id: res.id, creator: user.bind(this, res.creator) }; }catch (err) { throw err; } }; // User关联查询方法,保持原有逻辑即可,此处补全参考实现 const user = async userId => { try { if (!userId) return null; const res = await User.findById(userId); if (!res) return null; return { ...res._doc, _id: res.id }; } catch (err) { throw err; } };
优化方案(Mongoose环境适用)
使用Mongoose内置的populate方法做关联预填充,可省去手动编写异步绑定函数的步骤,feeds查询Resolver示例:
// feeds根查询Resolver feeds: async () => { try { const feeds = await Feed.find() .populate('creator') .populate('feedformula.feeding'); return feeds.map(feed => ({ ...feed._doc, _id: feed.id })); } catch (err) { throw err; } }
populate会自动将路径上存储的ObjectId替换为对应集合的匹配文档,大幅简化嵌套关联的解析逻辑。
内容的提问来源于stack exchange,提问作者Arif Khan
相关产品推荐
相关产品推荐

