Memulai Pembuatan Aplikasi Web dengan Express.js (4): CRUD User

Muhammad Arslan 25 Oktober 2016

Memulai Pembuatan Aplikasi Web dengan Express.js (4): CRUD User

Sebelum mengikuti tutorial ini, Anda diharuskan untuk mengikuti tutorial sebelumnya yaitu:

Persiapan

Sebagai awalan, Anda harus memasang sebuah npm yang bernama method-override. NPM tersebut digunakan untuk menjalankan metode DELETE dan PUT dari form HTML. Karena saat ini beberapa web browser belum mendukung metode DELETE dan PUT seperti layaknya GET dan POST. Kemudian satu lagi Anda harus memasang NPM express-validator yang akan digunakan sebagai alat untuk memvalidasi kiriman yang dikirimkan oleh user melalui form. Anda dapat memasang kedua npm tersebut di dalam proyek aplikasi dengan cara seperti berikut:
$ npm install method-override
$ npm install express-validator
Lalu silahkan sesuaikan file app.js Anda seperti pada kode berikut karena kita harus mengkonfigurasi method-override:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var flash = require('express-flash');
var session = require('express-session');
var expressValidator = require('express-validator');
var mongoose = require('mongoose');
var methodOverride = require('method-override');

var routes = require('./routes/index'); var users = require('./routes/users');

var app = express();

// view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade');

// uncomment after placing your favicon in /public //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use(session({secret:"rahasia12345"})); app.use(flash()); app.use(expressValidator()); app.use(methodOverride(function(req, res){ if (req.body && typeof req.body == 'object' && '_method' in req.body) { var method = req.body._method; delete req.body._method; return method; } }));

app.use('/', routes); app.use('/users', users);

// setting MongoDB dengan Mongoose var user = require('./models/user');

mongoose.Promise = global.Promise; mongoose.connect('mongodb://localhost/organo');

// catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); });

// error handlers

// development error handler // will print stacktrace if (app.get('env') === 'development') { app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: err }); }); }

// production error handler // no stacktraces leaked to user app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: {} }); });

module.exports = app;

Sebagai pengingat, kita akan menggunakan model User yang terdapat di file models/user.js:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var userSchema = new Schema({
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  email: { type: String, required: true },
  firstname: String,
  lastname: String,
  admin: Boolean,
},
{
    timestamps: true
});

var User = mongoose.model('User', userSchema);

module.exports = User;

Bila sudah yakin, mari kita lanjutkan ke bagian tutorial berikutnya :D.

Membuat Halaman Index User

Di halaman index user, Anda akan membuat sebuah halaman yang terdiri dari tabel user dan diatasnya terdapat sebuah tombol "Tambah User". Selain itu di setiap barisnya terdapat dua tombol kecil untuk edit dan hapus user. Sekarang mari kita buat terlebih dahulu route index di file routes/users.js. Route tersebut akan ditengahi oleh is_admin dan check_login. Kemudian di dalamnya akan terdapat kode untuk mengambil semua user dari collection User. Hasilnya akan di-render di dalam file views/users/index.jade yang akan kita buat setelah ini.

Silahkan buat kode berikut ke dalam file routes/users.js:

var express = require('express');
var crypto = require('crypto');
var User = require('../models/user');
var Auth_mdw = require('../middlewares/auth');

var router = express.Router();
var secret = 'codepolitan';
var session_store;

var default_password = crypto.createHmac('sha256', secret)
                   .update('default')
                   .digest('hex');

router.get('/', Auth_mdw.check_login, Auth_mdw.is_admin, function(req, res, next) {
    session_store = req.session;

    User.find({}, function(err, user){
        console.log(user);
        res.render('users/index', { session_store:session_store, users: user });
    }).select('username email firstname lastname admin createdAt updatedAt');
});

module.exports = router;

Sebelum memulai membuat kode index.jade, silahkan buat terlebih dahulu folder dengan nama users di dalam folder views. Kemudian buat file dengan nama index.jade di dalam folder users tersebut. Sekarang kita buat kode halaman index berikut di file views/users/index.jade:

