MongoDB RESTful API structuring - javascript

So, I'm trying to create a RESTful API that works similar to reddit, where it has users, topics(subreddits), posts and comments. For that I'm trying to use Node.js and MongoDB.
This is the github repo so far: https://github.com/edmassarani/reddit-clone
The only problem is I don't know how to structure the deletion of documents and it's "dependencies" because a user might own a topic, that topic has posts, those posts have comments and those comments have authors, so how would I go about deleting a user without leaving behind a topic with no owner or a post with no topic and so on? Would it be easier if I were to use a relational database?

I can see on your Github repo that your structured your models like in a relational database (note : you named relational db as 'SQL database' on your question) with normalized data models :
Example :
In Topic.js, you refer Posts with reference :
posts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post',
},
],
It certainly works like it, but with NoSQL and specially with MongoDB you have possibility to embed documents into another document.
Why do not embed Post schema directly into Topic like it :
posts: [
{
title: {
type: String,
required: true,
},
body: {
type: String,
required: true,
},
...
upvotes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
],
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
},
],
},
],
As I can understand, if a Topic is deleted, all Posts related to this Topic have to be deleted.
With embedded schema, you have only one deletion to do.
I don't know if in your context embedded is the best solution (because of perfs), but you have two solutions :
Use different schema (your models files) and you have to manually
delete in cascade (exemple : when you delete a Topic, you have to
find all Posts references into the Topic and delete them...)
Refactoring and embed : with it, deleting the Topic is also deleting Comments.
See mongodb official docs for more information on embedded schema : https://docs.mongodb.com/manual/core/data-model-design/

Related

Sequelize Many-to-many querying model

