Belajar Membuat Web Menggunakan Framework Express JS (Part 5) - CRUDPRO

Belajar Membuat Web Menggunakan Framework Express JS (Part 5)


Di part sebelumnya kita sudah bahas tentang membuat login di Framework Express JS, pada tutorial kali ini kita akan belajar untuk membuat halaman dashboard user menggunakan middleware, bagi yang belum mengikuti tutorial sebelumnya silakan ikuti terlebih dahulu di tutorial sebelumnya, bagi yang sudah silakan untuk melanjutkan di tutorial kali ini.

1. Edit halaman login

Buka file login.pug yang berada di dalam folder views/auth/login.pug, dibagian fungsi login() javascript terdapat response / callback yang belum bisa mengalihkan ke halaman dashboard, nah sekarang ubah source codenya menjadi seperti ini

extends layout

block stylesheets
    //- Todo

block content
    div(style="padding:50px;")
    div.row
        div.col-md-4.offset-md-4
            form.login-form(action=`${process.env.NODE_APP_URL}/login`)
                center
                    b
                        h4 Login User
                hr
                div.form-group
                    label(for="email") Email
                    input.form-control#email(type="email" placeholder="Email" name="email")
                    div.invalid-feedback#emailFeedback
                div.form-group
                    label(for="password") Password
                    input.form-control#password(type="password" placeholder="Password" name="password")
                    div.invalid-feedback#passwordFeedback
                div.form-group
                    button.btn.btn-primary.btn-block#login-button(type="button" onclick="login()") Login
                    br
                    center
                        a.text-muted(href=`${process.env.NODE_APP_URL}`) Back To Home
block scripts
    script.

        var _url = '#{process.env.NODE_APP_URL}';

        function login() {

            var loginButton = jQuery('#login-button'),
                email = jQuery('#email'),
                password = jQuery('#password'),
                emailFeedback = jQuery('#emailFeedback'),
                passwordFeedback = jQuery('#passwordFeedback');

            jQuery.ajax({
                url : _url + '/auth',
                method : 'post',
                dataType : 'json',
                data : jQuery('.login-form').serialize(),
                beforeSend : function() {
                    email.attr('class','form-control');
                    password.attr('class','form-control');
                    emailFeedback.html(``);
                    passwordFeedback.html(``);
                    loginButton.attr('disabled','disabled');
                },
                success : function(response) {

                    loginButton.removeAttr('disabled');

                    nativeToast({
                        message: response.message,
                        position: 'top',
                    });

                    if(!response.error) {
                        setTimeout(function() {
                            location.href = _url + '/user';
                        }, 1000);
                        return;
                    }

                },
                error : function(err) {

                    nativeToast({
                        message: 'Error the fields.',
                        position: 'top',
                    });

                    loginButton.removeAttr('disabled');

                    var jsonMessage = err.responseJSON.message;

                    if(jsonMessage.email != undefined) {
                        email.attr('class','form-control is-invalid');
                        emailFeedback.html(jsonMessage.email.msg);
                    }

                    if(jsonMessage.password != undefined) {
                        password.attr('class','form-control is-invalid');
                        passwordFeedback.html(jsonMessage.password.msg);
                    }

                }
            });
        }

Dan sekarang kita sudah mempunyai fungsinya redirect nya, Jika berhasil login maka akan dialihkan ke dalam halaman dashboard jika user tidak ditemukan maka akan muncul pesan dengan popup, copy source code dibawah ini kemudian save file di dalam folder public/stylesheets/toastr.css dan public/javascripts/toastr.js.

toastr.css

.native-toast {
  position: fixed;
  background-color: rgba(50, 50, 50, .8);
  border-radius: 33px;
  color: white;
  left: 50%;
  text-align: center;
  padding: 10px 20px;
  opacity: 0;
  z-index: 99999;
  transition: transform .25s, opacity .25s, top .25s;
  box-sizing: border-box;
}

.native-toast-bottom {
  bottom: 50px;
  -ms-transform: translateX(-50%) translateY(50px);
      transform: translateX(-50%) translateY(50px)
}

.native-toast-bottom.native-toast-shown {
  opacity: 1;
  -ms-transform: translateX(-50%) translateY(0);
      transform: translateX(-50%) translateY(0);
}

.native-toast-bottom.native-toast-edge {
  bottom: 0;
}

.native-toast-top {
  top: 50px;
  -ms-transform: translateX(-50%) translateY(-50px);
      transform: translateX(-50%) translateY(-50px)
}

.native-toast-top.native-toast-shown {
  opacity: 1;
  -ms-transform: translateX(-50%) translateY(0);
      transform: translateX(-50%) translateY(0);
}