extends ../layout/base
block content  
    section.content-header
        h1
            | Daftar Pengguna
            small Kelola pengguna Anda disini
    // Main content
    section.content
        // Your Page Content Here
        // /.row
        .row
            .col-xs-12
                - if (messages.msg_error)
                    .alert.alert-danger.alert-dismissable
                        button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
                        | #{messages.msg_error}
                - if (messages.msg_info)
                    .alert.alert-success.alert-dismissable
                        button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
                        | #{messages.msg_info}
                .box
                    .box-header
                        h3.box-title Pengguna
                        .box-tools
                            a(href="/users/add").btn.btn-primary.btn-sm
                                i.glyphicon.glyphicon-plus  
                                |   Tambah Pengguna Baru
                    // /.box-header
                    .box-body.table-responsive.no-padding

                        table.table.table-striped
                            thead
                                th No.
                                th Username
                                th Email
                                th Nama Depan
                                th Nama Belakang
                                th Admin
                                th Action
                            tbody
                                for user, index in users
                                    tr
                                        td #{index+1}
                                        td #{user.username}
                                        td #{user.email}
                                        td #{user.firstname}
                                        td #{user.lastname}
                                        td 
                                            if (user.admin)
                                                p Admin
                                            else
                                                p Member
                                        td
                                            div(style="display:inline-block;margin-right:5px;")
                                                a(href="/users/edit/#{user._id}").btn.btn-success.btn-xs
                                                    i.glyphicon.glyphicon-pencil
                                            div(style="display:inline-block")
                                                form(method="POST", action="/users/delete/#{user._id}")
                                                    input(type="hidden", name="_method", value="DELETE")
                                                    button(type="submit").btn.btn-danger.btn-xs
                                                        i.glyphicon.glyphicon-trash
                    // /.box-body
                // /.box
    // /.content

Ada beberapa kode yang cukup unik, misal ketika kita melakukan extend, digunakanlah tanda ".." untuk menandakan bahwa direktori kita naik satu level. Kemudian elemen div dalam Jade tidak perlu diketik cukup langsung mengetik nama salah satu class-nya saja. Kemudian tombol hapus ternyata bukan tombol biasa, melainkan sebuah tombol submit di dalam sebuah form yang mengirimkan juga sebuah data dengan nama _method yang diisi dengan nilai "DELETE". Seperti yang sudah dibahas sebelumnya, saat ini web browser baru mendukung dua metode yaitu POST dan GET. Oleh karena itu kita harus melakukan override method untuk menggunakan PUT dan DELETE.

Bila semuanya sudah sesuai, Anda dapat menjalankan aplikasi dengan npm start atau nodemon kemudian lihat contoh gambar berikut:

Selection_001

Bila Anda mengikuti tutorial sebelumnya, Anda akan melihat dua user yang sudah dibuat yaitu superadmin dan supermember.

Membuat Halaman Tambah User

Sekarang kita tambahkan juga fitur tambah user dengan URL "/add" yang terbagi menjadi dua route yang satu GET dan satunya lagi POST. Kedua route akan ditengahi oleh is_admin dan check_login. Silahkan tambahkan kode berikut ke dalam file routes/users.js:
var express = require('express');
var crypto = require('crypto');
var User = require('../models/user');
var Auth_mdw = require('../middlewares/auth');

var router = express.Router(); var secret = 'codepolitan'; var session_store;

var default_password = crypto.createHmac('sha256', secret) .update('default') .digest('hex');

...................................................................................................

router.get('/add', Auth_mdw.check_login, Auth_mdw.is_admin, function(req, res, next) { session_store = req.session;

res.render('users/add', { session_store:session_store });

});

