Nodejs Restul Api Express Jsonwebtoken Sequelize MySQL

Created At: 2022-04-08 20:03:42 Updated At: 2022-04-13 13:19:08

We will use Nodejs to build a restful restful api. Go ahead a create a folder name node-js-jwt-auth.

We will use the below packages for building this app

  • Express  
  • Jsonwebtoken  
  • Sequelize
  • MySQL
  • bcryptjs

The project structure would look the like below

Configure Express Web Server

To set up a web server using express, inside the root folder of this project, create a new file name server.js. This file would be the entry point of our app.

node-js-jwt-auth-->server.js

Put the code below in the file.

const express = require("express");
const cors = require("cors");
const app = express();
var corsOptions = {
  origin: "http://localhost:8081"
};
app.use(cors(corsOptions));
// parse requests of content-type - application/json
app.use(express.json());
// parse requests of content-type - application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));
// simple route
app.get("/", (req, res) => {
  res.json({ message: "Welcome to bezkoder application." });
});
// set port, listen for requests
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}.`);
});

We just build a simple server using nodejs express. You can run the server from your termianl using the below command

node server.js

And if you type in http://localhost:8080 you will see a message.

Configure Mysql Database and Sequelize

First create a folder name app and then inside this folder create another folder name config and inside config folder create db.config.js

node-js-jwt-auth-->app-->config-->db.config.js

So inside this file we are going to configure our mysql database and  Sequelize

module.exports = {
  HOST: "localhost",
  USER: "root",
  PASSWORD: "123456",
  DB: "testdb",
  dialect: "mysql",
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  }
};

To create this model first we need to create folder inside app folder name models, and then inside this models folder create a new file name user.model.js

node-js-jwt-auth-->app-->models-->user.mode..js

And put the code like below

module.exports = (sequelize, Sequelize) => {
  const User = sequelize.define("users", {
    username: {
      type: Sequelize.STRING
    },
    useremail: {
      type: Sequelize.STRING
    },
    password: {
      type: Sequelize.STRING
    }
  });
  return User;
};

With this we are created an ORM for users' table name users. We would be able to write database query using the ORM for users table.

Create another file inside this models folder and name it role.model.js

node-js-jwt-auth-->app-->models-->role.mode..js

And put the code 

module.exports = (sequelize, Sequelize) => {
  const Role = sequelize.define("roles", {
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true
    },
    name: {
      type: Sequelize.STRING
    }
  });
  return Role;
};

Sequelize is same as Laravel's ORM or Gin's GORM

Because of this Sequelize we don't have to write  CRUD functions sepeately. Sequelize supports all the CRUD.

So just now we created two Sequelize models to represent our database tables users and roles.

You will have the below support from the Sequelize

  • create a new User: create(object)
  • find a User by id: findByPk(id)
  • find a User by email: findOne({ where: { email: ... } })
  • get all Users: findAll()
  • find all Users by usernamefindAll({ where: { username: ... } })

Initialize Sequelize

Once the model structures are ready we need to call them and actually create. To do it, let's create a new file inside the models folder. And call it index.js

node-js-jwt-auth-->app-->models-->index.js

Put the code below in it 

const config = require("../config/db.config.js");
const Sequelize = require("sequelize");
const sequelize = new Sequelize(
  config.DB,
  config.USER,
  config.PASSWORD,
  {
    host: config.HOST,
    dialect: config.dialect,
    operatorsAliases: false,
    pool: {
      max: config.pool.max,
      min: config.pool.min,
      acquire: config.pool.acquire,
      idle: config.pool.idle
    }
  }
);
const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.user = require("../models/user.model.js")(sequelize, Sequelize);
db.role = require("../models/role.model.js")(sequelize, Sequelize);
db.role.belongsToMany(db.user, {
  through: "user_roles",
  foreignKey: "roleId",
  otherKey: "userId"
});
db.user.belongsToMany(db.role, {
  through: "user_roles",
  foreignKey: "userId",
  otherKey: "roleId"
});
db.ROLES = ["user", "admin", "moderator"];
module.exports = db;

First we create sequelize object using the database information and then create model object using the sequelize.

At the same time we define the relationship between user and role table. They have one to many relationship and vice verce.

Now in server.js file we need to call sequelize. We called sync() to drop all the previous tables.

const express = require("express");
const cors = require("cors");
const app = express();
var corsOptions = {
  origin: "http://localhost:8081"
};
app.use(cors(corsOptions));
// parse requests of content-type - application/json
app.use(express.json());
// parse requests of content-type - application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));

const db = require("./app/models");
const Role = db.role;
db.sequelize.sync({force: true}).then(() => {
  console.log('Drop and Resync Db');
  initial();
});
function initial() {
  Role.create({
    id: 1,
    name: "user"
  });
 
  Role.create({
    id: 2,
    name: "moderator"
  });
 
  Role.create({
    id: 3,
    name: "admin"
  });
}
// simple route
app.get("/", (req, res) => {
  res.json({ message: "Welcome to bezkoder application." });
});
// set port, listen for requests
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}.`);
});

