Building a Food Order system with MEAN Stack (Mongoose, Expressjs, Angularjs, Nodejs)

in #utopian-io6 years ago (edited)

In our last tutorial we explained the concept of NoSql and Sql databases, the schema and the express.ts files which is the core of our application and lastly, we were able to set up a mongDB instance for our voltron server (Application).

Overview.

In this installment, we would start by creating the model of our users, we are also going to explain the concept of token based authentications with passport and Jwt token. This won't be all, we would set up the controller and the routes.

Table of content.

  1. Setting up.
  2. Creating the user model and adding methods to the schema document.
  3. Building the passport.ts
  4. Creating the users route and controller.
  5. What is the concept behind API authentications or jwt token base authentication.

Difficulty

This tutorial is rated intermediate.

Setting up.

When we mean Setting up, it quite easy. In the src folder, we are going to organize our application to have to folders, api and config. The root directory contains the tsconfig.json and the server.ts. In the last tutorial, we didn't talk about the server.ts found on the Matheusdavison Stater pack.

Untitled-1.jpg
schematic diagram for setup

Let Talk about the server.ts. This is our server file that listens for any changes on the application.
The following has to carried out on this file;

  1. importing the useful modules.
  2. Setting the port and initiating the server with express.ts

Importing the useful modules

The following modules should be imported.

import * as http from 'http';
import * as debug from 'debug';
import Express from './config/express';

The first is for creating the server, debug module is for reporting errors and lastly, We import the express.ts file we created before.

Setting the port and initiating the server with express.ts

const port = normalizePort(process.env.PORT) || 3000;
Express.set('port', port);
const server = http.createServer(Express);
server.listen(port);
server.on('listen', onListening);
server.on('error', onError);

If you noticed, we create a variable called port to save the port we want the application to run.
next up, we set the port on our express application and finally we created the server to run express.

In our setup, we are going to be having each domains on their own, such as a user, item and order and each of them would contain their own model, route and controller file.

Creating the user model and adding methods to the schema document.

The user model sets a base for us, and help us restrict the type of data out mongodb show accept or save.
To begin creating the model, we have to import a couple of modules.

import { Schema, Document, model, Model } from 'mongoose';
import * as bcrypt from 'bcrypt';
import * as uniqueValidator from "mongoose-unique-validator";
import * as mongoose from 'mongoose';
const SALT_WORK_FACTOR = 10;

Above, we just imported Schema, Document model and Mode from mongoose which is an ORM for mogodb.
bcrpty which would be used for password encryption.
uniqueValidator for validating data and lastly Our ORM to connect to mongodb.

Next up lets create Our schema.

let UserSchema: Schema = new Schema({
    name: {
        type: String,
        required: true,
      },
    email: {
        type: String,
        unique: true,
        required: true,
        default: ''
      },
    username: {
        type: String,
        unique: true,
        required: true,
        default:'',
        lowercase: true
      },
    password: {
        type: String,
        required: true,
        default: ''
      },
    createdAt: {
      type: Date,
      default: new Date
    },
    updatedAt: {
      type: Date,
    default: new Date
    }, 
});

Its really easy all we did was to set the type of document the DB should accept, we also set some default and if it is also compulsory to be filled and unique with the requiredand unique attributes.

Next we need to add methods to this model.
first we add the uniqueValdator to the plugin method of the UserSchema.

UserSchema.plugin(uniqueValidator);

The next method is the one that bcrypt or encrypt the password before saving to the database.

Generation of encryption for new or modified password
UserSchema.pre('save', function(next) {
  var user = this;

 // only hash the password if it has been modified (or is new)
  if (!user.isModified('password')) return next();

 // generate a salt
  bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
      if (err) return next(err);

      // hash the password using our new salt
      bcrypt.hash(user.password, salt, function(err, hash) {
          if (err) return next(err);

          // override the cleartext password with the hashed one
          user.password = hash;
          next();
      });
});

You can read the comment on each line to understand the code above, we are simply hashing the password if it is modified or new.

The next part, we are going to create a function for comparing the password the user entered and the one in the database, this method returns a boolen ( true or false)

compare password method
interface UserSchemaDoc extends Document {
  comparePassword(pw, cb);
}

//interface UserSchemaInterface extends UserSchemaDoc {}
// method to compare password
UserSchema.methods = {
  comparePassword: function(pw, cb) {
    bcrypt.compare(pw, this.password, function(err, isMatch) {
        if (err) {
          return cb(err);
        }
        cb(null, isMatch); 
    });
  }
};
export default new UserRouter().router;

We add the method to the UserSchemaDoc,
And lastly we export the class to be used in the controller

Building the passport.ts.

We are going to write the the passport script that checks the database using jwt to check the payload of a user to confirm if a user has a said token, if not passport should return an err.

