Solo Solo - 3 months ago 43
Node.js Question

Denormalized schema?

Mongoose schema:

const postSchema = new mongoose.Schema({
title: { type: String },
content: { type: String },

comments: {
count: { type: Number },
data: [{
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
content: { type: String },
created: { type: Date },

replies: [{
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
content: { type: String },
created: { type: Date },
}]
}]
}
})





I don't want to normalize comments and replies because:


  • I will always need post comments together (read in a single go)

  • I will never use comments to query or sort anything

  • It's so much easier to create, update and delete post with comments (document locking)






If I normalized it, I would have to:


  • Get post from database

  • Get comments from database

  • Get replies for each comment






This is one of the biggest strengths of MongoDB: to group your data specifically to suit your application data needs but GraphQL and Relay.js doesn't seem to support that?

Question:

Is there a way to get comments nested inside of a single post object (or at least get replies nested inside of a single comment) in order to get the whole thing in a single read?




GraphQL schema:

const postType = new GraphQLObjectType({
name: 'Post',
fields: () => ({
id: globalIdField('Post'),
title: { type: GraphQLString },
content: { type: GraphQLString },

comments: {

// ?????

}
}),
interfaces: [nodeInterface],
})





Update:



const postType = new GraphQLObjectType({
name: 'Post',
fields: () => ({
id: globalIdField('Post'),
title: { type: GraphQLString },
content: { type: GraphQLString },

commentCount: { type: GraphQLInt },
comments: { type: new GraphQLList(commentType) }
}),
interfaces: [nodeInterface]
})





const commentType = new GraphQLObjectType({
name: 'Comment',
fields: () => ({
content: { type: GraphQLString },
created: { type: GraphQLString },
author: {
type: userType,
resolve: async (comment) => {
return await getUserById(comment.author)
}
},
replies: {
type: new GraphQLList(commentType),
resolve: (comment) => {

// Log is below
console.log(comment)

// Error only occurs if I return it!
return comment.replies
}
}
})
})





Log (
comment
):

{
author: 'user-1',
created: '05:35'
content: 'Wow, this is an awesome comment!',
replies:
[{
author: 'user-2',
created: '11:01',
content: 'Not really..',
},
{
author: 'user-1',
created: '11:03',
content: 'Why are you so salty?',
}]
}

Answer

This is one of the biggest strengths of MongoDB: to group your data specifically to suit your application data needs but GraphQL and Relay.js doesn't seem to support that?

GraphQL does support what you're trying to do. Before going into the details, we need to keep in mind that GraphQL is about exposing data, NOT about how we store data. We can store in whatever way we like - normalized or not. We just need to tell GraphQL how to get the data that we define in the GraphQL schema.

Is there a way to get comments nested inside of a single post object (or at least get replies nested inside of a single comment) in order to get the whole thing in a single read?

Yes, it's possible. You just define the comments as a list. However, GraphQL does not support arbitrary type of field in a GraphQL object type. Therefore, we need to define separate GraphQL types. In your mongoose schema, comments is an object with count of comments and the comments data. The data property is a list of another type of objects. So, we need to define two GraphQL objects - Comment and CommentList. The code looks like below:

const commentType = new GraphQLObjectType({
  name: 'Comment',
  fields: () => ({
    author: { type: GraphQLString },
    content: { type: GraphQLString },
    created: { type: GraphQLString },
    replies: { type: new GraphQLList(commentType) },
  }),
});

const commentListType = new GraphQLObjectType({
  name: 'CommentList',
  fields: () => ({
    count: {
      type: GraphQLInt,
      resolve: (comments) => comments.length,
    },
    data: {
      type: new GraphQLList(commentType),
      resolve: (comments) => comments,
    },
  }),
});

const postType = new GraphQLObjectType({
  name: 'Post',
  fields: () => ({
    id: globalIdField('Post'),
    title: { type: GraphQLString },
    content: { type: GraphQLString },

    comments: {
      type: commentListType,
      resolve: (post) => post.comments,
    }
  }),
  interfaces: [nodeInterface],
});

I don't want to normalize comments and replies because:

  • I will always need post comments together (read in a single go)
  • I will never use comments to query or sort anything
  • It's so much easier to create, update and delete post with comments (document locking)

Defining post and comment GraphQL object types in the above way let you:

  • read all post comments together
  • not to use comment to query, as it does not even have id field.
  • implement comments manipulation in whatever way you like with your preferred database. GraphQL has nothing to do with it.