Define Auth Key

Jsonwebtoken has functions like verify() or sign() that uses algorithm to encode or decode information based on secret key. We can define the secret key on our own. Let's create a new name auth.config.js in the config folder

node-js-jwt-auth-->app-->config-->auth.config.js

module.exports = {
  secret: "dbtech-secret-key"
};

Middleware

We need to create a file for checking user name and email. We will check if the user name or email already exist or not during sign up.  Go ahead a create a new folder called middleware and create a file name verifySignUp.js. 

node-js-jwt-auth-->app-->middleware-->verrifySignUp.js

Inside this we will verify user name and email.

const db = require("../models");
const ROLES = db.ROLES;
const User = db.user;
checkDuplicateUsernameOrEmail = (req, res, next) => {
  // Username
  User.findOne({
    where: {
      username: req.body.username
    }
  }).then(user => {
    if (user) {
      res.status(400).send({
        message: "Failed! Username is already in use!"
      });
      return;
    }
    // Email
    User.findOne({
      where: {
        email: req.body.email
      }
    }).then(user => {
      if (user) {
        res.status(400).send({
          message: "Failed! Email is already in use!"
        });
        return;
      }
      next();
    });
  });
};
checkRolesExisted = (req, res, next) => {
  if (req.body.roles) {
    for (let i = 0; i < req.body.roles.length; i++) {
      if (!ROLES.includes(req.body.roles[i])) {
        res.status(400).send({
          message: "Failed! Role does not exist = " + req.body.roles[i]
        });
        return;
      }
    }
  }
  
  next();
};
const verifySignUp = {
  checkDuplicateUsernameOrEmail: checkDuplicateUsernameOrEmail,
  checkRolesExisted: checkRolesExisted
};
module.exports = verifySignUp;

 

Next part is all about authentication using token. For that go ahead a new file authJwt.js

node-js-jwt-auth-->app-->middleware-->verrifySignUp.js

and put the code

const jwt = require("jsonwebtoken");
const config = require("../config/auth.config.js");
const db = require("../models");
const User = db.user;
verifyToken = (req, res, next) => {
  let token = req.headers["x-access-token"];
  if (!token) {
    return res.status(403).send({
      message: "No token provided!"
    });
  }
  jwt.verify(token, config.secret, (err, decoded) => {
    if (err) {
      return res.status(401).send({
        message: "Unauthorized!"
      });
    }
    req.userId = decoded.id;
    next();
  });
};
isAdmin = (req, res, next) => {
  User.findByPk(req.userId).then(user => {
    user.getRoles().then(roles => {
      for (let i = 0; i < roles.length; i++) {
        if (roles[i].name === "admin") {
          next();
          return;
        }
      }
      res.status(403).send({
        message: "Require Admin Role!"
      });
      return;
    });
  });
};
isModerator = (req, res, next) => {
  User.findByPk(req.userId).then(user => {
    user.getRoles().then(roles => {
      for (let i = 0; i < roles.length; i++) {
        if (roles[i].name === "moderator") {
          next();
          return;
        }
      }
      res.status(403).send({
        message: "Require Moderator Role!"
      });
    });
  });
};
isModeratorOrAdmin = (req, res, next) => {
  User.findByPk(req.userId).then(user => {
    user.getRoles().then(roles => {
      for (let i = 0; i < roles.length; i++) {
        if (roles[i].name === "moderator") {
          next();
          return;
        }
        if (roles[i].name === "admin") {
          next();
          return;
        }
      }
      res.status(403).send({
        message: "Require Moderator or Admin Role!"
      });
    });
  });
};
const authJwt = {
  verifyToken: verifyToken,
  isAdmin: isAdmin,
  isModerator: isModerator,
  isModeratorOrAdmin: isModeratorOrAdmin
};
module.exports = authJwt;

