Is this the best way to design a mongodb schema? - node.js

I'm fairly new to programming, and am now at the stage where I am working on projects before starting to apply for jobs. I’m working on an express project and wanted some advice on my schema design, just in case I’m going about things in a horrible way.
The premise of the project is a desk booking app. A business administrator can create an account to register a company and add multiple offices if needed. Employees can also register as employees of a company using the unique company code and passcode, and then book desks in different offices.
This is the schema I have designed, I’ve modelled the data in this way as I want to be able to access the data in different ways e.g. an employee viewing all bookings they have made, as well as an employer viewing all bookings made in an office.
Any feedback would be much appreciated! Thanks in advance.
NB: When building the project I will have each schema in a different file, and I know I haven't required all the dependencies I will need. The purpose of this question is purely on best practices for mongoose schema design.
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const CompanySchema = new Schema({
companyName: String,
companyLogo: String,
uniqueCompanyCode: String,
companyPasscode: String,
companyAdmin: [
{
type: Schema.Types.ObjectId,
ref: "User"
}
],
offices: [
{
type: Schema.Types.ObjectId,
ref: 'Office'
}
],
})
const UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true
},
email: {
type: String,
required: true,
unique: true
},
bookings: [
{
type: Schema.Types.ObjectId,
ref: 'Booking'
}
]
})
UserSchema.plugin(passportLocalMongoose);
const OfficeSchema = new Schema({
officeAddress:{
streetAddress: String,
town: String,
county: String,
postcode: String
},
floorPlan: String,
desks: [
{
deskNumber: Number,
bookings:{
type: Schema.Types.ObjectId,
ref: 'Booking'
}
}
]
})
const bookingSchema = new Schema({
bookedFrom: Date,
bookedTo: Date,
bookedBy: {
type: Schema.Types.ObjectId,
ref: "User"
}
})

If you’re using the architecture of Node.js, MongoDB with mongoose, you’ll create these schemas at the beginning of every project so it’s best to get familiar with them.
This is how we create a schema in mongoose.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let productSchema = new Schema({
name: {
type:String,
required:true
},
price: {
type:Number,
required:true
},
isAvailable: {
type:Boolean
}
});
const productModel = mongoose.model("product", productSchema);
module.exports = productModel;
The first parameter of the mongoose.model is the name of the collection that will contain the documents. The second parameter is the schema. Now, we need is to export productModel so they can be used elsewhere.
const productModel = mongoose.model("product", productSchema);
This productModel is ready to be imported and used to create data following our schema in the product collection. This is a common development pattern when using Node.js and MongoDB. An ODM-like mongoose makes managing your data more intuitive.

Related

Mongoose and Node - Implementing Roles - Best practices

I am working on a token based application, and I want to apply roles for different types of user.
My question is if it is a better practice to create a model "Group" and in the model "User" and reference that model "Group" to the user_role property in "User" model.
It would be like this: (I haven't started yet so I can not copy real code, this is just for demonstrative purposes)
const GroupSchema = new Schema({
role: {
type: String,
enum: ['admin', 'employee', 'guest']
},
}
const UserSchema = new Schema({
role: {
type: Schema.Types.ObjectId,
ref: 'Group'
},
},
...
On the other hand I I thought doing this:
const UserSchema = new Schema({
role: {
type: String,
required: true,
},
}
...
But I don't really know what would be the advantages and disadvantages of using one or the other, regarding scalability basically, and if there is a better way.

How to connect schemas with each other in mongooseJS?

Basically what I am trying to achieve is the following:
In my iOS application a QR-Code can be scanned to get the ID of a specific lounge and with that its JSON data (only ID and name for now) saved in MongoDB.
Every lounge is able (but doesn't have to) to add products to their lounge, so that in the end, after scanning the QR-Code, a customer is able to see which products are offered.
A product contains only a name and imageURL for now, but not a price, because the price can variate and be set by owners of a lounge. So a price of one and the same product can be different.
I would like to know, what the correct and best way is for implementing the schemas for that approach, so that I can easily fetch all products of a specific lounge, maybe based on its ID.
What I got so far:
lounge.js:
const mongoose = require('mongoose');
const loungeSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true }
});
module.exports = mongoose.model('Lounge', loungeSchema);
product.js
const mongoose = require('mongoose');
const productSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
imageURL: { type: String, required: true, unique: true }
});
module.exports = mongoose.model('Product', productSchema);
Basically you can use mongoose populate. You can achieve this by storing the products in separate collection and basically populate an array on the lounge schema using the _id for the lounge let me show you an example:
Your lounge schema would be something like this
const mongoose = require('mongoose');
const loungeSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true }
products: [type: Schema.Types.ObjectId, ref: 'Products' ]
});
module.exports = mongoose.model('Lounge', loungeSchema);
And your product schema
const mongoose = require('mongoose');
const productSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
imageURL: { type: String, required: true, unique: true }
});
module.exports = mongoose.model('Product', productSchema);
When saving creating a lounge just add the _id of the product to products array that apart of the lounge schema.
So basically you find the product and retrieve its _id.
To run a find query it would be something like this:
lounge.find({}).populate('products').exec();
products array will then have the related products for each lounge

