Reviewing models and server-side structure

So far, we have gained real knowledge of the Mongoose models and MongoDB. Now it is time to see them in action in our application, and to get familiarized with the server directories.

The server folder

We have already covered in detail the main folders (client, server, and e2e) of the client directory in the previous chapter. In this chapter, we are going to focus solely on the server directory. Here is an overview what it looks like:

meanshop/server
├── api              - Server API components
├── auth             - Authentication handlers
├── components       - App-wide/reusable components
├── config           - App configuration
│   └── local.env.js - Environment variables
│   └── environment  - Node environment configuration
├── views            - Server rendered views
└── app.js           - Bootstrap the application

The app.js script is the main script. It loads all the other scripts and bootstraps ExpressJS. Take a look on your own and follow the referenced files, just to get familiarized with them. We are going to explain them thoroughly in later chapters.

For the rest of this chapter, we are going to concentrate mainly on the server/api directory. Let's take a look at an example to understand what an API resource will look like:

meanshop/server/api/thing
├── index.js                - ExpressJS Routes
├── thing.controller.js     - ExpressJS Controller
├── thing.model.js          - Mongoose model
├── thing.socket.js         - SocketIO events
└── thing.spec.js           - Controller tests

Each API component in our system will have a similar naming convention.

Current Mongoose models

Take a look under meanshop/server/api. Notice the user and thing folders. Now, take a look at server/api/thing/thing.model.js:

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var ThingSchema = new Schema({
  name: String,
  info: String,
  active: Boolean
});

module.exports = mongoose.model('Thing', ThingSchema);

So far, we had explained how the schema works and even defined a ProductSchema ourselves. Let us now explain the module.exports and require methods.

CommonJS Modules

We have seen that AngularJS keeps the files organized in modules with angular.module. On the server side, however, we will do something similar using CommonJS's module.exports and require.

The purpose of CommonJS is to provide modules in the server-side JavaScript. It provides a way for handling dependencies, and to solve scope issues using the following:

  • require: This function allows the importing of a module into the current scope.
  • module.exports: This object allows exporting functionality from the current module. Everything attached to it (functions/attributes) will be available when the require function is invoked.
  • exports: This is the module.exports helper. Modules ultimately return module.exports, not exports. So, everything attached to exports is collected and passed to module.exports if and only if module.exports have not been assigned to anything yet.

These concepts might seem a little abstract. Let's do a couple of examples to drive these home. Create a new file user.js as follows:

// user.js

exports.name = function(name){
  return 'My name is ' + name;
}

Go to node shell:

$ node
> var user = require('./user');
undefined
> user.name('Adrian');
'My name is Adrian'

You were able to access the method name that was exported in user.js.

Tip

Press Ctrl + D to exit the node console.

Now, let's try to assign something to module.export, and see what happens. Edit user.js as follows:

// user.js

module.exports = 'Mejia';

exports.name = function(){
  return 'My name is Adrian';
}

Run the console again:

node
> var user = require('./user');
undefined
> user.name()
TypeError: Object Mejia has no method 'name' …
> user
'Mejia'
>

What went wrong? We just verified that exports gets ignored if module.exports gets assigned to something, regardless of the order of assignment.

Tip

Exports versus module.exports

The rule of thumb is to use only exports to assign multiple properties or functions. On the other hand, when we want to export a single object or a single function, we can do it directly to module.exports. Remember never to use both since module.exports is going to override everything attached to exports.

The user model

Since we are learning about Mongoose in this chapter, let's see what we have learned so far in our project. Let's take a look at the user.model.js. It is a little bit long, so we are just going to highlight certain fragments as examples of what we have learned in this chapter:

/* server/api/user/user.model.js *modified excerpt */

var UserSchema = new Schema({
  name: String,
  email: { type: String, lowercase: true },
  role: {
    type: String,
    default: 'user'
  },
  hashedPassword: String,
  provider: String,
  salt: String,
  facebook: {},
  twitter: {}
});

We learned that in Mongoose that everything starts with a schema, which has properties and data types. It might also contain default values, middleware, virtual attributes, some built-in validations, and preprocessors (lowercase, trim, and so on).

// Virtual attributes 

UserSchema
  .virtual('profile')
  .get(function() {
    return {
      'name': this.name,
      'role': this.role
    };
  });

Virtual attributes are not stored in MongoDB, but allow us to make composites of different properties with get. Furthermore, we can break down the composed values and store them in separate properties using set:

// Validate empty password

UserSchema
  .path('hashedPassword')
  .validate(function(hashedPassword) {
    if (authTypes.indexOf(this.provider) !== -1) return true;
    return hashedPassword.length;
  }, 'Password cannot be blank');

Validations are necessary to keep the data in a healthy state. They are run before save/update, and display user error messages to the user when the conditions are not met. We use path to specify the property that we want to validate and validate to provide the validator function.

Sometimes, we may want to provide the users the ability to log in with social networks and also with their e-mail ID and passwords. Thus, users only need to offer a password if they do not have a social provider associated. This is a perfect job for a pre-save hook:

/* server/api/user/user.model.js *excerpt */

UserSchema
  .pre('save', function(next) {
    if (!this.isNew) return next();

    if (!validatePresenceOf(this.hashedPassword) && authTypes.indexOf(this.provider) === -1)
      next(new Error('Invalid password'));
    else
      next();
  });

Finally, we have instance methods:

/* server/api/user/user.model.js *simplified excerpt */

  UserSchema.methods.authenticate = function(plainText) {
    return this.encryptPassword(plainText) === this.hashedPassword;
  }

In this case, we can attach methods to the instance of the user model using Schema.methods. It can be used only in an instance of User. For example:

/* server/api/user/user.controller.js *excerpt */

User.findById(userId, function (err, user) {
  if(user.authenticate(oldPass)) {
    user.password = newPass;
    user.save(function(err) {
      if (err) return validationError(res, err);
      res.send(200);
    });
  } else {
    res.send(403);
  }
});
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.186.153