Writing the passport file, we are to import the UserModel and a couple of other modules such as;

  • JwtStrategy
  • ExtractJwt

Let create the class to check using passport.

module.exports = function(passport) { 
  let opts = {jwtFromRequest: ExtractJwt.fromAuthHeader(), secretOrKey: process.env.SECRET};
  passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
    UserSchema.findOne({_id: jwt_payload._id}, (err, user) => {
        if(err){
        return done(err, false);
      }
      if(user){
        done(null, user);
      }else{
        done(null, false);
      }
    });
  }));
};

We are to pass a new JwtStrategy for passport to using in checking the database for the said user. If there was an error, we are to return false, but when no error, we are to return the said user.

Creating the main route and users route and controller.

We are going to start with our main Route for our application, which has a prefix a api/users
Note this file routes.ts is created in the config folder .

lets create the main Route.

Lets import the Express, Router,Request ,Response andNextFunction from Express
and we are going to import the UserRouter from the location which we have not created yet.

import { Express, Router, Request, Response, NextFunction } from 'express';
import UserRouter from '../api/user/user.router';

Let create our Route class and create to variables called app and router of which router is a public variable and app is private, we initiate this variables in the constructor method

export default class Route {
    public router: Router;
    private app;

    constructor (app: Express) {
        // Set router
    this.router = Router();
     
    // set the app
    this.app = app;
    }
}

Next we need to have a method to set all routes and and it to the constructor method
add the snippet it to the constructor method

this.setAllRoutes();

And create a private function or method that sets the routes

private setAllRoutes() {
    this.app.use('/api/users', UserRouter);
}

Note: we are using the UserRouter for any request sent to /api/users
We would continue to add new route prefix to this methods
Lets create the UserRouter class.

Creating the UserRouter

In the directory api/user create the file user.router.ts if not created.
In the file, which is responsible for all the users routing, we to import the following modules;

  • Router, NextFunction, Response and Request from Express
  • The UserController from its location (api/user/user.controller.ts)

Next up lets create our UserRouter class instantiate a router instance, pass the routes to constructor and create the method from initiating the routes.
for this installment, we would be creating two routes;

  1. The route to create a user which uses the POST protocol
  2. The route to authenticate a user.(login)
import { Router, Request, Response, NextFunction } from 'express';
import UserController from './user.controller';

export class UserRouter {

    public router: Router;

    /*--------  Constructor  --------*/
    constructor() { 
        // Set router
        this.router = Router();
        this.init();
    }

    /*--------  Methods  --------*/
     * Init all routes in this router
     */
    init () {
        this.router.post('/', UserController.create);
        this.router.post('/authenticate', UserController.authenticate);
    }

} 
// Create Router and export its configured Express.Route
export default new UserRouter().router;

If you closely notice, the init method is called from constructor which initiate the UserController which is responsible for all the logic.

Creating the UserController.

In this installment, we are going to work on two methods which are the Create and Authenticate method for the users.

We would begin with the create method.
Before adding these methods, we need to import some modules

import { Request, Response, NextFunction } from 'express';
import UserModel from './user.model';
import * as isEmail from 'validator/lib/isEmail';
import * as jwt from 'jwt-simple';

The isEmail from the validator, would help us to validate the email and make sure the value entered by the user.
we are to use the UserModel into the controller.
Next up create the UserController class

create user

The following should be done before a user can be created in the database.

Add a method to pass the token to the user object.

private static userDataToPassInToken(user): Object{
    return {
      _id: user._id,
      email: user.email
    };
}
  1. Get all request from the body entered by the user.
  2. Validate email and password and return all errors.
  3. Create user and generate jwt token.

We would start by setting each request of the user.

    let email = req.body.email;
    let password = req.body.password;
    let username = req.body.username;
    let name = req.body.name;

next, we validate the email and push all errors to the errors array

 // The errors object
    let errors: Array<Object> = [];

    // Check email
    if(!email){ 
      errors.push({
        title: "Attribute is missing",
        detail: "No email specified"
      });
    }else{
      // If email has not email format
      if(!isEmail(email)){
        errors.push({
          title: "Invalide attribute",
          detail: "Email must have an email format"
        });
      }
      // If email doesn't have characters length requirments
      if(email.length < 5){
        errors.push({
          title: "Invalid attribute",
          detail: "Email must contain at least five characters"
        });
      }
}

next up we validate the password and push all errors to the error array and create the user with the generated jwt token. The token is created using the jwt module import and the secretOrKey added in the .env file from encryption.