.native-toast-top.native-toast-edge {
  top: 0;
}

.native-toast-center {
  top: 0;
  -ms-transform: translateX(-50%) translateY(-50px);
      transform: translateX(-50%) translateY(-50px)
}

.native-toast-center.native-toast-shown {
  opacity: 1;
  top: 50%;
  -ms-transform: translateX(-50%) translateY(-50%);
      transform: translateX(-50%) translateY(-50%);
}

.native-toast-edge {
  border-radius: 0;
  width: 100%;
  text-align: left;
}

@media screen and (min-width: 40rem) {
  .native-toast:not(.native-toast-edge) {
    max-width: 18rem;
  }
}

/*
  max-width does not seem to work in small screen?
*/

/*@media screen and (max-width: 768px) {
  .native-toast:not(.native-toast-edge) {
    max-width: 400px;
  }
}

@media screen and (max-width: 468px) {
  .native-toast:not(.native-toast-edge) {
    max-width: 300px;
  }
}*/

/* types */

.native-toast-error {
  background-color: #d92727;
  color: white;
}

.native-toast-success {
  background-color: #62a465;
  color: white;
}

.native-toast-warning {
  background-color: #fdaf17;
  color: white;
}

.native-toast-info {
  background-color: #5060ba;
  color: white;
}

[class^="native-toast-icon-"] {
  vertical-align: middle;
  margin-right: 8px
}

[class^="native-toast-icon-"] svg {
  width: 16px;
  height: 16px;
}

toastr.js

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global.nativeToast = factory());
}(this, (function () { 'use strict';

/*
object-assign
(c) Sindre Sorhus
@license MIT
*/

/* eslint-disable no-unused-vars */
var getOwnPropertySymbols = Object.getOwnPropertySymbols;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var propIsEnumerable = Object.prototype.propertyIsEnumerable;

function toObject(val) {
  if (val === null || val === undefined) {
    throw new TypeError('Object.assign cannot be called with null or undefined');
  }

  return Object(val);
}

function shouldUseNative() {
  try {
    if (!Object.assign) {
      return false;
    }

    // Detect buggy property enumeration order in older V8 versions.

    // https://bugs.chromium.org/p/v8/issues/detail?id=4118
    var test1 = new String('abc');  // eslint-disable-line no-new-wrappers
    test1[5] = 'de';
    if (Object.getOwnPropertyNames(test1)[0] === '5') {
      return false;
    }

    // https://bugs.chromium.org/p/v8/issues/detail?id=3056
    var test2 = {};
    for (var i = 0; i < 10; i++) {
      test2['_' + String.fromCharCode(i)] = i;
    }
    var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
      return test2[n];
    });
    if (order2.join('') !== '0123456789') {
      return false;
    }

    // https://bugs.chromium.org/p/v8/issues/detail?id=3056
    var test3 = {};
    'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
      test3[letter] = letter;
    });
    if (Object.keys(Object.assign({}, test3)).join('') !==
        'abcdefghijklmnopqrst') {
      return false;
    }

    return true;
  } catch (err) {
    // We don't expect any of the above to throw, but better to be safe.
    return false;
  }
}

var index = shouldUseNative() ? Object.assign : function (target, source) {
  var arguments$1 = arguments;

  var from;
  var to = toObject(target);
  var symbols;

  for (var s = 1; s < arguments.length; s++) {
    from = Object(arguments$1[s]);

    for (var key in from) {
      if (hasOwnProperty.call(from, key)) {
        to[key] = from[key];
      }
    }

    if (getOwnPropertySymbols) {
      symbols = getOwnPropertySymbols(from);
      for (var i = 0; i < symbols.length; i++) {
        if (propIsEnumerable.call(from, symbols[i])) {
          to[symbols[i]] = from[symbols[i]];
        }
      }
    }
  }

  return to;
};

var prevToast = null;

var icons = {
  warning: "<svg viewBox=\"0 0 32 32\" width=\"32\" height=\"32\" fill=\"none\" stroke=\"currentcolor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"6.25%\"><path d=\"M8 17 C8 12 9 6 16 6 23 6 24 12 24 17 24 22 27 25 27 25 L5 25 C5 25 8 22 8 17 Z M20 25 C20 25 20 29 16 29 12 29 12 25 12 25 M16 3 L16 6\" /></svg>",
  success: "<svg viewBox=\"0 0 32 32\" width=\"32\" height=\"32\" fill=\"none\" stroke=\"currentcolor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"6.25%\"><path d=\"M2 20 L12 28 30 4\" /></svg>",
  info: "<svg viewBox=\"0 0 32 32\" width=\"32\" height=\"32\" fill=\"none\" stroke=\"currentcolor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"6.25%\"><path d=\"M16 14 L16 23 M16 8 L16 10\" /><circle cx=\"16\" cy=\"16\" r=\"14\" /></svg>",
  error: "<svg viewBox=\"0 0 32 32\" width=\"32\" height=\"32\" fill=\"none\" stroke=\"currentcolor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"6.25%\"><path d=\"M16 3 L30 29 2 29 Z M16 11 L16 19 M16 23 L16 25\" /></svg>"
};