router.post('/add', Auth_mdw.check_login, Auth_mdw.is_admin, function (req, res, next){ session_store = req.session;

req.assert('username', 'Nama diperlukan').isAlpha().withMessage('Username harus terdiri dari angka dan huruf').notEmpty();
req.assert('email', 'E-mail tidak valid').notEmpty().withMessage('E-mail diperlukan').isEmail();
req.assert('firstname', 'Nama depan harus terdiri dari angka dan huruf').isAlpha();
req.assert('lastname', 'Nama belakang harus terdiri dari angka dan huruf').isAlpha();

var errors = req.validationErrors();  
console.log(errors);

if (!errors)
{
    v_username = req.sanitize( 'username' ).escape().trim();
    v_email = req.sanitize( 'email' ).escape().trim();
    v_firstname = req.sanitize( 'firstname' ).escape().trim();
    v_lastname = req.sanitize( 'lastname' ).escape().trim();
    v_admin = req.sanitize( 'admin' ).escape().trim();

    User.find({username:req.param('username')}, function (err, user){
        if (user.length == 0)
        {
            var admin = new User({
                username: v_username,
                email: v_email,
                password: default_password,
                firstname: v_firstname,
                lastname: v_lastname,
                admin: v_admin,
            });

            admin.save(function(err) {
                if (err) 
                {
                    console.log(err);

                    req.flash('msg_error', 'Punten, sepertinya ada masalah dengan sistem kami...');
                    res.redirect('/users');
                }
                else
                {
                    req.flash('msg_info', 'User berhasil dibuat...');
                    res.redirect('/users');
                }
            });
        }
        else
        {
            req.flash('msg_error', 'Punten, username sudah digunakan...');
            res.render('users/add', { 
                session_store:session_store,
                username: req.param('username'),
                email: req.param('email'),
                firstname: req.param('firstname'),
                lastname: req.param('lastname'),
            });
        }
    });
}
else
{   
    // menampilkan pesan error
    errors_detail = "<p>Punten, sepertinya ada salah pengisian, mangga check lagi formnyah!</p><ul>";

    for (i in errors)
    {
        error = errors[i];
        errors_detail += '<li>'+error.msg+'</li>';
    }

    errors_detail += "</ul>";

    req.flash('msg_error', errors_detail);
    res.render('users/add', {
        session_store: session_store, 
        username: req.param('username'),
        email: req.param('email'),
        firstname: req.param('firstname'),
        lastname: req.param('lastname'),
    });
}

});

module.exports = router;

Pada route /add dengan metode GET, kita lakukan render form add.jade yang berada di folder views/users. File tersebut akan kita buat nanti. Kemudian pada route /add dengan metode POST terdapat proses validasi terlebih dahulu untuk beberapa data seperti username dan email. Validasi pun dilakukan bila lolos maka ke tahap selanjutnya, bila gagal maka akan dikembalikan ke halaman tambah user dengan isian form sebelumnya dan pesan pengisian yang salah. Bila proses validasi berhasil maka data yang dikirimkan akan dibersihkan dengan menggunakan escape() dan trim().

Selanjutnya kita cari apakah user sudah pernah dibuat sebelumnya atau tidak. Bila sudah pernah ada maka dikembalikan ke halaman tambah user dengan memberikan peringatan bahwa user sudah ada. Bila belum Ada, maka kita gunakan instans dari model User untuk membuat document baru, setelah diisi lalu disimpan dengan menggunakan method save(). Bila tidak ada masalah dengan MongoDB, proses akan di-redirect ke URL "/users" sambil membawa pesan bahwa user sudah berhasil dibuat.

Sekarang mari kita buat kode Jade berikut di dalam file views/users/add.jade:

extends ../layout/base
block content  
    section.content-header
        h1
            | Tambah Pengguna
            small Tambah pengguna Anda disini
    // Main content
    section.content
        // Your Page Content Here
        // /.row
        .row
            .col-xs-12
                .box
                    .box-header
                        h3.box-title Pengguna Baru
                    // /.box-header
                    .box-body
                        - if (messages.msg_error)
                            .alert.alert-danger.alert-dismissable
                                button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
                                | !{messages.msg_error}
                        - if (messages.msg_info)
                            .alert.alert-success.alert-dismissable
                                button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
                                | #{messages.msg_info}
                        form(action="/users/add", method="POST").form-horizontal
                            .form-group
                                label(for="username").col-md-3.control-label Username
                                .col-md-6
                                    input(type="text", name="username", id="username", value="#{ (username) ? username : '' }").form-control
                            .form-group
                                label(for="email").col-md-3.control-label E-Mail
                                .col-md-6
                                    input(type="text", name="email", id="email", value="#{ (email) ? email : '' }").form-control
                            .form-group
                                label(for="firstname").col-md-3.control-label Nama Depan
                                .col-md-6
                                    input(type="text", name="firstname", id="firstname", value="#{ (firstname) ? firstname : '' }").form-control
                            .form-group
                                label(for="lastname").col-md-3.control-label Nama Belakang
                                .col-md-6
                                    input(type="text", name="lastname", id="lastname", value="#{ (lastname) ? lastname : '' }").form-control
                            .form-group
                                label(for="admin").col-md-3.control-label Hak Akses
                                .col-md-6
                                    select(type="text", name="admin", id="admin").form-control
                                        option(value="true") Admin
                                        option(value="false") Member
                            .form-group
                                .col-sm-offset-3.col-sm-6
                                    button(type="submit").btn.btn-primary
                                        i.fa.fa-save
                                        |   Simpan
                                    |  
                                    a(href="/users").btn.btn-danger Batal

                    // /.box-body
                // /.box
    // /.content

