Building a Food Order system with MEAN Stack (Mongoose, Expressjs, Angularjs, Nodejs)
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.
- Typescript version 2.4.2
- Node version version 8.9.4
- Npm version 5.6.0
- Express version ^4.15.3.
- Voltron Server Repository
Table of content.
- Setting up.
- Creating the user model and adding methods to the schema document.
- Building the passport.ts
- Creating the users route and controller.
- 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.
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;
- importing the useful modules.
- 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 required
and 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;
- The route to create a user which uses the POST protocol
- 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
};
}
- Get all request from the body entered by the user.
- Validate email and password and return all errors.
- 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.
- Save the request which is email and password.
- Validate the Email
- Validate the password.
- Find the user in the database using the email
- Compare the password using the method on the schema document.
If there was an error return the err with the error message. - 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
Congratulations @sirfreeman! You have completed some achievement on Steemit and have been rewarded with new badge(s) :
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
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) :
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