var Toast = function Toast(ref) {
  if ( ref === void 0 ) ref = {};
  var message = ref.message; if ( message === void 0 ) message = '';
  var position = ref.position; if ( position === void 0 ) position = 'bottom';
  var timeout = ref.timeout; if ( timeout === void 0 ) timeout = 3000;
  var el = ref.el; if ( el === void 0 ) el = document.body;
  var square = ref.square; if ( square === void 0 ) square = false;
  var type = ref.type; if ( type === void 0 ) type = '';
  var debug = ref.debug; if ( debug === void 0 ) debug = false;
  var edge = ref.edge; if ( edge === void 0 ) edge = false;

  if (prevToast) {
    prevToast.destroy();
  }

  this.message = message;
  this.position = position;
  this.el = el;
  this.timeout = timeout;

  this.toast = document.createElement('div');
  this.toast.className = "native-toast native-toast-" + (this.position);

  if (type) {
    this.toast.className += " native-toast-" + type;
    this.message = "<span class=\"native-toast-icon-" + type + "\">" + (icons[type] || '') + "</span>" + (this.message);
  }

  this.toast.innerHTML = this.message;

  if (edge) {
    this.toast.className += ' native-toast-edge';
  }

  if (square) {
    this.toast.style.borderRadius = '3px';
  }

  this.el.appendChild(this.toast);

  prevToast = this;

  this.show();
  if (!debug) {
    this.hide();
  }
};

Toast.prototype.show = function show () {
    var this$1 = this;

  setTimeout(function () {
    this$1.toast.classList.add('native-toast-shown');
  }, 300);
};

Toast.prototype.hide = function hide () {
    var this$1 = this;

  setTimeout(function () {
    this$1.destroy();
  }, this.timeout);
};

Toast.prototype.destroy = function destroy () {
    var this$1 = this;

  if (!this.toast) { return }

  this.toast.classList.remove('native-toast-shown');

  setTimeout(function () {
    if (this$1.toast) {
      this$1.el.removeChild(this$1.toast);
      this$1.toast = null;
    }
  }, 300);
};

function toast(options) {
  return new Toast(options)
}

var loop = function () {
  var type = list[i];

  toast[type] = function (options) { return toast(index({}, {type: type}, options)); };
};

for (var i = 0, list = ['success', 'info', 'warning', 'error']; i < list.length; i += 1) loop();

return toast;

})));

Lalu tambahkan block css di dalam file layout login views/auth/layout.pug, dan panggil file kedua yang tadi kita buat, sehingga akan menjadi seperti ini

doctype html
html
    head
        title #{title}

        meta(name='viewport', content='width=device-width, initial-scale=1, shrink-to-fit=no')

        link(rel='stylesheet', href='/bootstrap/dist/css/bootstrap.min.css')
        link(rel='stylesheet', href='/stylesheets/toastr.css')

        style.
            body {
                background-color:#eee;
            }
        block stylesheets
    body

        div.container-fluid
            block content
        script(src="/jquery/dist/jquery.min.js", type='text/javascript')
        script(src="/bootstrap/dist/js/bootstrap.min.js", type='text/javascript')
        script(src="/javascripts/toastr.js", type='text/javascript')
        block scripts

2. Membuat authenticated middleware

Apa itu middleware?

Middleware yaitu berfungsi sebagai penengah antara request dengan controller, maka ketika request diminta middleware otomatis akan mengecek request yang masuk terlebih dahulu kemudian jika valid maka akan dilanjutkan ke controller, jika request tidak valid maka request tidak akan dilanjutkan ke controller.

Buat folder dengan nama middleware, kemudian copy source code berikut lalu simpan dengan nama authenticatedMiddleware.js

const routes = require('../routes/routes'),
      routePrefix = routes()

