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 created two model users and post where users reference post.
This is my user model.
var mongoose = require("mongoose");
mongoose.connect("mongodb://localhost:27017/demo",{ useNewUrlParser: true });
var Post = require('./post');
var UserSchema = mongoose.Schema({
username: String,
password: String,
posts:[{
type:mongoose.Schema.Types.ObjectId,
ref: Post
}] });
This is my post model
var postSchema = mongoose.Schema({
title:String,
content: String,
likes:Number
});
When I create and save a new post to a specific user I get an id in the post field of the user model. like this:-
{
posts: [
60e33a2d18afb82f8000d8f0,
],
_id: 60d9e931b5268920245c27f0,
username: 'user1',
password: '1234',
__v: 5
}
How do I access and display the contents of the post field?
Thanks!
You can get and display Reference data in MongoDB with Aggregate.
Like:
await User.aggregate([
{
$match: { _id: ObjectId(user._id)}
},
{
$lookup: { from: "Posts", localField: "post", foreignField: "_id", as: "posts" }
},
{
$unwind: { path: "$users", preserveNullAndEmptyArrays: true,}
}, {
$project: {
" Now you can select here whatever data you want to show "
}
}
])
PS: If you have any confusion please do comment.
Thanks
Mongoose has a powerful alternative to using aggregate. You can do this with populate .
Use it like this 👇 this will populate the ids inside the posts field with the actual post documents.
User.findById(id).populate('posts')
// Full example
try {
const user = await User.findById(id).populate('posts').exec();
for (let post of user.posts) {
console.log(post.content);
}
} catch (ex) {
console.error(ex);
}
If I may, a small suggestion on how to setup your document model or just to show you an alternative. Don't reference the Posts inside your User Document. Do it the other way round and reference the User inside your Post Document.
const postSchema = mongoose.Schema({
title : String,
content : String,
likes : Number,
user : { type:mongoose.Schema.Types.ObjectId, ref: 'User' }
});
// GET ALL POSTS FROM ONE USER
Post.find({user:id});
This way you don't need to use populate when trying to show all posts of 1 user.
I have a post route that receives data from a PUT request in an express app that aims to update a mongoose document based on submitted form input. The "Base" model is Profile, and I have two discriminator models Helper and Finder that conditionally add fields to the Profile schema (see below for details).
Thus, req.body.profile will contain different fields depending on the discriminator it's associated with, but will always contain the fields (username, email city, accountType) present in the "base" model, Profile.
Before I send my PUT request, an example of a document in Profile looks like this:
{ jobTitle: '',
lastPosition: '',
email: '',
city: '',
accountType: 'helper',
_id: 5c77883d8db04c921db5f635,
username: 'here2help',
__v: 0 }
This looks good to me, and suggests that the model is being created as I want (with base fields from Profile, and those associated with the Helper model - see below for models).
My POST route then looks like this:
router.put("/profile/:id", middleware.checkProfileOwnership, function(req, res){
console.log(req.body.profile);
Profile.findOneAndUpdate(req.params.id, req.body.profile, function(err, updatedProfile){
if(err){
console.log(err.message);
res.redirect("/profile");
} else {
console.log(updatedProfile);
res.redirect("/profile/" + req.params.id);
}
});
});
The information I receive from the form (console.log(req.body.profile)) is what I expect to see:
{ accountType: 'helper',
username: 'here2help',
email: 'helpingU#me.com',
city: 'New York',
jobTitle: 'CEO',
lastPosition: 'sales rep'}
However, after updating the document with req.body.profile in Profile.findOneAndUpdate(), I do not see my returned document updated:
console.log(updatedProfile)
{ jobTitle: '',
lastPosition: '',
email: 'helpingu#me.com',
city: 'New York',
accountType: 'helper',
_id: 5c77883d8db04c921db5f635,
username: 'here2help',
__v: 0 }
So, the fields that are defined in my 'Base' model (ie those defined in ProfileSchema - see below) are being updated (e.g. city), but those that are in my discriminators are not - see below.
The updated information is clearly present in req, but is not propagated to the Profile model - How can this be?
I've also tried using findByIdAndUpdate but I get the same result.
Here are the Schemas I'm defining:
Profile - my "base" schema:
var mongoose = require("mongoose");
var passportLocalMongoose = require("passport-local-mongoose");
var profileSchema = new mongoose.Schema({
username: String,
complete: { type: Boolean, default: false },
email: { type: String, default: "" },
city: { type: String, default: "" }
}, { discriminatorKey: 'accountType' });
profileSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("Profile", profileSchema);
Finder
var Profile = require('./profile');
var Finder = Profile.discriminator('finder', new mongoose.Schema({
position: { type: String, default: "" },
skills: Array
}));
module.exports = mongoose.model("Finder");
Helper
var Profile = require('./profile');
var Helper = Profile.discriminator('helper', new mongoose.Schema({
jobTitle: { type: String, default: "" },
lastPosition: { type: String, default: "" }
}));
module.exports = mongoose.model("Helper");
This is my first attempt at using discriminators in mongoose, so it's more than possible that I am setting them up incorrectly, and that this is the root of the problem.
Please let me know if this is unclear, or I need to add more information.
It matters what schema you use to query database
Discriminators build the mongo queries based on the object you use. For instance, If you enable debugging on mongo using mongoose.set('debug', true) and run Profile.findOneAndUpdate() you should see something like:
Mongoose: profiles.findAndModify({
_id: ObjectId("5c78519e61f4b69da677a87a")
}, [], {
'$set': {
email: 'finder#me.com',
city: 'New York',
accountType: 'helper',
username: 'User NAme', __v: 0 } }, { new: true, upsert: false, remove: false, projection: {} })
Notice it uses only the fields defined in Profile schema.
If you use Helper, you would get something like:
profiles.findAndModify({
accountType: 'helper',
_id: ObjectId("5c78519e61f4b69da677a87a")
}, [], {
'$set': {
jobTitle: 'CTO',
email: 'finder#me.com',
city: 'New York',
accountType: 'helper ',
username: 'User Name', __v: 0 } }, { new: true, upsert: false, remove: false, projection: {} })
Notice it adds the discriminator field in the filter criteria, this is documented:
Discriminator models are special; they attach the discriminator key to queries. In other words, find(), count(), aggregate(), etc. are smart enough to account for discriminators.
So what you need to do when updating is to use the discriminator field in order to know which Schema to use when calling update statement:
app.put("/profile/:id", function(req, res){
console.log(req.body);
if(ObjectId.isValid(req.params.id)) {
switch(req.body.accountType) {
case 'helper':
schema = Helper;
break;
case 'finder':
schema = Finder;
break;
default:
schema = Profile;
}
schema.findOneAndUpdate({ _id: req.params.id }, { $set : req.body }, { new: true, upsert: false, remove: {}, fields: {} }, function(err, updatedProfile){
if(err){
console.log(err);
res.json(err);
} else {
console.log(updatedProfile);
res.json(updatedProfile);
}
});
} else {
res.json({ error: "Invalid ObjectId"});
} });
Notice, above is not necessary when creating a new document, in that scenario mongoose is able to determine which discriminator to use.
You cannot update discriminator field
Above behavior has a side effect, you cannot update the discriminator field because it will not find the record. In this scenario, you would need to access the collection directly and update the document, as well as define what would happen with the fields that belong to the other discriminator.
db.profile.findOneAndUpdate({ _id: req.params.id }, { $set : req.body }, { new: true, upsert: false, remove: {}, fields: {} }, function(err, updatedProfile){
if(err) {
res.json(err);
} else {
console.log(updatedProfile);
res.json(updatedProfile);
}
});
Please add option in findOneAndUpdate - { new: true };
In Moongose findOneAndUpdate() Method have four parameters
like
A.findOneAndUpdate(conditions, update, options, callback) // executes
And you need to execute like this
var query = { name: 'borne' };
Model.findOneAndUpdate(query, { name: 'jason bourne' }, options, callback)
or even
// is sent as
Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options, callback)
This helps prevent accidentally overwriting your document with { name: 'jason bourne' }.
I am having problem managing the states of different types of users using Passport.js in Express.js 4.x.
I have 3 kinds user collections in my mongodb database
1. Member (has his own profile page)
2. Operator (has his own dashboard)
3. Admin (handles the backend)
I have created their separate Login/Registration systems. But only member seems to work, and the others don't. I have even written different sets of login/registration strategies for each user.
Like for the member passport.use('signup') and passport.use('login').
for operator passport.use('op-signup') and passport.use('op-login') and so on.
What I think is that I am not using the correct approach for handling users, means the collections don't need to be separated but role based in a single collection. Right ?
Here is the current mongoose models I have right now;
// Member Schema
var MemberSchema = new Schema({
username: String,
password: String,
name: { first: String, last: String },
locality: String,
// and other attributes
});
module.exports = mongoose.model('Member', MemberSchema);
// OperatorSchema
var OperatorSchema = new Schema({
username: String,
password: String,
name: { first: String, last: String },
officeAddress: String,
privatePhone: Number,
// and other attributes related to the operator
});
module.exports = mongoose.model('Operator', OperatorSchema);
Is the above approach correct or like this ?
var UserSchema = new Schema({
username: String,
password: String,
roles: {
member: { type: Schema.Types.ObjectId, ref: 'Member' },
operator: { type: Schema.Types.ObjectId, ref: 'Operator' },
admin: { type: Schema.Types.ObjectId, ref: 'Admin' }
}
});
module.exports = mongoose.model('User', UserSchema);
// and then plug the sub models to this parent one
// Member Schema
var MemberSchema = new Schema({
_user: { type: Schema.Types.ObjectId, ref: 'User' },
name: { first: String, last: String },
locality: String,
// and other attributes
});
module.exports = mongoose.model('Member', MemberSchema);
// OperatorSchema
var OperatorSchema = new Schema({
_user: { type: Schema.Types.ObjectId, ref: 'User' },
name: { first: String, last: String },
officeAddress: String,
privatePhone: Number,
// and other attributes related to the operator
});
module.exports = mongoose.model('Operator', OperatorSchema);
I am quite confused here and in a stuck situation, because when a user state is managed in session after login, the user object is exposed to the request object, and so it can only handle one type of user at a time, and may be member, operator and admin can't log in at the same time from the same browser.
So how do I manage all of these user as different instances in the browser ?
I am quite a newbie in Node.js and coming from a PHP background where managing user states was a breeze :)
What i would do is to add plugins, because you are duplicating username and password field, it is very redundant
models/plugins/member.js
module.exports = function(schema) {
schema.add({
// All the appropriate fields that your member schema need
role: String,
});
}
models/user.js
var member = require(./plugins/member);
var UserSchema = mongoose.Schema({
username: String,
password: String
});
UserSchema.plugins(member);
Later on when you want to check which user could access to which route, use middleware to check it
create this in your passport configuration
exports.requireRole = function(role) {
return function(req, res, next) {
if (req.user && req.user.role === role) next();
else
res.send(404);
}
}
In your route later
app.get('/profile', requireRole('member'), function(req, res) {
// do whatever you want to do
});
app.get('/dashbord', requireRole('operator'), function(req, res) {
// do whatever you want to do
});
There are a lot of ways to implement different access level to a user. This method is one of many.
The best solution would be to use schema inhertiance. That is why we use an ORM like mongoose.
var VehicleSchema = mongoose.Schema({
make : String,
}, { collection : 'vehicles', discriminatorKey : '_type' });
var CarSchema = VehicleSchema.extend({
year : Number
});
var BusSchema = VehicleSchema.extend({
route : Number
})
var Vehicle = mongoose.model('vehicle', VehicleSchema),
Car = mongoose.model('car', CarSchema),
Bus = mongoose.model('bus', BusSchema);
var accord = new Car({
make : 'Honda',
year : 1999
});
var muni = new Bus({
make : 'Neoplan',
route : 33
});
accord.save(function(err) {
muni.save(function(err) {
// vehicles are saved with the _type key set to 'car' and 'bus'
});
})
At this point in MongoDB you will have documents similar to this
{ "_type" : "car", "make" : "Honda", "year" : 1999, "_id" : ObjectId("5024460368368a3007000002"), "__v" : 0 }
{ "_type" : "bus", "make" : "Neoplan", "route" : 33, "_id" : ObjectId("5024460368368a3007000003"), "__v" : 0 }
Source
when querying
Vehicle.find({}, function(err, vehicles) {
console.log(vehicles[0]); // vehicles[0] instanceof Car === true
console.log(vehicles[1]); // vehicles[1] instanceof Bus === true
});
Checkout source / more examples by looking at briankircho little cheatsheet enter link description here
I am working on a project using node.js mongodb. My schema somewhat looks like:
var Doctor = new Schema({
email : String,
password : String,
Dname : String,
blockAppoint:[{
day:String,
sslot:[Number],
eslot:[Number],
address:String,
status1:String
}]
});
If I take all these values as input from user, I can't figure out how to insert into the array of nested objects.
If my post api looks like:
var doc = new Doctor({
email : req.body.email,
password : req.body.password,
name : req.body.Dname,
blockAppoint:{
status1:req.body.xx,
day:req.body.day,
sslot:req.body.sslot,
eslot:req.body.eslot,
address:req.body.address
}
});
doc.save(function(err){
if(err){
res.send(err);
return;
}
res.json({
success: true,
message: 'doctor has been added!'
});
});
I'm able to input just one entry into the database. Does anyone know how do I change my api code so as to be able to take read input into my database.
Try adding the values to an array first using the push() method:
var sslot = [], eslot = [], blockAppoint = [];
sslot.push(req.body.sslot);
eslot.push(req.body.eslot);
blockAppoint.push({
status1: req.body.xx,
day: req.body.day,
sslot: sslot,
eslot: eslot,
address: req.body.address
});
var doc = new Doctor({
email: req.body.email,
password: req.body.password,
name: req.body.Dname,
blockAppoint: blockAppoint
});