Data modeling in mongoose - node.js

I'm creating my website with node.js(express.js) and I have one question about mongodb and mongoose.
This question is how to make schema for my collection? For example I have users on my webiste, and user can subscribe for another user? How to write schema for this?
For now I have something like this:
userSchema = mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true }
});

This line will allow you to add things to the database via the schema. (Very idiomatically, the database will be called 'users', as Mongoose pluralises the first argument for you.)
mongoose.model('user', userSchema);
There are a couple of ways to add things to the database - I use promises, which requires you to call this before you connect (I think it has to be before?)
mongoose.Promise = Promise;
You can then save new instances of the schema:
new User ({
username: 'me',
password: 'dontlooknow',
}).save()
Save returns a promise, so you can follow it with a .then() after if you want. You can do it with callbacks too if you prefer, just look at the docs. http://mongoosejs.com/docs/guide.html.

Related

How to specify the _id field with Mongoose when creating a document in MongoDB?

I have an app where users log in using Firebase Auth and I then store the user details in MongoDB using Mongoose. This works fine. However, the _id of each document is currently random. I would like to specify the _id field as the uid from Firebase which I am already storing separately.
This is my Schema:
const UserSchema = new mongoose.Schema({
email: {
type: String,
},
name: {
type: String,
},
photoUrl: {
type: String,
},
token: {
type: String,
},
uid: {
type: String,
},
})
I have tried seemingly all variations of the below but nothing seems to work:
_id: {
type: mongoose.Schema.Types.ObjectId,
},
No errors are given, just nothing gets written to Mongo (it does if I don't include the _id field). Can anyone see what I'm doing wrong, please?
This should do it, because FB Auth uid is not an ObjectId:
_id: {
type: String
}
Converting _id: String is not such a great idea because mongoose will store it as a string instead of by default as an objectId. Mongoose fetching works efficiently because of objectId's which correspond to their key structure. Firebase id doesnt do that unfortunately. So we cant convert our firebase id to mongoose id.
Another issue is if you want to save it as a foreign key into another collection by the mongoose method type: Schema.Types.ObjectId with ref of another table it will not work.
In my opinion the best way is to use the key of mongoose to create firebase user because you will be dealing with mongoose for all database usage except firebase auth. And firebase id's dont have a strict uid structure. You ca create firebase uid with literally anything.
const uid = new mongoose.Types.ObjectId()._id
const firebaseUser = await admin.auth().createUser({ uid: uid.toString(), email: payload.email, password: payload.password });

Can I access multiple schemas with Mongoose?

could someone please give me a suggestion? My schema example looks like this:
const eventSchema = new Schema({
eventName : String,
date: Date,
location: String,
role: [],
task:[],
});
const userSchema = new Schema({
email: { type: String, unique: true, lowercase: true },
password: String,
eventList: [eventSchema],
});
I'm even wondering about creating a 3rd schema and put it into the eventSchema. Do you think it's possible to work on?
So far, I only access the userSchema through
const ModelClass = mongoose.model('user', userSchema);
module.exports = ModelClass;
Could I somehow export the other schemas and access them directly at the same tiem? How is that done? Thanks a lot in advance!
Multiple Schema's in Mongoose is perfectly possible. The best approach I've seen is to create each schema in a separate file (so you can export each one). Then you can import and consume them anywhere you need.

Displaying content from different mongoose tables on one view

I'm a (relatively inexperienced) Java EE developer who is looking to learn node.js. I'm working with the express framework, mongodb, and the mongoose framework. I've been working on building a simple blog site (just for practice) with an mvc like architecture. It would have would have 4 mongodb collections: post, image, user, comment. The basic Schemas are as follows:
postSchema = mongoose.Schema({
id: Number,
dateCreated, {type: Date, default: Date.now}
title: String,
content: String
});
var Post = mongoose.model('Post', postSchema);
imageSchema = mongoose.Schema({
id: Number,
postId: Number,
path: String
});
var Image = mongoose.model('Image', imageSchema);
userSchema = mongoose.Schema({
id: Number,
username: String,
password: String,
email: String
});
var User = mongoose.model('User', userSchema);
commentSchema = mongoose.Schema({
id: Number,
postId: Number,
userId: Number,
dateCreated: {type: Date, default: Date.now},
content: String
});
var Comment = mongoose.model('Comment', commentSchema);
I want to be able to show a post, an image, comments, and user info all on one page. My issue is that I can't quite figure out how retrieve and send all this data in an asynchronous way. This seems to be what most of the examples I have found do (not necessarily all in one file):
app.get('/', function(res, req) {
Post.findOne(function(err, post) {
if (err) return res.send(500);
res.render('index', post);
});
});
This wouldn't work for me because I would info from the image, comment, and user collections as well. Is there an asynchronous way to do this? If not is there a way to reconfigure what I have so that it could be asynchronous? (I'm trying to get a feel for asynchronous programming.)
Thanks in advance!
Simples way to do this as-is would be to use promises and perform simultaneous async operations:
Post.findOne(id).then(post => {
let postId = post.id;
return Promise.all([
Comments.find({postId}),
Images.find({postId}),
// Not sure what query you need here
User.find(),
post,
]);
}).then(data => {
let [comments, images, users, post] = data;
res.render('index', {comments, images, users, post});
});
In your index template you would have an object with the four properties.
You can perform simultaneous async operations without promises, but I'll leave that for someone else to talk about. I would prefer to work with promises.
Mongoose will also allow you to use other schema definitions as data types as in:
commentSchema = mongoose.Schema({
id: Number,
postId: Post,
userId: User,
dateCreated: {type: Date, default: Date.now},
content: String
});
In this case you can use .populate after some queries in order to perform another query under the hood -- e.g. get the post data for a comment.
Since you are using MongoDB -- a NoSQL Database -- I would look into denormalizing the data and keeping it flat. Firebase, which stores data in a similar structure has a great article on how to store and use denormalized data