module.exports = function(authenticatedRoutes) {
    return function (req, res, next) {

        if(req.originalUrl == routePrefix.auth.login) {
            if(req.session.userdata) {
                return res.redirect(routePrefix.dashboards.users.index);
            }
        }

        if(authenticatedRoutes.indexOf(req.originalUrl) >= 0) {
            if(req.session.userdata == undefined || req.session.userdata == '') {
                if(req.xhr == true) {
                    return res.status(403).json({
                        error : 1,
                        message : '403 Forbidden.'
                    });
                }
                return res.redirect(routePrefix.auth.login);
            }
        }

        next();
    }
}

Kemudian buat controller baru dengan nama userController.js seperti ini

exports.index = function(req, res, next) {
    res.render('users/index', {
        title : process.env.NODE_APP_NAME + ' Users',
        sess : JSON.parse(req.session.userdata)
    });
}

Lalu sekarang kita satukan semua route ke dalam 1 file route dan membuat list route nya di dalam folder routes, berikut source code nya

routes.js

module.exports = function() {
    return {
        index : '/',
        auth : {
            login : '/auth'
        },
        dashboards : {
            users : {
                index : '/user',
            }
        }
    };
}

web.js

'use-strict'

const   express = require('express'),
        router = express.Router(),
        authController = require('../controllers/authController'),
        userController = require('../controllers/userController'),
        { check } = require('express-validator'),
        routes = require('../routes/routes'),
        routePrefix = routes()

router

    /*
    * Index Route
    */
    .get(routePrefix.index, function(req, res, next) {
        res.render('index', { title : process.env.NODE_APP_NAME })
    })

    /*
    * Auth Routes
    */
    .get(routePrefix.auth.login, authController.index)
    .post(routePrefix.auth.login, [
        check('email','Email is required.').not().isEmpty(),
        check('email','Invalid email format.').isEmail(),
        check('password', 'Password is required.').not().isEmpty(),
    ], authController.login)

    /*
    * User Dashboard Routes
    */
    .get(routePrefix.dashboards.users.index, userController.index)

module.exports = router;

Kenapa kita satukan semua route di dalam file web.js karena jika ada beberapa route berbeda kita ribet juga harus membuat file route nya lagi satu persatu, maka untuk file route yang lama kita hapus saja dan sekarang di dalam folder routes hanya ada dua file yaitu web.js dan routes.js

Jika sudah, maka di dalam file app.js pun ikut berubah sehingga akan seperti ini

const  createError = require('http-errors'),
        express = require('express'),
        path = require('path'),
        cookieParser = require('cookie-parser'),
        logger = require('morgan'),
        expressSession = require('express-session'),
        app = express(),

        authenticatedMiddleware = require('./middleware/authenticatedMiddleware'),
        routes = require('./routes/routes'),
        routePrefix = routes(),
        webRouter = require('./routes/web')
        require('dotenv').config()

// view engine setup
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'pug')
app.use(logger('dev'))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static(path.join(__dirname, 'public')))
app.use(express.static(path.join(__dirname, 'bower_components')))
app.use(expressSession({
    secret : process.env.NODE_APP_KEY,
    saveUninitialized: false,
    resave : false
}))
app.use(function (req, res, next) {
    res.locals.route = routePrefix
    next()
})

/*
* Authenticated Routes
*/
const authenticatedRoutes = [
    routePrefix.dashboards.users.index
];
app.use(authenticatedMiddleware(authenticatedRoutes))
app.use('/', webRouter)

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    next(createError(404))
})

// error handler
app.use(function(err, req, res, next) {
    // set locals, only providing error in development
    res.locals.message = err.message
    res.locals.error = req.app.get('env') === 'development' ? err : {};
    // render the error page
    res.status(err.status || 500)
    res.render('error')
})

module.exports = app;

Di dalam file app.js kita sudah memanggil authenticated middlewarenya, lihat source code berikut

/*
* Authenticated Routes
*/
const authenticatedRoutes = [
    routePrefix.dashboards.users.index
];
app.use(authenticatedMiddleware(authenticatedRoutes))

3. Membuat halaman user dashboard

Pada tahap kedua kita sudah membuat middleware dan controllernya, di tahap ke tiga kita buat halaman dashboard user nya.

Buat folder baru bernama users di dalam folder views, kemudian buat file index.pug dan layout.pug simpan di dalam folder users tersebut.

Copy source code berikut

index.pug

extends layout

block stylesheets
    //- Todo

block content
    div.row

        div.col-md-3
            .card.x-pointer.menu(onclick="location.href = '/user'")
                .card-body
                    center
                        i.fa.fa-user-circle-o.fa-5(style="font-size:5em;")
                        p
                        h3
                            b USERS

block scripts
    script.

layout.pug

