Mastering "M" in "MEAN" full-stack javascript (3)
MEAN full-stack javascript is popular in today’s web application development. It is short for four javascript-based web technologies:
- M: mongoDB – NoSQL database
- E: Express.js – node.js web application framework
- A: AngularJS – front-end web framework
- N: Node.js – a platform built on Chrome’s JavaScript runtime
This series of articles are aimed to list some critical concepts, foundamentals and my preliminary understanding of this M.
Key Words: ObjectID, embedded document, schema, document
In last blog, we talked about the mongoodb data creation and pass user information (username, password, etc) to mongodb via http POST request.
let’s have a quick review of the following code snippet:
var auth = express.Router();
auth.post('/signup', function(req, res){
var user = new User({
username: req.body.username,
email: req.body.email,
password: req.body.password,
firstName: req.body.firstName,
lastName: req.body.lastName,
});
user.save(function(err){
if(err){
res.send(err);
return;
}
res.json({
success: true,
message: "new user created"
});
});
});
One thing you need to be careful is eveytime you change, update or create a mongodb document, you should remember
to call .save(callback_function)
to save your changes. Otherwise, your change and update will not be saved into
mongodb. This situation happened to me several time, and cost me lots of time to debug.
Tip: “encapsulation” is a good idea to avoid this problem. For example, instead using the above direct way to create a new user document and then save it, we could write a schema custom method
adUser(..)
, in this method, we encapsulate the.save()
method, then everytime when we wanna add a new user, we could calladdUser()
directly and no need to call.save()
each time.
Maybe this problem is easy enough, now let’s have a tricky one about data creation and http POST request.
In the last problem, we create a new user by POST, now suppose in our web app, we not only have users, we still have some “jobs”. In one “Job”, there could be several users, and two “chats”, a private one and a public one. The users could chat in private or public chat.
Confused?
let’s look at their schemas:
// job schema
...
var JobSchema = new Schema({
jobname: {type: String, required: true, index:{unique: true}},
createdBy: {type: Schema.Types.ObjectId, ref: 'User'},
users: [{type: Schema.Types.ObjectId, ref: 'User'}],
privateChat: {type: Schema.Types.ObjectId, ref: 'Chat'},
publicChat: {type: Schema.Types.ObjectId, ref: 'Chat'}
});
module.exports = mongoose.model('Job', JobSchema);
// chat schema
...
var mongoose = require('mongoose');
var chatSchema = new mongoose.Schema({
chatType: String, // privateChat or publicChat
messages: [{
text: String,
time: { type: Date, default: Date.now},
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User'}
}]
});
module.exports = mongoose.model('Chat', chatSchema);
In the above schemas, the most trick part is definitely the various Schema.Types.ObjectId
objects.
Actually, it did cost me a lot of time to undestand how to use that. But bascially, the usage is fixed
and we temporarily only need to know how to use it.
So, take JobSchema for example. We have five properities. jobname
is easy to master. After that, we have
a property called createdBy
, from the reference ref: 'User'
we know that it is actually pointing to a user
(who created this job). Since a user
is actually a mongoose document (or you can undersand it as an object if you will),
for each object/document in mongoodb, it has an _id
property with value “ObjectId”. (Actually, for a job document, it also has _id
).
This value is unique for each user. So when we want to point to a specific user, we only need to give his ID, right?
Schema.Types.ObjectId
is a general statement, how can we let the program know we are pointing to user’s ID? yes, use
ref:'User'
statement.
The above principle also applies to the following privateChat
and ‘publicChat’ property definitions.
I think the second tricy part to understand the above schemas is about the message property in “chatSchema”. Technically, it is called “embedded document”. It means in each chat, we have several messages. Abosolutely we could define a seperate schema called messageSchema, and use its ObjectID in chatSchema like what we did for users. But consider it is very simple, so here we just put the definition directly in chatSchema, which is also readable.
Now I think you understand what are the schemas? Or at least, have a high intuition of the relationshiops among “user”, “chat” and “job”, if so, I will post my question:
- How to create chat, user and job documents and let them fit together?
For the clarification, “fit together” means we could create a user named “Johson”, and Alice creates a job (jobname: ‘helloworld) with two chats, and for each chat, we both have the initial message “Welcome to private (public) chat”. Clear?
Ok, again, we are expecting to output the following information to console or browser when the development is done:
// job "helloworld" information in JSON format
{
"jobname": "helloworld",
"createdBy": "5564a12da1853e1c46d06ebe",
"privateChat": "5572223c053b6f40324f56ad",
"publicChat": "5572223c053b6f40324f56af",
"_id": "5572223c053b6f40324f56b1",
"__v": 0,
"users": [
"5564a12da1853e1c46d06ebe"
]
}
// private chat messages information in JSON
[
{
"text": "Welcome to private chat",
"user": "5564a12da1853e1c46d06ebe",
"_id": "5572223c053b6f40324f56ae",
"time": "2015-06-05T22:27:08.020Z"
}
]
// public chat messages information in JSON
[
{
"text": "Welcome to public chat",
"user": "5564a12da1853e1c46d06ebe",
"_id": "5572223c053b6f40324f56b0",
"time": "2015-06-05T22:27:08.020Z"
}
]
From the above output, we could see in job “helloworld” the creator is with id “5564a12da1853e1c46d06ebe”,
it the also the only user of the two chats. Job “helloworld” also has its unique objectId "_id": "5572223c053b6f40324f56b1"
.
Additionally, it has two chats with two different objectIDs. For each chat, we has one initial welcome message, for each
message, it has a text field "text": "Welcome to public chat"
, the message poster field
"user": "5564a12da1853e1c46d06ebe",
which is also the job creator and the message post time "time": "2015-06-05T22:27:08.020Z"
.
Ok, I think the assignment is clear enough now, how to implement the above functionality? We will discuss this in the next article.