Creating dynamic schema in Mongoose using { strict: true }

I have this 2 schemas in mongoose:
The Booking schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bookingSchema = new Schema({
bookingNO: { type: Number, unique: true},
plateNO: String,
startDate: String,
bookedTime: Number,
creator: {type: Schema.Types.ObjectId, ref: 'User'}
});
var Booking = mongoose.model('Booking', bookingSchema);
The User schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = new Schema({
username: String,
password: String,
balance: Number,
bookings: [{type: Schema.Types.ObjectId, ref: 'Booking'}]
});
var User = mongoose.model('User', userSchema);
The problem with this design is:
Some Bookings were NOT created by a user - in which case the 'creator' field in Booking would be empty. Likewise, some Users do NOT necessarily contain a Booking - they may later on.
I was thinking about deleting the creator and bookings fields from the 2 Schemas, and using the { strict: false } option in Mongoose. Would this be the best option ?
If this is the case, I would have to add the 'creator' property to the Booking model, and 'bookings' property to the User, which would then get saved to the DB.
MOST importantly, due to the fact that I've removed the references from the Schema, how do I go about creating the reference in the case of using { strict: false } ?
Thanks in advance.
Unless a field has require:true flag, it can be left empty.
If you have a field which isn't defined in the Mongoose schema but is present in a document in MongoDB, you'll have to use doc.get('field') instead of just doc.field. Similarly for saving doc.set('field', value) and strict:false will be required otherwise it won't persist.
IMO you should have your schema inclusive rather than exclusive. Mongoose is just a wrapper for your data in MongoDB which at its heart is already schemaless.
In your specific case, you can create a booking without specifying a 'creator', because its of type ObjectId Mongoose will simply create the document and will leave that field empty. The user.bookings is a different matter as it is an Array, in which case Mongoose will always default to an empty array, even if it was left undefined when creating the document. In this case, would it be that bad to just leave the empty array there? It still represents the data accurately, where the user simply has no bookings, but are still able to potentially have them. If you explicitly don't want bookings, then yes, you'll either have to deal with strict: false, or manually $unset/delete the field from the documents.

Use mongoose populate to "join" matching subrecords

Using node.js, mongoose (3.5+), mongodb. Have got two collections in the DB:
var AuthorSchema = new mongoose.Schema({
name: { type: String },
});
var StorySchema = new mongoose.Schema({
title: { type: String },
author: { type: type: Schema.Types.ObjectId },
});
What I would like to do is retrieve an author and populate it with a subcollection (say, "stories") that is looked up from Story and match the author. Yes, much like a SQL join.
All of the examples out there work on the AuthorSchema having an array of objectids that reference StorySchema objects - that works fine. But I want to go the opposite direction; partly due to minimizing insert/updates. If I follow the example, adding a new store requires adding a new Story document and updating the Author. I want to just insert a new Story that references the Author.
I suspect that populate() is the right way to go, but can't get it to work. I'm doing something like this:
Author.find({name: 'Asimov').populate({
path: 'stories',
model: 'Story',
match: {'author': this['_id']},
}).exec(function(err, authors) {
console.log(authors);
})
But this doesn't return any stories member in the returned authors. Is this not a populate() solution? Do I really need to structure the schemas differently? Or is there some other feature of mongoose/mongo that would do what I'm looking for.
In the story schema, do this:
author: { type: type: Schema.Types.ObjectId, ref:'Author' }, //or whatever the model name is
then you can run
Story.find({}).populate('author').exec(function(err,stories) {...});

Resources