Easy MVC backend with JWT authentication using express.js & MySql
Hello
In this story, I’m going to teach how to make an MVC backend with a maintainable code and a proper strategy pattern.
We are going to use this boilerplate that I have created to ease our way.
Boilerplate link:
https://github.com/aratta-studios/aratta-express-boilerplate
Project Setup & Configuration
First, install this provided installer:
npm i -g @aratta-studios/aratta-express
For creating the project and initial setup, open your terminal in your working folder and use this command:
aratta-express aratta-express-example
Then in phpMyAdmin, create a database named:
aratta_express_db
Now let’s insert database credentials in our project to connect the project with database
First edit env/index.js as
export default {
username: "yourUserName",
password: "yourPassword",
database: "aratta_express_db",
host: "127.0.0.1",
dialect: "mysql",
jwtSecret: 'randomstring'
}
Then edit config/config.json as
{
"development": {
"username": "yourUserName",
"password": "yourPassword",
"database": "aratta_express_db",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "yourUserName",
"password": "yourPassword",
"database": "aratta_express_db",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "yourUserName",
"password": "yourPassword",
"database": "aratta_express_db",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
This file is a config for Sequelize ORMwhich I’m using in this boilerplate
Ps: if you using XAMPP for DB and has not changed the configurations, Username is root and no password needed
Migrations and creating required tables
For this feature, we are using Sequelize and this is a very useful feature to use
You can read more in this link
http://docs.sequelizejs.com/manual/migrations.html
Before we start, it’s better to delete all files in migration folder I just put them for example also delete all files except index.js in the models folder
Then use these commands to generate a skeleton for migrations and models
npx sequelize-cli model:generate --name User --attributes username:string,password:stringnpx sequelize-cli model:generate --name Contact --attributes phone_number:string,name:string,user_id:integer
This commands will generate some files in models and migrations folders
Now let’s edit them and prepare them for use in the next steps
Edit user_id object in migrations/some-numbers-create-contact.js file as following
user_id: {
type: Sequelize.INTEGER,
references: {
model: "Users",
key: "id"
}
}
Now edit models/index.js as
import Sequelize from 'sequelize';
import env from '../env';
import UserModel from '../models/user';
import ContactModel from '../models/contact';
const Op = Sequelize.Op;
const operatorsAliases = {....};
const sequelize = new Sequelize(env.database, env.username, env.password, {
operatorsAliases,
host: env.host,
dialect: env.dialect,
pool: {
max: 10,
min: 0,
acquire: 30000,
idle: 10000
}
});
/**
* Link our models with sequelize
*/
const User = UserModel(sequelize, Sequelize);
const Contact = ContactModel(sequelize, Sequelize);
User.hasMany(Contact,{foreignKey:'user_id'});
module.exports = {
User,
Contact
};
Now run this command to make tables in the defined database
npx sequelize-cli db:migrate
Well, now our database and models are up and running.
Authentication
Now, let’s create signup and a login API for our application
First, delete exampleController.js file in controllers folder I just made this file for example
Then create a file named phoneBookController.js in controllers folder and initiate it with
import {User, Contact} from '../models';
import bcrypt from "bcrypt";
import {jwtAuthenticate} from "../auth";
import QueryHelper from "../helpers/queryHelper";
export function login(req, res) {
jwtAuthenticate({
username: req.body.username,
password: req.body.password,
usersTable: 'users',
usernameField: "username",
passwordField: 'password',
},res);
}
export function signup(req, res) {
bcrypt.hash(req.body.password, 10, (err, hash) => {
console.log(req.body.password);
User.create({username: req.body.username, password: hash}).then(user => res.status(200).json({
type: 'success',
message: 'Signed up!',
data: user
}));
});
}
Now lets set some routes for our project in routes/index.js
import {login, signup} from "../controllers/phoneBookController";
import {isJwtAuthenticated} from "../auth";
module.exports = function(app){
app.post('/login', login);
app.post('/signup', signup);
//other routes..
};
Now if we run the project with this command:
npm start
We can send post requests to these url
http://localhost:4000/signup
With this body
{
"username":"aratta",
"password":"aratta123"
}
We will make a user
Then if we use this url
http://localhost:4000/login
With this body
{
"username":"aratta",
"password":"aratta123"
}
In our response we will receive a field in the header named Authorization with a content like this:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTY0MTY1NzE2fQ.2RXk5GWZpETJnmUzDqhvAz8AjfSZjV_cfohKIPvGZfY
This is our JWT token and we must use it in APIs that needed authorization
CRUD
Now lets make a crud for contacts edit phoneBookController.js in controllers folder
import {User, Contact} from '../models';
import bcrypt from "bcrypt";
import {jwtAuthenticate} from "../auth";
import QueryHelper from "../helpers/queryHelper";
import fs from "fs";
import csv from "fast-csv";export function login(req, res) {
jwtAuthenticate({
username: req.body.username,
password: req.body.password,
usersTable: 'users',
usernameField: "username",
passwordField: 'password',
},res);
}
export function signup(req, res) {
bcrypt.hash(req.body.password, 10, (err, hash) => {
console.log(req.body.password);
User.create({username: req.body.username, password: hash}).then(user => res.status(200).json({
type: 'success',
message: 'Signed up!',
data: user
}));
});
}
/**
*
* @param req
* @param res
* @constructor
*/
export function createOrUpdateContact(req, res) {
const body = req.body;
new QueryHelper(Contact).createOrUpdate(body, res);
}
/**
* req.body: {where}
* @param req
* @param res
*/
export function readConditionalContacts(req, res) {
const body = req.body;
new QueryHelper(Contact).find(body, res,null).conditional();
}
/**
* req.body: Doctor
* @param req
* @param res
*/
export function readOneUser(req, res) {
const body = req.body;
const relations = [
{ model: Contact}
];
new QueryHelper(User).find(body, res,relations).one();
}
/**
* req.body: {where}
* @param req
* @param res
*/
export function deleteConditionalContacts(req, res) {
const condition = req.body.where;
new QueryHelper(Contact).delete(condition, res);
}
Then add these functions in routes with my jwtAuthentication middleware
import {
createOrUpdateContact, deleteConditionalContacts,
login,
readConditionalContacts,
readOneUser,
signup
} from "../controllers/phoneBookController";
import {isJwtAuthenticated} from "../auth";
module.exports = function(app){
app.post('/login', login);
app.post('/signup', signup);
app.post('/create-or-update-contact',isJwtAuthenticated, createOrUpdateContact);
app.post('/read-conditional-contacts',isJwtAuthenticated, readConditionalContacts);
app.post('/read-one-user',isJwtAuthenticated, readOneUser);
app.post('/delete-conditional-contacts',isJwtAuthenticated, deleteConditionalContacts);
//other routes..
};
Now we can use them with an Authorization field in our request’s head with that Bearer value that we received in the previous step.
E.g for create-or-update-contact we can use a body like this
{
"phone_number":"+1929394949",
"name":"aratta123",
"user_id":"1"
}
In the body, we must use keys with names exactly like our DB fields if needed.
For using conditional APIs like read-conditional-contacts we must use a key named where in our body something like:
{
"where":{"id":"1"}
}
Uploading a CSV and batch create data
In this step we want to do something cool, we want to upload a CSV with a request and insert the data in our database
edit phoneBookController.js in the controllers folder
First, make sure that you have imported these in your controller
import fs from "fs";
import csv from "fast-csv";
Then add this function
/**
* @param req
* @param res
*/
export function csvCreateContacts(req, res) {
// open uploaded file
const queryHelper = new QueryHelper(Contact);
csv.fromPath(req.file.path)
.on("data", function (data) {
queryHelper.createWithoutResponse({phone_number:data[0],name:data[1]}, res);
})
.on("end", function () {
fs.unlinkSync(req.file.path); // remove temp file
res.status(200).json({
type: 'success',
message: 'createdAll!',
data: null
});
})
}
Now edit routes/index.js file
First, make sure that you have imported this and csvCreateContacts function
import multer from 'multer';
Then initiated
const upload = multer({ dest: 'tmp/csv/' });
Now add a new route like
app.post('/csv-create-contacts',isJwtAuthenticated,upload.single('file'),csvCreateContacts);
Now you can upload a CSV file with the first column of phone numbers and second column of names and use it in the request body as a form
That’s it, guys we just made a back-end app with express js using my boilerplate
If you had any problem just take a look at this repository