Sekarang mari kita lihat hasilnya dengan menjalankan aplikasi menggunakan npm start atau nodemon:

Selection_002 Selection_003 Selection_004 Selection_005 Selection_012

Anda pun dapat masuk sebagai akun kresnagaluh dengan menggunakan password default:

Selection_010 Selection_011
Ada beberapa hal yang perlu Anda ingat dalam cara mengankap suatu data yang dikirimkan oleh client. Pertama bila data berasal dari URL maka digunakan req.params.nama_variabel, sedangkan bila data berasal dari form maka diambil dengan menggunakan req.param('nama_variabel'). Hal ini akan terus digunakan hingga episode terakhir tutorial berseri ini.

Membuat Halaman Edit User

Sekarang mari kita lanjutkan dengan edit user. Silahkan tambahkan kode route "/edit" dengan metode GET dan PUT berikut ke file routes/users.js:
var express = require('express');
var crypto = require('crypto');
var User = require('../models/user');
var Auth_mdw = require('../middlewares/auth');

var router = express.Router(); var secret = 'codepolitan'; var session_store;

var default_password = crypto.createHmac('sha256', secret) .update('default') .digest('hex');

...................................................................................................

router.get('/edit/(:id)', Auth_mdw.check_login, Auth_mdw.is_admin, function(req, res, next) { session_store = req.session;

User.findOne({_id:req.params.id}, function (err, user){
    if (user)
    {
        console.log(user);

        res.render('users/edit', { session_store:session_store, user: user });
    }
    else
    {
        req.flash('msg_error', 'Punten, user tidak ditemukan!');
        res.redirect('/users');
    }
});

});

router.put('/edit/(:id)', Auth_mdw.check_login, Auth_mdw.is_admin, function (req, res, next){ session_store = req.session;

req.assert('username', 'Nama diperlukan').isAlpha().withMessage('Username harus terdiri dari angka dan huruf').notEmpty();
req.assert('email', 'E-mail tidak valid').notEmpty().withMessage('E-mail diperlukan').isEmail();
req.assert('firstname', 'Nama depan harus terdiri dari angka dan huruf').isAlpha();
req.assert('lastname', 'Nama belakang harus terdiri dari angka dan huruf').isAlpha();

var errors = req.validationErrors();  
console.log(errors);

if (!errors)
{
    v_username = req.sanitize( 'username' ).escape().trim();
    v_email = req.sanitize( 'email' ).escape().trim();
    v_firstname = req.sanitize( 'firstname' ).escape().trim();
    v_lastname = req.sanitize( 'lastname' ).escape().trim();
    v_admin = req.sanitize( 'admin' ).escape().trim();


    User.findById(req.params.id, function(err, user){
        user.username = req.param('username');
        user.email = req.param('email');
        user.firstname = req.param('firstname');
        user.lastname = req.param('lastname');
        user.admin = req.param('admin');

        user.save(function(err, user){
            if (err) 
            {
                req.flash('msg_error', 'Punten, sepertinya ada masalah dengan sistem kami...');
            }
            else
            {
                req.flash('msg_info', 'Edit user berhasil!');
            }

            res.redirect('/users/edit/'+req.params.id);

        });
    });
}
else
{   
    // menampilkan pesan error
    errors_detail = "<p>Punten, sepertinya ada salah pengisian, mangga check lagi formnyah!</p><ul>";

    for (i in errors)
    {
        error = errors[i];
        errors_detail += '<li>'+error.msg+'</li>';
    }

    errors_detail += "</ul>";

    req.flash('msg_error', errors_detail);
    res.render('users/edit', {
        _id: req.params.id, 
        session_store: session_store, 
        username: req.param('username'),
        email: req.param('email'),
        firstname: req.param('firstname'),
        lastname: req.param('lastname'),
    });
}

});

module.exports = router;

Pada route "/edit" metode GET kita pilih dulu user yang akan di-edit, lalu datanya kita kirimkan ke file edit.jade yang ada di folder views/users. Sedangkan untuk route "/edit" metode PUT kita validasi terlebih dahulu kemudian dibersihkan. Selanjutnya kita pilih user yang akan di-edit, dan kita isi dengan nilai baru kemudian disimpan. Terakhir kita akan di-redirect ke halaman edit lagi dengan diberikan pesan bahwa edit telah berhasil.

Sekarang kita buat terlebih dahulu file dengan nama edit.jade di folder views/users, dan buat kode berikut di dalam file tersebut:

