Mongo schema for read books and followers

I have a users collections and a books collection.
A user should be able to record if (and when) they read a book.
A user should be able to follow an other user too.

As a user I want to be able to list the books I’ve read, sorted by read date and filterable by book category and/or author. I want this list to be paginated.

Also on a book’s page I wan’t to list who read it in the users I’m following.

How would you store the « has read » information and how would you query this with meteor publish/subscribe ?

Thanks, I can’t find the good way to do all this. Is there a topic or a blog post that covers this questions ?

The simplest way would be to have a “readBy” array on each book with the the userIds of eveyone who has read it. Then, to find which books a specific user has read, you could do Books.find({readBy:userId}). In Mongo, if the field is an array, it will count as a match if any of items matches the query.

For a personal project that will work just fine, but for a big site it might not be the most performance friendly I think. Then maybe you could take the “denormalized” approach and also save an array on each user with the ids of the books they have read.

Or maybe it’s okay as long as you index it? I’m not a Mongo performance expert. Maybe there are other ways too.

Thanks a lot. But I have to store also the date when the user read the book. And I have to sort books by this date on the user’s page. Your approach can’t do this am I right ?
I thought of having a readBy object (not array) on each book with keys being userIds of eveyone who has read it and value being the date. I can then find books read by one user sorted by date.
But it doesn’t help me to show on a book’s page the list of my friends who read it.
My site will have around 1000 books marked as read daily and each user will soon have more than 1000 books in his list (in fact it is not books but it is the best simple example I found)

You could have an array like readBy:[{userId,Date},{userId,Date}], and do Books.find({“readBy.userId”:userId}). Then sort them by date using Underscore.

I really don’t know about the performance though. Maybe it’s ok, maybe it’s not? :sweat_smile:

Or maybe it’s better like you said, and have readBy be an object with userIds as keys. Then you could do Books.find({[“readBy.”+userId]:{$exists:true}}). I don’t know if Mongo would ve able to index that though?

Have another collection called ReadBooks and store the bookId and userId. Then query it by userId to get a list of the books they read. You could store more info than the bookId (and avoid doing another lookup) or you can do another lookup to make sure the book info (from books collection) didn’t change.

Users
Books
ReadBooks

You’re right I thinks it’s a bad idea for indexing.

And the date. But then how can I filter my list of read books on books characteristics ? (author, style…) I’ll have to store this information in the readBooks collection ?

Also check out the MongoDB Aggregation Pipeline (in version 3.2+), which supports the equivalent of SQL’s left join. That makes it easy to run performant queries on large collections (assuming you get your indexing right).

Also, look at the reywood:publish-composite package, which provides reactive collection joins even on older versions of MongoDB (but watch performance!).

Finally, check out jcbernack:reactive-aggregate, which combines the aggregation pipeline with reactivity.

1 Like

Or what about Grapher?


It’s like GraphQL for Meteor+Mongo. It also uses Mongo aggregations under the hood.

1 Like

I’ll look at it. Is it used in production on any serious app ?

I’ll try reactive-aggregate thanks

1 Like

I guess it depends if you’re using a publication or a method to grab the data. In a method you could sort that logic out. Or use an aggregation with rob mentions.

Well, I need reactivity. I have to use a publication