The biggest problem is I'm totally new to MongoDB. I know how to do it in SQL but am unable to shift my thinking into NoSQL. I have this model:
var accountSchema = new mongoose.Schema({
isPremium: Boolean,
website: []
});
I'm using mongoose so it creates Id, username, and password automatically. My registered user looks like this:
{
_id: ObjectId('5a79c89b59b6042a5d89584b'),
websites: ['a.com', 'b.com', 'c.com'],
username: 'a#a.a',
isPremium: false,
hash:
'a long hash',
salt: 'a long salt',
__v: 0,
};
I want want to write out the websites array. I want to grab only the websites under a certain user. (Don't want others to just see all websites).
How would I do it? Would I pass the userId after a click or make it in a session? And would the 'query' look like?
You can do it like this.
First define your model as a separate module
var mongoose = require('mongoose');
const accountSchema = mongoose.Schema(
{
isPremium: Boolean,
website: []
});
module.exports = mongoose.model('account',accountSchema);
Then you can use this model everywhere in your code
const account = require('yourModulePath');
account.findOne({YouSearchParameters}).
then((account) => {
// let websites = account.website
// Do you logic
})
You also can filter result with .select
account.findOne({YouSearchParameters}).select({ "website": 1, "_id": 0}).then((account)
So you will just get your array of websites
Related
I have created a Notes model having schema as shown below
const notesschema = new Schema({
user :{
type : Schema.Types.ObjectId,
ref : 'User',
required : true
},
problem : {
type : Schema.Types.ObjectId,
ref : 'Problems',
required : true
},
content : {
type : 'string'
}
},{
timestamps : true
})
To show the User his notes for a particular task/problem I am trying to fetch notes and show to him and possibly update if he do some changes and save, The problem is with this schema I dont know how to write <model.findById >API to find notes from my notes model having particular user and specific task/problem.Which I would know the Id of.
With this particular schema , and my current knowledge i would have to write So much code. So if there is any easier way to do this task is welcomed, I was also thinking to change my schema and just placing my user id in my schema instead of whole user and finding notes from my database
edit : as suggested by all the answers we can simply find using user.id which I thought initially would not word as that was just the path, but which stores actually user.id
You create the notes' collection the same way you're doing it,
const notesSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User', // # the name of the user model
required: true
},
problem: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Problem', // # the name of the user model
required: true
},
content: String
})
Then you'll create the model of the notesSchema as follows:
const NoteModel = mongoose.model('Note', notesSchema, 'notes')
export them so you can use them in your controllers:
module.exports = {
NoteModel,
notesSchema
}
or if you're using +es6 modules (think of, if you're using TypeScript):
export default {
NoteModel,
notesSchema
}
This will result in creating the following table (collection) in the database:
Let's think of the following challenges:
To get all the notes:
NoteModel.find({})
To get all the users:
UserModel.find({}) // you should have something like this in your code of course
To get all the problems:
ProblemModel.find({}) // you should have something like this in your code of course
To get all the notes of a user:
NotesModel.find({ user: USER_ID })
To search for notes by problems:
NotesModel.find({ problem: PROBLEM_ID })
Now, the above is how you do it in mongoose, now let's create a RESTFUL controller for all of that: (assuming you're using express)
const expressAsyncHandler = require('express-async-handler') // download this from npm if you want
app.route('/notes').get(expressAsyncHandler(async (req, res, next) => {
const data = await NotesModel.find(req.query)
res.status(200).json({
status: 'success',
data,
})
}))
The req.query is what's going to include the search filters, the search filters will be sent by the client (the front-end) as follows:
http://YOUR_HOST:YOUR_PORT/notes?user=TheUserId
http://YOUR_HOST:YOUR_PORT/notes?problem=TheProblemId
http://YOUR_HOST:YOUR_PORT/notes?content=SomeNotes
const notesschemaOfUser = await notesschema.findOne({user: user_id});
I have the following Schema with a array of ObjectIds:
const userSchema = new Schema({
...
article: [{
type: mongoose.Schema.Types.ObjectId,
}],
...
},
I will count the array elements in the example above the result should be 10.
I have tried the following but this doesn't worked for me. The req.query.id is the _id from the user and will filter the specific user with the matching article array.
const userData = User.aggregate(
[
{
$match: {_id: id}
},
{
$project: {article: {$size: '$article'}}
},
]
)
console.log(res.json(userData));
The console.log(article.length) give me currently 0. How can I do this? Is the aggregate function the right choice or is a other way better to count elements of a array?
Not sure why to use aggregate when array of ids is already with user object.
Define articles field as reference:
const {Schema} = mongoose.Schema;
const {Types} = Schema;
const userSchema = new Schema({
...
article: {
type: [Types.ObjectId],
ref: 'Article',
index: true,
},
...
});
// add virtual if You want
userSchema.virtual('articleCount').get(function () {
return this.article.length;
});
and get them using populate:
const user = await User.findById(req.query.id).populate('articles');
console.log(user.article.length);
or simply have array of ids:
const user = await User.findById(req.query.id);
console.log(user.article.length);
make use of virtual field:
const user = await User.findById(req.query.id);
console.log(user.articleCount);
P.S. I use aggregate when I need to do complex post filter logic which in fact is aggregation. Think about it like You have resultset, but You want process resultset on db side to have more specific information which would be ineffective if You would do queries to db inside loop. Like if I need to get users which added specific article by specific day and partition them by hour.
I am trying to make a messages schema + routes for my backend. I want that two users can write a message to each other and the message has to be stored for both of them.
I made the the user-model-schema and the user-routes, they are working but I'm stuck with the messaging.
mongoDB should contain the message
how can I manage sending messages?
Here is what I tried so far
messages-route:
var express = require("express");
var User = require("../models/users.js");
var router = express.Router();
const message = require("../models/messages");
router.post("/:recipient", (request, response) => {
User.find({
username: [request.body.sender, request.params.recipient],
// }, {
// message: request.body.message
// }, {
// upsert: false,
// new: true,
})
.then((users) => {
users.forEach((user) => {
user.updateMany({
message: request.body.message
})
})
response.status(200).json(users);
})
.catch((error) => {
response.status(500).json(error);
});
});
module.exports = router;
and my messages-schema:
var mongoose = require("mongoose");
var UserMessage = new mongoose.Schema({
user: { "type": mongoose.Schema.ObjectId, "ref": "User" },
username: String,
view: {
inbox: Boolean,
outbox: Boolean,
archive: Boolean
},
content: {type: String},
read: {
marked: { "type": Boolean, default: false },
date: Date
}
});
var schemaMessage = new mongoose.Schema.ObjectId({
from: String,
to: [UserMessage],
message: String,
created: Date
});
module.exports = mongoose.model("Messages", UserMessage);
I'm very unsure with the schema, I put in some suggestions I found here on stackoverflow.
Thanks!
I'm now making a messages Schema, and i thought of something like this:
const ChatSchema = new mongoose.Schema({
participants: [{type: mongoose.Schema.objectId, ref: "users"}],
last_message: Number,
_id: {type: mongoose.Schema.objectId}
//...
})
Here you could use the same schema to make chat groups, and it will work having a separate document for each message with a field with the chatroom _id, so you could query them
And you might be thinking, why not put all the messages in an array on ChatSchema?
Well, because we don't want the users to recieve all their messages each time they enter a chat.
I know, you might be thinking about something like this to prevent that over-load of data to the client
MessageSchema.find(({ ... })
.sort({ updatedAt: -1 })
.limit(20)
But here you're still recieving all the messages from the database to the server, and users could have even 1000 messages per chat, so an idea that came to my mind was making MessageSchema like this:
const MessageSchema = new mongoose.Schema({
message: String,
chatRoom_id: {type: mongoose.Schema.objectId},
sentBy: {type: mongoose.Schema.objectId, ref: "users"},
seenBy: [{ user: {type: mongoose.Schema.objectId, ref: "users"}, seen: Boolean }],
numberOfMessage: Number
//...
})
And the magic is with numberOfMessage, because you could query the MessageSchema with comparison operators like this:
const currentMessage = 151 //this is the numberOfMessage number of the last message the user saw, and when the user scrolls up the chat you could send the numberOfMessage of the last loaded message.
//To know from wich numberOfMessage start, just use the ``last_message`` field from the chatroom's ``ChatSchema``
const quantityNewMessages = 10 //this is the quantity of new messages you want the user to recieve
MessageSchema.find({
chatRoom_id,
numberOfMessage:
{ $lt: currentMessage , $gte: currentMessage + quantityNewMessages } //we use less than operator because the user will always recieve the last message first, so the previous messages are going to have a numberOfMessage less than this first message
})
And it will return us an array of 10 messages, wich you can concat to the messages array you already have in your frontend.
take note:
With this MessageSchema, all the users will share the same document for each message, you could make a separate document of a message for each user in a chatroom, so each user can delete a message without affecting the others.
I don't recommend you saving a username field, because the user could change it's username and if he does that, you would have to update ALL message documents with that username to the new username, so just leave the ref and populate it
You shouldn't put the document object in the to field, make it a separate document and only save it's ref. But I don't recommend doing it that way either
Don't use users.forEach because it modifies the array, use users.map because it returns a new array and doesn't mutates the original array
I see you have a bug, you are asking the data with the returned variable of the forEach( user => user.update(...) ), it should be map( user => UserSchema.update({ _id: user._id }) )
But still, looping an array and making a call to the DB each time is very expensive and will lag the server, so use something like the ChatSchema I showed you, because you could get the chatroom information, and with the _id of that chatroom, query the newest messages
I've not tested this code, i'm about to do it. If you run into any problem feel free to comment
Does anyone know why the following one-to-many relationship between "users" and "posts" (users can have many posts) is not working? It appears I have setup my mongoose associations correctly, but when a new post is created, not only is it not assigned a user, but the users themselves are also not associated with any posts. I'm not sure what I might be doing wrong here.
If you see the JSON object below, it should have a user value, denoting the user whom created the post. You'll see in the Post Model below, that a user value should be created, but does not.
What am I doing wrong?
Here's the JSON object after creating a new post
{
__v: 0
_id: "587ee8f5a99b1709b012ce8f"
createdAt: "2017-01-18T04:03:01.446Z"
message: "This is my first test post!"
updatedAt: "2017-01-18T04:03:01.446Z"
}
Question: Why is the user field missing from the JSON above despite being created in the Post Model below?
Here's my Post Model:
// Setup dependencies:
var mongoose = require('mongoose');
// Setup a schema:
var PostSchema = new mongoose.Schema (
{
message: {
type: String,
minlength: 2,
maxlength: 2000,
required: true,
trim: true,
}, // end message field
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
},
{
timestamps: true,
}
);
// Instantiate our model and export it:
module.exports = mongoose.model('Post', PostSchema)
Here's my User Model:
// Setup dependencies:
var mongoose = require('mongoose');
// Setup a schema:
var UserSchema = new mongoose.Schema (
{
username: {
type: String,
minlength: 2,
maxlength: 20,
required: true,
trim: true,
unique: true, // username must be unique
dropDups: true,
lowercase: true,
validate: {
validator: function(username) {
var regex = /^[a-z0-9_]+$/i;
return regex.test(username);
},
message: 'Username may contain only letters, numbers or underscores.',
},
}, // end username field
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}],
},
{
timestamps: true,
});
// Instantiate our model and export it:
module.exports = mongoose.model('User', UserSchema)
Here's the Controller that queries the DB:
Note: This is the method that runs when the post form is submitted.
// Grab our Mongoose Models:
var User = require('mongoose').model('User');
var Post = require('mongoose').model('Post');
module.exports = {
// Creates a new post for logged in user:
newPost: function(req, res) {
Post.create(req.body)
.then(function(newPost) {
return res.json(newPost);
})
.catch(function(err) {
return res.json(err);
})
}
};
Does anyone know if my associations are improperly setup and this is why I'm not getting any actual posts or users to show up in their respective fields?
It seems that my server-side controller is firing properly, as the post is actually created. But the associations themselves are not linking up and I'm not sure what I'm doing wrong.
I'm adding just a simple answer below to follow up with the example above. Essentially, #cdbajorin was correct, I was absently thinking there was some automation going on and was not appropriately following through the proper mongoose commands to achieve my desired results.
The solution to my question is as follows:
In the User Model, update the UserSchema posts attribute to be an empty array, instead of a mongoose.Schema.Types.ObjectID, since an object ID is not stored here anyhow and I misunderstood how this works.
The code:
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}],
Instead, should be written simply:
posts: [],
The newPost method, in the server Controller, should be modified as follows (see comments inline for clarification):
newPost: function(req, res) {
// creates new post:
Post.create(req.body)
.then(function(newPost) {
// look up current user based on session ID:
// note: session setup not shown in this example.
User.findById(req.session.userID)
.then(function(user) {
// push new post into users.posts array from model setup**:
user.posts.push(newPost);
user.save();
return res.json(newPost);
})
})
.catch(function(err) {
return res.json(err);
})
This does solve the issue of the new post being generated, and then pushed into a user's posts array (from the UsersSchema).
Though the issue from the initial post is solved, one may question if this is the best use of database management. Storing posts inside of a user, as this example does, can take up a lot of space as users and posts start to add up.
This post ends up being duplicated in the database twice: first, as a document itself in the posts collection, and secondly, as an object in the posts array within the UserSchema.
A better solution is to keep the post as a unique document in the posts collection, but add the userID from the session information to it. Then, if all of user's posts are needed for any reason, a query to the Posts collection, based on the userID, would return all posts with that userID assigned to it. Then, only one copy of the post exists in the DB instead of two.
** Additional Note: Another way to modify the existing document would be to use an instance method, where an actual method would be inserted into the User Model (Schema) file, and called when needed:
For example, inserting the following code before the module.exports line in the UserSchema Model above, allows for convenient access this function when needed:
UserSchema.methods.addPost = function(post) {
this.posts.push(post);
this.save();
return true;
};
To call this instance method from our server Controller, we could re-write our Controller as follows:
User.findById(req.session.userID)
.then(function(user) {
// call our instance method above:
user.addPost(newPost);
return res.json(newPost);
});
The post will be pushed and saved by the instance method, which has been built into the instance object itself.
I am currently trying to learn how to work with NoSQL, coming from a relational database background. In this project, I am using Express with Mongoose.
I am struggling with callbacks as I try to merge two models together, which reference each other. I am trying to edit each item in a group of one model (Ribbits) to contain the attributes of another (Users who posted a Ribbit). Because the call to find the User associated with a Ribbit is asynchronous, I am unable to return the collection of edited Ribbits (with user info).
In my website, I have ribbits (a.k.a. tweets) which belong to users. Users can have many ribbits. In one of my pages, I would like to list all of the ribbits on the service, and some information associated with the user who posted that ribbit.
One solution I found was embedded documents, but I discovered that this is, in my case, limited to showing ribbits which belong to a user. In my case, I want to start by getting all of the ribbits first, and then, for each ribbit, attach info about who posted that.
Ideally, I'd want my schema function to return an array of Ribbit objects, so that I can then render this in my view.
// models/user.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var userSchema = Schema({
username: String,
email: String,
password: String,
name: String,
profile: String,
ribbits: [{
type: Schema.Types.ObjectId,
ref: 'Ribbit',
}]
});
module.exports = mongoose.model('User', userSchema);
// models/ribbit.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
User = require('./user');
var ribbitSchema = Schema({
content: { type: String, maxlength: 140 },
created: { type: Date, default: Date.now() },
owner: { type: Schema.Types.ObjectId, ref: 'User' },
});
ribbitSchema.methods.getOwnerObj = function(cb) {
return User.findOne({ _id: this.owner }, cb);
}
ribbitSchema.statics.getAllRibbits = function(cb) {
this.find({}, function(err, ribbits) {
console.log('Before Transform');
console.log(ribbits);
ribbits.forEach(function(ribbit) {
ribbit.getOwnerObj(function(err, owner) {
ribbit = {
content: ribbit.content,
created: ribbit.created,
owner: {
username: owner.username,
email: owner.email,
name: owner.name,
profile: owner.profile,
}
};
});
});
});
}
module.exports = mongoose.model('Ribbit', ribbitSchema);
If I understand correctly, you can use Mongoose populate method for this scenario:
ribbitSchema.statics.getAllRibbits = function(cb) {
this.find({}).populate('owner').exec(function(err, ribbits){
console.log(ribbits[0].owner)
return cb(err, ribbits);
})
}