Have some troubles with querying data.
Have 2 models with many to many relationships.
1st - S3FileData, 2nd - Playlists, they are connected through PlaylistContent table.
Also S3FileData is connected with User with User has-many playlists.
I need to query S3Files which belongs to user, but are not presented in user playlist.
I have playlist Id,userId.
Also I need offset and limit methods, so I tried to query this using findAndCountAll
But got no luck with it.
Would be very appreciate for any help :)
You will require include(joins in SQL) that will connect all these tables.
Something like this but first you will need to intertwine your models(db tables) which will look something like this:
User.belongsToMany(Profile, { through: Grant });
Profile.belongsToMany(User, { through: Grant });
User.hasMany(Grant);
Grant.belongsTo(User);
Profile.hasMany(Grant);
Grant.belongsTo(Profile);
Reference:
https://sequelize.org/master/manual/advanced-many-to-many.html
Now, Once you are through the joins you'll require to use those joins using include keyword in your findAll (or findAllAndCount as per the requirement):
Some basic code (You will need to try and tweak accordingly, this is rough code):
S3Files.findAll({
include: [
{
model: user,
attributes: ['some columns']
include: {
model: userPlaylist,
attributes: ['some columns'],
required: false
},
where: {[Op.and]: Sequelize.where(Sequelize.col('userPlaylist.userId'), 'is not' null)},
attributes: ['some columns']
]
});
lastly for offset and limit you'll require basic SQL logic of LIMIT and OFFSET
sample snippet from official docs for offset and limit:
Project
.findAndCountAll({
where: {
title: {
[Op.like]: 'foo%'
}
},
offset: 10,
limit: 2
})
Reference for the same:
https://sequelize.org/v5/manual/models-usage.html

group and collect document based on field in mongodb

I have notifications collection with this schema
_const notificationsSchema = new Schema({
_from: {
type: Schema.ObjectId, ref: 'User',
},
_to: {
type: Schema.ObjectId, ref: 'User',
},
_target: String,
type: {
type: String,
},
createdAt: {
type: Date,
default: Date.now,
},
});
How to group notifications like facebook " and 50 others liked your post"?
How to retrieve this shape of data, for the documents that has same _parent?
[
{
_id: "",
_to: "",
_parent: "",
lastThreeUsers: [_ from, ...],
count: 50
}
]
This is a very open ended response. There is no one right answer. I have provided both a naive and ideal approach to the problem. You may think of another, better way.
You would need to have a something along the lines of a "Likes" collection where you store all likes from users where each like has a reference to the post being liked. Then you would have to perform an aggregation on the likes collection to get all the likes where the postId is equal to the id of the post you want total likes for.
First attempt that.
Then a better, "real world" approach is to also add a "likesCount" (call it what you want) property onto Post documents that keeps tracks of the likes on the post. Update the likesCount each time a user likes/dislikes that post. This way you won't actually have to search through the "Likes" collection over and over again everytime your have to show that data.

Mongoose - Updating a referenced Document when saving

If I have a Schema which has an Array of references to another Schema, is there a way I can update both Documents with one endpoint?
This is my Schema:
CompanySchema = new Schema({
addresses: [{
type: Schema.Types.ObjectId,
ref: 'Address'
}]
});
I want to send a Company with the full Address object to /companies/:id/edit. With this endpoint, I want to edit attributes on Company and Address at the same time.
In Rails you can use something like nested attributes to do one big UPDATE call, and it will update the Company and update or add the Address as well.
Any idea how would you do this in Mongoose?
Cascade saves are not natively supported in Mongoose (issue).
But there are plugins (example: cascading-relations) that implement this behavior on nested populate objects.
Take in mind that mongodb is not a fully transactional database, and the "big save" is achieved with various insert()/update() op calls and you (or the plugin) have to handle errors and rollback.
Example of cascade save:
company.save()
.then(() => Promise.all(company.addresses.map(address => {
/* update fkeys if needed */
return address.save()
}))
.catch(err => console.error('something went wrong...', err))

Sails.js Waterline query by association

I'm developing and app with Sails.js and using Waterline orm for db. I'm developing functionality for users to do friend requests and other similar requests to each other. I have following URequest model for that:
module.exports = {
attributes: {
owner: {
model: 'Person'
},
people: {
collection: 'Person'
},
answers: {
collection: 'URequestAnswer'
},
action: {
type: 'json' //TODO: Consider alternative more schema consistent approach.
}
}
};
Basically owner is association to Person who made the request and people is one-to-many association to all Persons who the request is directed. So far fine.
Now I want to have a controller which returns all requests where certain user is involved in meaning all requests where user is either in owner field or in people. How I do query like "give me all rows where there is association to person P" ? In other words how I ca know which URequest models have association to a certain Person?
I tried something like this:
getRequests: function (req, res) {
var personId = req.param('personId');
URequest.find().where({
or: [
{people: [personId]}, //TODO: This is not correct
{owner: personId}
]
}).populateAll().then(function(results) {
res.json(results);
});
},
So I know how to do the "or" part but how do I check if the personId is in people? I know I should somehow be able to look into join-table but I have no idea how and couldn't find much from Waterline docs relating to my situation. Also, I'm trying to keep this db-agnostic, though atm I'm using MongoDB but might use Postgres later.
I have to be honest this is a tricky one, and, as far as I know what you are trying to do is not possible using Waterline so your options are to write a native query using query( ) if you are using a sql based adapter or native otherwise, or try doing some manual filtering. Manual filtering would depend on how large of a dataset you are dealing with.
My mind immediately goes to reworking your data model a bit, maybe instead of a collection you have a table that stores associations. Something like this:
module.exports = {
attributes: {
owner: {
model: 'URequest'
},
person: {
model: 'Person'
}
}
Using the sailsjs model methods (like beforeCreate) you could auto create these associations as needed.
Good Luck, I hope you get it working!

Create multiple user contracts in Meteor

I want to create a system where multiple contracts can be created belonging to different users.
In Django, Ruby, etc., I would create a model Contract with field user = models.ForeignKey(settings.AUTH_USER_MODEL) but I don't know how to do this in Meteor.
Is it better to make a schema with
Schema.User = new SimpleSchema({
contracts: {
type: [Object],
},
"contracts.$.start_date": {
type: Date,
},
"contracts.$.end_date": {
type: Date,
},
"contracts.$.salary": {
type: Number,
}
});
or something like that? And then use meteor-autoform to create these? It seems very difficult to make objects relational in Meteor.
MongoDB isn't a relational database and so you'll need to manually handle relationships yourself (using multiple queries). Traditionally a Mongo document would use embedding whenever possible and use separate collections when necessary. See here for more information:
MongoDB relationships: embed or reference?
However, Meteor throws a spanner in the works since the publish model can only publish top-level documents, and there's little support for document hierarchies when writing templates etc.
Therefore the normal approach under Meteor is to create table for each collection and to have a records refer to other records using an ID:
Schema.User = new SimpleSchema({
contracts: {
type: [String],
},
...
});
Schema.Contract = new SimpleSchema({
user: {
type: String,
index: true
},
...
});
Although this will result in you having to do multiple queries, the structure will work very will with Meteor's design philosophies.

Categories