extends ../layout/base
block content  
    section.content-header
        h1
            | Edit Pengguna
            small Edit pengguna Anda disini
    // Main content
    section.content
        // Your Page Content Here
        // /.row
        .row
            .col-xs-12
                .box
                    .box-header
                        h3.box-title Mengedit data
                    // /.box-header
                    .box-body
                        - if (messages.msg_error)
                            .alert.alert-danger.alert-dismissable
                                button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
                                | !{messages.msg_error}
                        - if (messages.msg_info)
                            .alert.alert-success.alert-dismissable
                                button.close(type='button', data-dismiss='alert', aria-hidden='true') ×
                                | #{messages.msg_info}
                        form(action="/users/edit/#{ (_id == undefined) ? user._id : _id }", method="POST").form-horizontal
                            .form-group
                                label(for="username").col-md-3.control-label Username
                                .col-md-6
                                    input(type="text", name="username", id="username", value="#{ (username == undefined) ? user.username : username }").form-control
                            .form-group
                                label(for="email").col-md-3.control-label E-Mail
                                .col-md-6
                                    input(type="text", name="email", id="email", value="#{ (email == undefined) ? user.email : email }").form-control
                            .form-group
                                label(for="firstname").col-md-3.control-label Nama Depan
                                .col-md-6
                                    input(type="text", name="firstname", id="firstname", value="#{ (firstname == undefined) ? user.firstname : firstname }").form-control
                            .form-group
                                label(for="lastname").col-md-3.control-label Nama Belakang
                                .col-md-6
                                    input(type="text", name="lastname", id="lastname", value="#{ (lastname == undefined) ? user.lastname : lastname }").form-control
                            .form-group
                                label(for="admin").col-md-3.control-label Hak Akses
                                .col-md-6
                                    select(type="text", name="admin", id="admin").form-control
                                        option(value="true") Admin
                                        option(value="false") Member
                            .form-group
                                input(type="hidden", name="_method", value="PUT")
                                .col-sm-offset-3.col-sm-6
                                    button(type="submit").btn.btn-primary
                                        i.fa.fa-save
                                        |   Simpan
                                    |  
                                    a(href="/users").btn.btn-danger Kembali

                    // /.box-body
                // /.box
    // /.content

Untuk melihat hasilnya, silahkan jalankan aplikasi organo ini dengan npm start atau nodemon:

Selection_006 Selection_007 Selection_008

Membuat Hapus User

Dan yang terakhir untuk tutorial ini, Anda akan membuat fitur hapus user dengan membuat sebuah route "/delete/(:id)" yang bermetode DELETE. Silahkan tambahkan kode hapus user ke file routes/users.js:
var express = require('express');
var crypto = require('crypto');
var User = require('../models/user');
var Auth_mdw = require('../middlewares/auth');

var router = express.Router(); var secret = 'codepolitan'; var session_store;

var default_password = crypto.createHmac('sha256', secret) .update('default') .digest('hex');

...................................................................................................

router.delete('/delete/(:id)', Auth_mdw.check_login, Auth_mdw.is_admin, function (req, res, next){ User.findById(req.params.id, function(err, user){ user.remove(function(err, user){ if (err) { req.flash('msg_error', 'Punten, sepertinya user yang dimaksud sudah tidak ada. Dan kebetulan lagi ada masalah sama sistem kami :D'); } else { req.flash('msg_info', 'Hapus user berhasil!'); } res.redirect('/users'); }); }); });

module.exports = router;

Disini route untuk menghapus user hanya melakukan pencarian terlebih dahulu terhadap collection User bila ada maka akan dihapus dengan menggunakan method remove() bila tidak ada maka akan dialihkan ke URL "/users" dengan membawa pesan error bila berhasil maka akan muncul pesan "Hapus user berhasil!" seperti pada gambar berikut:

Selection_009

Penutup

Dengan menyelesaikan tutorial ini, Anda dapat menguasai validasi dan menggunakan metode PUT dan DELETE, di Express.js. Tidak hanya itu Anda pun dapat mengetahui pembuatan CRUD dasar, sebelum membuat CRUD lainnya di tutorial ini. Tujuan dari tutorial ini adalah membuat aplikasi web yang cukup kompleks dimana Anda dapat membuat aplikasi yang dapat mengelola sebuah keanggotaan dan kepengurusan di organisasi.

Tetap pantengin terus Codepolitan yah, happy coding :D.

(codepolitan/expressjs/nodejs)