How to dynamically populate array of objects in mongoose?

I am trying to dynamically populate array of objects in mongoose. On my user model I want an array that contains all posts that user made. The problem is that I have multiple types of posts.
Different post models:
const ImagePost = mongoose.model('ImagePost', new Schema({ url: String }))
const TextPost = mongoose.model('TextPost', new Schema({ text: String }))
My user model looks like this:
const userSchema = new Schema({
userName: {
type: String,
required: true
},
posts: [{
postId: {
type: Schema.Types.ObjectId,
required: true,
refPath: "postModel"
},
postModel: {
type: String,
required: true,
enum: ['ImagePost', 'TextPost']
}
}]
})
const User = mongoose.model('User', userSchema)
How can I get the user from my database and automatically populate the posts the user made?
The whey I think it should work is this but for some reason it doesn't do anything:
User.findById('5d302c7caf1b8906ccb611b6').populate('posts.postId')
Changing your refPath from postModel to posts.postModel may solve your problem.

MongoDB complex associations

I am trying to figure out how to structure my mongoose models. I have a User model, a Task model, and Project model. Projects will have users, and each user will have tasks for that specific project. The way I have this set up is the User model has a schema reference to Project model and the Task model has a scheme reference to a user. How can I do this so that when I render the information retrieved, each project will show its relevant members and each members will have their relevant tasks for that particular project. I also have an admin property of the User model which is just a boolean set default to false. The purpose of this is so that when a user created a team, the Admin property will be set to True allowing the admin to set tasks for users in the project created by admin. The problem with this is, after a team create by the user, if the admin is set to true, the ternary condition on my front end that enables a form input to show based on the boolean value of the 'admin' property will show up for all projects, even for ones the user is not an admin of.
I am using React for the rendering.
//Tasks Models
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var TodoSchema = new Schema({
task: {
type: String
},
userTasks: [{
type: Schema.Types.ObjectId,
ref: "User"
}]
});
var Task = mongoose.model('Task', TodoSchema);
module.exports = Task;
//Users model
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: {
type: String,
trim: true,
required: "First Name is Required"
},
username: {
type: String,
trim: true,
required: "Username is Required"
},
skills: {
type: String,
trim : true
},
avatar: {
type: String
},
SQLid: {
type: Number
},
userCreated: {
type: Date,
default: Date.now
},
lastUpdated: {
type: Date
},
userAdmin: {
default: false
},
adminTeams: [{
type: Schema.Types.ObjectId,
ref: "Team"
}],
team: [{
type: Schema.Types.ObjectId,
ref: "Team"
}],
task: [{
type: Schema.Types.ObjectId,
ref: "Task"
}]
});
var User = mongoose.model("User", UserSchema);
// Export the model so the server can use it
module.exports = User;
//Projects model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var TeamSchema = new Schema({
teamname: {
type: String,
trim: true,
required: "Team Name is Required"
},
description: {
type: String
},
tech: {
type: String
},
teamMembers: [{
type: Schema.Types.ObjectId,
ref: "User"
}]
});
var Team = mongoose.model('Team', TeamSchema);
module.exports = Team;
Once a User creates a team/project, they become an admin of that created team. Admins have the authority to assign task to Users, and Users can belong to many teams. I am thinking about moving the admin boolean to the Projects/Team model and giving that property the _id of the User once they create a team and then Ill use those as keys to match and use a ternary to render a form if the project they are viewing is one they created. However, I am still confused on how I can associate each user with a task, and users can belong to many teams, so I need the tasks that users have to be in the correct Project/Team section.
A lay of what I am talking about
//Projects Page (the div below is just one project out of many listed on the projects page
<div>
Project/Team 1
User Name -> User Task
User Name -> User Task
User Name -> User Task
...
</div>
<div>
Project/Team 2
User Name -> User Task
User Name -> User Task
User Name -> User Task
...
</div>
In general you should review the mongodb documentation on associations
When you're dealing with mongodb you may want to look into holding more associated information on your documents then you would in an sql environment. Theres a good top level answer here about designing your data to use mongodb well.
Your admin problem sounds like a coding issue that will not be solved through your db. I'm hard pressed to see why your form would need a ternary at all if it's displaying on a boolean field, but can't provide any further help then:
if (user.admin) {//form}
without snippets or information on your front end architecture or the ternary in question . It sounds like you may be overcomplicating things for yourself.
edit for your update: I would change your model so projects store the user who created them.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var TeamSchema = new Schema({
teamname: {
type: String,
trim: true,
required: "Team Name is Required"
},
description: {
type: String
},
tech: {
type: String
},
teamMembers: [{
type: Schema.Types.ObjectId,
ref: "User"
}],
// now holds the 'admin' of the team. you can keep the admin naming
//if you like
creator: [{
type: Schema.Types.ObjectId,
ref: "User"
}]
});
var Team = mongoose.model('Team', TeamSchema);
module.exports = Team;
also your user model could use some naming improvements, also an admin field on the user column is often used for application admins like yourself, as opposed to an admin inside a project.
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: {
type: String,
trim: true,
required: "First Name is Required"
},
username: {
type: String,
trim: true,
required: "Username is Required"
},
skills: {
type: String,
trim : true
},
avatar: {
type: String
},
SQLid: {
type: Number
},
// you might want to look up timestamps createdAt and updatedAt
// instead of this field
userCreated: {
type: Date,
default: Date.now
},
lastUpdated: {
type: Date
},
userAdmin: {
default: false
},
// this could be removed as its is now stored on the Team schema
adminTeams: [{
type: Schema.Types.ObjectId,
ref: "Team"
}],
// this should be plural as it represents a one to many
teams: [{
type: Schema.Types.ObjectId,
ref: "Team"
}],
// tasks is removed because you can find them from the Task schema
});
var User = mongoose.model("User", UserSchema);
// Export the model so the server can use it
module.exports = User;
Since your task model needs to be team specific it can look like this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var TodoSchema = new Schema({
task: {
type: String
},
// I would suggest an 'assignedTo', 'owner', or 'taskOwner' naming
// as user may be a bit non-descriptive.
user: [{
type: Schema.Types.ObjectId,
ref: "User"
}],
team: [{
type: Schema.Types.ObjectId,
ref: "Team"
}]
});
var Task = mongoose.model('Task', TodoSchema);
module.exports = Task;
There are a lot of ways to load the information you want with the schema, but given this slight restructuring you could get all the information you need like this:
var tasks;
Team.findOne({'id': the_team_id}, function(err, team) {
if (err) return handleError(err)
Task.find({'team': team}).populate('user').exec(function(err, foundTasks) {
tasks = foundTasks
})
})
You can now check your admin by doing something along this line:
team.creator.id === user.id
mogoose populate