// Check password
    if(!password){
      errors.push({
        title: "Attribute is missing",
        detail: "No password specified"
      });
    }else{
      if(password.length < 6){
        errors.push({
          title: "Invalid attribute",
          detail: "Password must contain at least 6 characters"
        });
      }
    }

    // If a least one error
    if(errors.length > 0){
      res.status(403).send({
        errors: errors
      });
    }else{
      UserModel.create({ email, password, username, name })
      .then(user => {
        res.status(201).send({
          data: {
            type: "users",
            id: user._id,
            token: "JWT " + jwt.encode(UserController.userDataToPassInToken(user), process.env.SECRET, "HS256", "")
          }
        });
      })
      .catch(err => {
        res.status(400).send({
          errors: [{
            title: "Can't create the user",
            detail: err.message
          }]
        });
      }); 
    }
    
}

If there was an error simple return a json response telling the user that the user cant be created.

User Authentication.

In Authenticating a user, its quite easy, we would be breaking it into steps.

  1. Save the request which is email and password.
  2. Validate the Email
  3. Validate the password.
  4. Find the user in the database using the email
  5. Compare the password using the method on the schema document.
    If there was an error return the err with the error message.
  6. If successful login the user by returning the user with a generated token.
public static async authenticate(req: Request, res: Response, next: NextFunction) {
    
        // The attributes.
        let email = req.body.email;
        let password = req.body.password;
    
        // The errors object
        let errors: Array<Object> = [];
    
        // Check email
        if (!email) { 
          errors.push({
            title: "Attribute is missing",
            detail: "No email specified"
          });
        } else {

          // If email has not email format
          if (!isEmail(email)) {
            errors.push({
              title: "Invalide attribute",
              detail: "Email must have an email format"
            });
          }

          // If email doesn't have characters length requirments
          if(email.length < 5){
            errors.push({
              title: "Invalid attribute",
              detail: "Email must contain at least five characters"
            });
          }

        }
       
        // Check password
        if (!password) {
          errors.push({
            title: "Attribute is missing",
            detail: "No password specified"
          });
        } else {
          if (password.length < 6) {
            errors.push({
              title: "Invalid attribute",
              detail: "Password must contain at least 6 characters"
            });
          }
        }
    
        // If a least one error
        if (errors.length > 0) {
          res.status(403).send({
            errors: errors
          });
        } else {

            UserModel.findOne({ email }).then(user => {
            if (user) {
              user.comparePassword(password, (err, isMatch) => {
    
                if (err) {
                  errors.push({
                    title: "Can't login user",
                    detail: "Error comparing the password"
                  });
                }
    
                if (!isMatch) {
                  errors.push({
                    title: "Can't login user",
                    detail: "The password doesn't match"
                  });
                }
    
                if (errors.length > 0) {
                  res.status(400).send({
                    errors: errors
                  });
                } else {
                  res.status(201).send({
                    data: {
                      type: "users",
                      id: user._id,
                      attributes: {
                        email: user['email']
                      },
                      token: "JWT " + jwt.encode(UserController.userDataToPassInToken(user), process.env.SECRET, "HS256", "")
                    }
                  })
                }
                
                    });
            } else {
              res.status(400).send({
                errors: [{
                  title: "Invalid attribute",
                  detail: "The email does not exist"
                }]
              })
            }          
        });

        }
}

What is the concept behind API authentications or jwt token base authentication.

API authentication is quit important to create stateless applications, in api, we don't a concept of a user session, a token is generated when a user a created using API and this is like a marker for the user. The token is passed along on every Request to knoow the logged in user. To logout a user you simply need to destroy the token access.

Conclusion.

In this series, we talked about authentication and creation of users and also building the module, controller and routes for our application.

Curriculum

Building a food order system with MEAN Stack (MongoDB, Express, AngularJs and NodeJs). pt1

Sort:  

Congratulations @sirfreeman! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of posts published
Award for the number of upvotes received

Click on any badge to view your own Board of Honor on SteemitBoard.

To support your work, I also upvoted your post!
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

Upvote this notification to help all Steemit users. Learn why here!

Thanks for the contribution.


Need help? Write a ticket on https://support.utopian.io.
Chat with us on Discord.

[utopian-moderator]

Hey @sirfreeman! Thank you for the great work you've done!

We're already looking forward to your next contribution!

Fully Decentralized Rewards

We hope you will take the time to share your expertise and knowledge by rating contributions made by others on Utopian.io to help us reward the best contributions together.

Utopian Witness!

Vote for Utopian Witness! We are made of developers, system administrators, entrepreneurs, artists, content creators, thinkers. We embrace every nationality, mindset and belief.

Want to chat? Join us on Discord https://discord.gg/Pc8HG9x

Congratulations @sirfreeman! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

Upvote this notification to help all Steemit users. Learn why here!

Coin Marketplace

STEEM 0.17
TRX 0.13
JST 0.027
BTC 59046.50
ETH 2654.73
USDT 1.00
SBD 2.50