in the above file we get the user model object so that we can operate on the user table. But first we check the send token if it's valid or not.

Let's create an index.js inside middleware folder

node-js-jwt-auth-->app-->middleware-->verrifySignUp.js

and

const authJwt = require("./authJwt");
const verifySignUp = require("./verifySignUp");
module.exports = {
  authJwt,
  verifySignUp
};

 

Controllers

Let's go ahead and create a controllers folder inside app folder.

We will have two controllers. One for authentication and other one is for user role. Let's create auth.controller.js

node-js-jwt-auth-->app-->controllers-->auth.controller.js

User Authentication

This controller will have signup and signin methods.

Signup() method creates a user into the database and assigns a role to the user.

Signin() method logs in and returns a token if souccessful

const db = require("../models");
const config = require("../config/auth.config");
const User = db.user;
const Role = db.role;
const Op = db.Sequelize.Op;
var jwt = require("jsonwebtoken");
var bcrypt = require("bcryptjs");
exports.signup = (req, res) => {
  // Save User to Database
  User.create({
    username: req.body.username,
    email: req.body.email,
    password: bcrypt.hashSync(req.body.password, 8)
  })
    .then(user => {
      if (req.body.roles) {
        Role.findAll({
          where: {
            name: {
              [Op.or]: req.body.roles
            }
          }
        }).then(roles => {
          user.setRoles(roles).then(() => {
            res.send({ message: "User was registered successfully!" });
          });
        });
      } else {
        // user role = 1
        user.setRoles([1]).then(() => {
          res.send({ message: "User was registered successfully!" });
        });
      }
    })
    .catch(err => {
      res.status(500).send({ message: err.message });
    });
};
exports.signin = (req, res) => {
  User.findOne({
    where: {
      username: req.body.username
    }
  })
    .then(user => {
      if (!user) {
        return res.status(404).send({ message: "User Not found." });
      }
      var passwordIsValid = bcrypt.compareSync(
        req.body.password,
        user.password
      );
      if (!passwordIsValid) {
        return res.status(401).send({
          accessToken: null,
          message: "Invalid Password!"
        });
      }
      var token = jwt.sign({ id: user.id }, config.secret, {
        expiresIn: 86400 // 24 hours
      });
      var authorities = [];
      user.getRoles().then(roles => {
        for (let i = 0; i < roles.length; i++) {
          authorities.push("ROLE_" + roles[i].name.toUpperCase());
        }
        res.status(200).send({
          id: user.id,
          username: user.username,
          email: user.email,
          roles: authorities,
          accessToken: token
        });
      });
    })
    .catch(err => {
      res.status(500).send({ message: err.message });
    });
};

Now we will create a user controller to deal with user access and role

node-js-jwt-auth-->app-->controllers-->user.controller.js

exports.allAccess = (req, res) => {
  res.status(200).send("Public Content.");
};
exports.userBoard = (req, res) => {
  res.status(200).send("User Content.");
};
exports.adminBoard = (req, res) => {
  res.status(200).send("Admin Content.");
};
exports.moderatorBoard = (req, res) => {
  res.status(200).send("Moderator Content.");
};

Comment

Add Reviews