Mongoose populate ObjectID from multiple possible collections

I have a mongoose model that looks something like this
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: 'article',
index:true,
},
});
But 'item' could be referenced from multiple collections. Is it possible to do something like this?
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: ['article','image'],
index:true,
},
});
The idea being that 'item' could be a document from the 'article' collection OR the 'image' collection.
Is this possible or do i need to manually populate?
Question is old, but maybe someone else still looks for similar issues :)
I found in Mongoose Github issues this:
mongoose 4.x supports using refPath instead of ref:
var schema = new Schema({
name:String,
others: [{ value: {type:mongoose.Types.ObjectId, refPath: 'others.kind' } }, kind: String }]
})
In #CadeEmbery case it would be:
var logSchema = new Schema({
item: {type: mongoose.Types.ObjectId, refPath: 'kind' } },
kind: String
})
But I did't try it yet...
First of all some basics
The ref option says mongoose which collection to get data for when you use populate().
The ref option is not mandatory, when you do not set it up, populate() require you to give dynamically a ref to him using the model option.
#example
populate({ path: 'conversation', model: Conversation }).
Here you say to mongoose that the collection behind the ObjectId is Conversation.
It is not possible to gives populate or Schema an array of refs.
Some others Stackoverflow people asked about it.
Soluce 1: Populate both (Manual)
Try to populate one, if you have no data, populate the second.
Soluce 2: Change your schema
Create two link, and set one of them.
var LogSchema = new Schema({
itemLink1: {
type: ObjectId,
ref: 'image',
index: true,
},
itemLink2: {
type: ObjectId,
ref: 'article',
index: true,
},
});
LogSchema.find({})
.populate('itemLink1')
.populate('itemLink2')
.exec()
Dynamic References via refPath
Mongoose can also populate from multiple collections based on the value of a property in the document. Let's say you're building a schema for storing comments. A user may comment on either a blog post or a product.
body: { type: String, required: true },
on: {
type: Schema.Types.ObjectId,
required: true,
// Instead of a hardcoded model name in `ref`, `refPath` means Mongoose
// will look at the `onModel` property to find the right model.
refPath: 'onModel'
},
onModel: {
type: String,
required: true,
enum: ['BlogPost', 'Product']
}
});
const Product = mongoose.model('Product', new Schema({ name: String }));
const BlogPost = mongoose.model('BlogPost', new Schema({ title: String }));
const Comment = mongoose.model('Comment', commentSchema);

Resources