doctype html
html
    head
        title #{title}

        meta(name='viewport', content='width=device-width, initial-scale=1, shrink-to-fit=no')

        link(rel='stylesheet', href='/bootstrap/dist/css/bootstrap.min.css')
        link(rel='stylesheet', href='/stylesheets/toastr.css')
        link(href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css", rel="stylesheet", integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN", crossorigin="anonymous")

        style.
            body {
                background-color:#eee;
            }

            .x-pointer {
                cursor:pointer;
            }

            .menu {
                background-color: #fff;
                -webkit-transition: .2s all;
                -webkit-transition-delay: .2s;
                -moz-transition: .2s all;
                -moz-transition-delay: .2s;
                -ms-transition: .2s all;
                -ms-transition-delay: .2s;
                -o-transition: .2s all;  
                -o-transition-delay: .2s;
                transition: .2s all;
                transition-delay: .2s;
                color : #2d3436;
            }

            .menu:hover {
                border-bottom: 2px solid #0984e3;
                color : #0984e3;
                -webkit-transition-delay: 0s;
                -moz-transition-delay: 0s;
                -ms-transition-delay: 0s;
                -o-transition-delay: 0s;
                transition-delay: 0s;
            }

        block stylesheets
    body
        include includes/navbar
        div(style="padding-bottom:50px;")
        div.container-fluid
            block content
        script(src="/jquery/dist/jquery.min.js", type='text/javascript')
        script(src="/bootstrap/dist/js/bootstrap.min.js", type='text/javascript')
        script(src="/javascripts/toastr.js", type='text/javascript')
        block scripts

Jika sudah kita kita buat folder baru dengan nama includes didalam folder views/users dan buat file baru dengan nama navbar.pug simpan di dalam folder tersebut, berikut source code nya

header.navbar.navbar-expand-lg.navbar-dark.bg-primary
    a.navbar-brand(href=`${process.env.NODE_APP_URL}`) #{ process.env.NODE_APP_NAME }
    button.navbar-toggler(type='button', data-toggle='collapse', data-target='#navbarSupportedContent', aria-controls='navbarSupportedContent', aria-expanded='false', aria-label='Toggle navigation')
        span.navbar-toggler-icon
    #navbarSupportedContent.collapse.navbar-collapse
        ul.navbar-nav.mr-auto
            li.nav-item.active
                a.nav-link(href=`${ process.env.NODE_APP_URL + route.dashboards.users.index }`)
                    | Home
                    span.sr-only (current)
        ul.nav.navbar-nav.navbar-right
            li.nav-item.active
                a.nav-link(href=`${ process.env.NODE_APP_URL + route.auth.logout }`) Logout

4. Membuat fungsi logout

Tambahkan list route logout di dalam object auth dengan nama objectnya logout, tepatnya ada di dalam file routes.js tadi, maka akan seperti ini jadinya.

module.exports = function() {
    return {
        index : '/',
        auth : {
            login : '/auth',
            logout : '/auth/logout'
        },
        dashboards : {
            users : {
                index : '/user',
            }
        }
    };
}

Kemudian kita tambahkan 1 method di dalam file authController.js untuk menambahkan fungsi logoutnya kurang lebih seperti ini

exports.logout = function(req, res, next) {

    if(req.session.userdata) {
        req.session.userdata = null
    }

    if(req.xhr == true) {
        return res.json({
            error : 0,
            message : 'Logout successfull.'
        });
    }

    return res.redirect(routePrefix.auth.login);
}

Jika sudah kita buat route logoutnya di dalam file web.js, tambahkan source code berikut

.get(routePrefix.auth.logout, authController.logout)

Maka akan seperti ini hasilnya

'use-strict'

const   express = require('express'),
        router = express.Router(),
        authController = require('../controllers/authController'),
        userController = require('../controllers/userController'),
        { check } = require('express-validator'),
        routes = require('../routes/routes'),
        routePrefix = routes()

router

    /*
    * Index Route
    */
    .get(routePrefix.index, function(req, res, next) {
        res.render('index', { title : process.env.NODE_APP_NAME })
    })

    /*
    * Auth Routes
    */
    .get(routePrefix.auth.login, authController.index)
    .post(routePrefix.auth.login, [
        check('email','Email is required.').not().isEmpty(),
        check('email','Invalid email format.').isEmail(),
        check('password', 'Password is required.').not().isEmpty(),
    ], authController.login)
    .get(routePrefix.auth.logout, authController.logout)

    /*
    * User Dashboard Routes
    */
    .get(routePrefix.dashboards.users.index, userController.index)

module.exports = router;

Finally, beres juga membuat halaman dashboard user dengan middleware menggunakan Framework Express JS

membuat halaman dashboard user menggunakan middleware pada framework express js
Dashboard User

Download Source Code