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:

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 call addUser() 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:

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.