Cara Membuat Aplikasi Web System Monitor dengan Node.js



Aplikasi berbasis web semakin mudah dibuat akhir-akhir ini.

Dengan lahirnya beberapa front-end javascript framework seperti Vue.js, pengembangan aplikasi web menjadi lebih ter-manage dengan baik.

Apalagi, front-end framework tersebut didukung oleh teknologi back-end dari Node.js.

Dengan menggunakan Node.js, kita dapat melakukan sebuah operasi secara nonblocking.

Artinya, server bisa merespon request lebih cepat.

Dengan adanya peluang tersebut, saya sebagai penulis berpendapat bahwa ada gunanya mempelajari teknologi-teknologi tersebut.

Oleh karena itulah, kali ini kita akan mempelajari pembuatan aplikasi web berbasis Node.js dan framework front-end yang cukup populer, yakni Vue.js.

Download source code project ini di sini:


Apa yang Akan Kita Buat

Kali ini, kita akan belajar membuat aplikasi web system monitoring dengan Node.js, Express.js, Vue.js, dan Chart.js.

Aplikasi ini berguna untuk memantau system performance, misalnya, CPU usage, memory usage, dan berbagai info lainnya.

Kurang lebih, aplikasi ini mirip Task Manager-nya Windows, tetapi berjalan di web.

Akan tetapi, berbeda dengan Windows Task Manager, aplikasi ini akan mengukur system performance dari server di mana aplikasi ini di-host, bukan untuk komputer user, walaupun user dapat melihat hasilnya di komputer user melalui browser.

Adapun data-data yang akan diperoleh melalui aplikasi ini adalah:

  • Current CPU usage
  • CPU usage over time
  • Current memory usage
  • Memory usage over time
  • Jumlah core CPU
  • Platform server yang meng-host aplikasi ini
  • Free memory
  • Total memory
  • System uptime
  • Process uptime


Untuk melihat aplikasi ini yang sudah jadi, buka browser Anda ke:
https://ls-web-system-monitor.herokuapp.com
Atau Anda boleh lihat screenshotnya di sini:


Mengapa Kita Membuatnya

Aplikasi web system monitoring ini berguna untuk memantau server performance, sehingga kita dapat dengan mudah mengetahui apakah server sedang terbebani atau tidak.

Selain itu, pembuatan aplikasi ini sangat bermanfaat untuk mempelajari konsep-konsep yang berkaitan dengan Node.js dan package-package pendukungnya dan yang terpenting adalah memberikan gambaran umum bagaimana aplikasi web single-page berbasis Node.js dibuat.

Bagaimana Kita Membuatnya
Untuk membuatnya, kita perlu memahami rancangan dari aplikasi ini.

Perhatikanlah gambar di bawah ini:


Pertama-tama, kita berasumsi bahwa Node.js telah terinstall di server.

Begitu pula dengan script aplikasi ini yang telah selesai dibuat.

Pada saat user membuka URL (misalnya server Anda adalah localhost port 5000):
http://localhost:5000/
Maka server akan meresponnya dengan sebuah file "index.html" yang diolah di browser.

Setelah itu "index.html" akan mengeksekusi AJAX untuk melakukan request ke localhost port 5000 mengenai data yang harus ditampilkan di browser, misalnya CPU usage, memory usage dan lain lain.

Eksekusi javascript ini akan dilakukan setiap selang waktu satu detik.

Server akan meresponnya dengan data terkait dalam format JSON.

Data dalam bentuk JSON tersebut akan diolah di browser menjadi data yang siap saji, misalnya dalam bentuk grafik.

Selanjutnya javascript di "index.html" akan mengupdate grafik juga setiap selang waktu satu detik.

Hal ini terjadi terus-menerus sampai user menutup halaman web tersebut.

Yang Anda Butuhkan sebelum Kita Belajar

Perlu saya akui, bahwa level artikel ini adalah menengah. Saya berasumsi bahwa Anda telah memahami pemrograman javascript dan setidaknya pernah membuat aplikasi web sebelumnya.

Di samping itu, Anda juga membutuhkan ini:

  • Node.js dan NPM.
  • Text Editor apapun, tapi saya menyarankan Visual Studio Code (gratis, silahkan cari di google).
  • Web Browser apapun, tapi saya menyarankan Chrome.
  • Koneksi internet.

Menginstall Node.js dan NPM

Langkah pertama dalam pembuatan system monitor ini adalah menginstall Node.js dan NPM. Caranya adalah dengan mendownload installer Node.js dari situs resminya.

Installer tersebut sudah mencakup Node.js dan NPM.

Cara menginstallnya cukup sederhana, hanya klik next dan finish saja.


Setelah Node.js terinstall, maka NPM juga akan terinstall.

Membuat Project Node.js

Setelah menginstall Node.js dan NPM, langkah selanjutnya adalah membuat project Node.js.

Pertama-tama, buatlah folder dengan nama "ls-web-system-monitor".

Lokasi folder tersebut bisa di mana saja, pastikan di tempat yang mudah dijangkau.

Untuk membuatnya, bisa menggunakan Right Click>New>Folder di Windows Explorer.

Setelah itu, masuklah ke dalam folder tersebut.

Lalu di Windows Explorer, pergi ke menu File>Open Windows Power Shell>Open Windows Power Shell as Administrator.

Dengan mengklik menu tersebut, Power Shell akan dibuka sebagai administrator pada folder "ls-web-system-monitor".

Power Shell ini sebenarnya mirip dengan Command Prompt, hanya saja ada fiturfitur tambahan seperti beberapa perintah yang mirip dengan Terminal yang ada di Linux.

Setelah Power Shell dibuka ketik:
npm init
Nanti akan diajukan beberapa pertanyaan. Isilah dengan jawaban ini:
name: ls-web-system-monitor
version: 1.0.0
description: terserah
entry point: server.js
test command: kosongkan
git repository: kosongkan
keywords: kosongkan
author: nama Anda
license: ISC
is this ok: yes
Pastikan Anda berada dalam folder "ls-web-system-monitor".

Maka project Node.js dengan nama "ls-web-system-monitor" selesai dibuat.

Menginstall Package yang Dibutuhkan

Setelah project Node.js dibuat, kita akan menginstall package yang diperlukan dalam project ini.

Ada cukup banyak package yang diperlukan, tetapi cara menginstallnya cukup mudah.

Vue.js

npm install vue --save

Vue.js adalah package Node.js yang berfungsi sebagai framework untuk front-end. Framework ini berguna untuk mengembangkan aplikasi web yang single-page seperti yang sedang kita buat ini.

Perhatikan bahwa di sini kita menggunakan argument --save. Tujuannya adalah agar package yang kita install akan dimasukkan ke dalam daftar package di "package.json". Dengan ini, apabila kita memiliki source code tanpa package, kita akan bisa menginstall semua package sekaligus dengan:
npm install 

Vue-Resource

npm install vue-resource --save
Vue-Resource adalah HTTP client untuk Vue.js. Dengan ini kita bisa menggunakan HTTP request dari client side.

Bootstrap

npm install bootstrap --save
Bootstrap adalah library CSS untuk membuat tampilan web menjadi responsive.

JQuery

npm install jquery --save
JQuery dibutuhkan karena Bootstrap membutuhkannya.

Chart.js

npm install chart.js --save
Chart.js adalah package Node.js yang berfungsi sebagai library yang dapat menampilkan grafik. Kita akan membuat grafik pie dan line dengan Chart.js.

OS-Utils Module

npm install os-utils --save
Package ini berfungsi untuk mendapatkan informasi terkait dengan operating system seperti jumlah core pada CPU, penggunaan CPU, penggunaan memory, system uptime, dan process uptime.

Express.js

npm install express --save
Package ini merupakan framework Node.js. Framework ini berfungsi untuk menyederhanakan routing dari aplikasi yang akan kita buat.

FS Module

Module ini berfungsi untuk menulis file snapshot aplikasi ini yang nantinya akan bisa di-download oleh user ke komputer mereka. Kita tidak akan menginstall module ini, karena module ini sudah ada secara default bersama Node.js.

Setelah package tersebut kita install, perhatikanlah pada folder "ls-web-system-monitor". Di sana akan kita jumpai folder baru bernama "node_modules". Di sinilah semua package yang kita install berada.

Mengimpor Semua Package yang Telah Kita Install
Setelah semua package kita install, kita harus mengimpornya terlebih dahulu sebelum dapat digunakan.

Pertama-tama buatlah file bernama "server.js" di dalam folder "ls-web-system-monitor".

Selanjutnya, untuk mengimpor salah satu package, buka file target lalu tulis:

var apapun = require("nama_package");

Jadi, jika kita akan mengimpor package "os-utils", kita akan menulis ini pada file "server.js":

var osu = require("os-utils");

Kali ini, kita akan mengimpor semua package yang telah kita install, maka tulislah ini pada file "server.js":

var osu = require('os-utils');
var fs = require('fs');
var express = require('express');
var app = express();

Perhatikan bahwa pada saat mengimport Express.js, ada lanjutan seperti ini:

var app = express();

Hal itu dilakukan karena pada saat kita me-require Express.js, yang diimpor adalah fungsi. Untuk mendapatkan objectnya, fungsi harus dijalankan.

Lalu bagaimana dengan Vue.js dan Chart.js?

Kedua package tersebut merupakan package front-end. Dengan kata lain, package tersebut hanya digunakan dalam file HTML yang akan dieksekusi dari client.

Sementara, file "server.js" adalah server side script yang berjalan di server. Jadi file "server.js" tidak membutuhkan kedua package tersebut untuk dijalankan di server.

Untuk mengimpor kedua package tersebut, kita harus membuat file HTML terlebih dahulu, lalu mengimpornya seperti mengimpor javascript dalam HTML.

Hal itu akan dibahas pada bagian selanjutnya.

Sementara ini, fokuskan perhatian Anda terlebih dahulu pada server.

Membuat Kerangka Request Handler

Mengetes Request Handler Express

Sampai saat ini, secara teori kita sudah bisa membuat HTTP server sederhana.

Untuk mencobanya, tulis kode di bawah ini pada file baru bernama "servertest.js":

var express = require('express');
var app = express();

app.get('/', function (req, res) {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.end("Hello World\n");
});

app.listen(5000, function () {
    console.log('Node app is running on port 5000');
});

Saya sengaja menuliskannya pada file lain agar konsentrasi kita pada file "server.js" tidak buyar.

Setelah menuliskannya, jalankan perintah ini pada Power Shell:
node servertest.js
Tampilannya akan seperti ini:


Penjelasannya...

Pada saat user memasukkan input "http://localhost:5000/" pada browser, yang artinya browser akan menyambungkan dirinya dengan server pada alamat localhost, port 5000, maka request "/" akan diminta pada "servertest.js".

Request ini disaring melalui script "servertest.js" pada baris kode:

app.get('/', function (req, res) { //YANG INI
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.end("Hello World\n");
});

Tentu saja itu hanya bisa terjadi apabila "servertest.js" memiliki instruksi ini:

app.listen(5000, function () {//YANG INI
    console.log('Node app is running on port 5000');
});

Jadi fungsi get akan menyaring request GET dari browser, dan fungsi "app.listen" akan mendengarkannya pada port yang diberikan, dalam hal ini 5000.

Mengembangkan Request Handler Express Pada Aplikasi Kita

Dengan dasar percobaan tadi, sekarang kita bisa mengembangkan request handler untuk menangani semua request yang relevan pada aplikasi yang sedang kita kerjakan ini.

Anda boleh menghapus atau memindahkan file "servertest.js" ke tempat lain agar tidak mengganggu konstentrasi. Itu terserah Anda.

Yang penting, sekarang kita fokuskan kembali pikiran kita pada file "server.js".

Sekarang, kita akan menambahkan baris kode ini di file "server.js":

app.get('/', function (req, res) {
    //di sini kita akan mengembalikan file index.html
});
app.get('/api/home', function (req, res) {
    //di sini kita akan mengembalikan json untuk konten home
});
app.get('/api/about', function (req, res) {
    //di sini kita akan mengembalikan json untuk konten about
});
app.get('/api/contact', function (req, res) {
    //di sini kita akan mengembalikan json untuk konten contact
});
app.get('/api/os_utils_cpu_usage', function (req, res) {
    //di sini kita akan mengembalikan json untuk nilai cpu usage
});
app.get('/api/os_utils_memory_usage', function (req, res) {
    //di sini kita akan mengembalikan json untuk nilai memory usage
});
app.get('/api/os_utils_others', function (req, res) {
    //di sini kita akan mengembalikan json untuk info lainnya
});
app.get('/api/download_snapshot', function (req, res) {
    //di sini kita akan mengembalikan file snapshot untuk didownload
});

Tujuan dari setiap request handler dijelaskan melalui komentarnya. Semua dapat dijelaskan melalui komentar tersebut.

Hanya saja, perhatikan pada:

app.get('/', function (req, res) {
    //di sini kita akan mengembalikan file index.html
});

dan

app.get('/api/download_snapshot', function (req, res) {
    //di sini kita akan mengembalikan file snapshot untuk didownload
});

Yang pertama, "app.get('/' ....".

Di sana kita mengembalikan response berupa file HTML.

Sedangkan yang ke-2, "app.get('/api/download_snapshot' ...".

Di sana kita akan mengembalikan file snapshot untuk didownload.

Dengan kata lain, yang ke-2 adalah download link.

Sisanya, semua request diresponse dengan JSON.

Ini adalah prinsip utama dalam single-page web application, di mana hanya sebuah file HTML saja yang dikembalikan, sedangkan serah-terima data lainnya dari aplikasi tersebut dilakukan melalui JSON yang diterima melalui AJAX.

Dengan cara tersebut, user tidak perlu me-refresh halaman untuk mendapatkan update.

Jadi, sampai saat ini keseluruhan kode dalam file "server.js" adalah:

var osu = require('os-utils');
var fs = require('fs');
var express = require('express');
var app = express();

app.get('/', function (req, res) {
    //di sini kita akan mengembalikan file index.html
});
app.get('/api/home', function (req, res) {
    //di sini kita akan mengembalikan json untuk konten home
});
app.get('/api/about', function (req, res) {
    //di sini kita akan mengembalikan json untuk konten about
});
app.get('/api/contact', function (req, res) {
    //di sini kita akan mengembalikan json untuk konten contact
});
app.get('/api/os_utils_cpu_usage', function (req, res) {
    //di sini kita akan mengembalikan json untuk nilai cpu usage
});
app.get('/api/os_utils_memory_usage', function (req, res) {
    //di sini kita akan mengembalikan json untuk nilai memory usage
});
app.get('/api/os_utils_others', function (req, res) {
    //di sini kita akan mengembalikan json untuk info lainnya
});
app.get('/api/download_snapshot', function (req, res) {
    //di sini kita akan mengembalikan file snapshot untuk didownload
});

app.listen(5000, function () {
    console.log('Node app is running on port', 5000);
});

Anda bisa mengetest server dengan:
node server.js
Sampai saat ini server belum bisa memberikan output apapun, tetapi telah melakukan listen di port 5000.

Membuat Pilihan Port yang Didengar Lebih Dinamis

Walaupun sebenarnya cukup dengan hanya membiarkan port yang didengarkan adalah port 5000, ada cara agar port yang kita tentukan lebih dinamis.

Jadi kita dapat mengganti port yang didengar aplikasi ini dengan port lain tanpa mengubah kode.

Caranya dengan menggunakan:
process.env.PORT
Dengan menggunakan itu, maka kita bisa menge-set port dalam environment variable, lalu nilai port tersebut akan digunakan dalam kode kita.

Sekarang coba tulis kode ini pada file "server.js" di atas request handler dan di bawah require:

//...
var express = require('express');
var app = express();
app.set('port', (process.env.PORT || 5000)); //TAMBAHKAN INI

//request handlers...
app.get('/'...


Maka sampai saat ini, kode file "server.js" adalah seperti ini:

var osu = require('os-utils');
var fs = require('fs');
var express = require('express');
var app = express();

app.set('port', (process.env.PORT || 5000)); //ADA TAMBAHAN INI

app.get('/', function (req, res) {
    //di sini kita akan mengembalikan file index.html
});
app.get('/api/home', function (req, res) {
    //di sini kita akan mengembalikan json untuk konten home
});
app.get('/api/about', function (req, res) {
    //di sini kita akan mengembalikan json untuk konten about
});
app.get('/api/contact', function (req, res) {
    //di sini kita akan mengembalikan json untuk konten contact
});
app.get('/api/os_utils_cpu_usage', function (req, res) {
    //di sini kita akan mengembalikan json untuk nilai cpu usage
});
app.get('/api/os_utils_memory_usage', function (req, res) {
    //di sini kita akan mengembalikan json untuk nilai memory usage
});
app.get('/api/os_utils_others', function (req, res) {
    //di sini kita akan mengembalikan json untuk info lainnya
});
app.get('/api/download_snapshot', function (req, res) {
    //di sini kita akan mengembalikan file snapshot untuk didownload
});

//ADA PERUBAHAN INI
app.listen(app.get('port'), function () {
    console.log('Node app is running on port', app.get('port'));
});

Lalu, pada Power Shell, ketikkan:
$env:PORT = 1234
Kemudian jalankan:
node server.js
Maka aplikasi ini akan memberitahu:
Node app is running on port 1234
Jika pada Power Shell kita langsung menjalankan aplikasi ini tanpa:
$env:PORT = 1234
Maka dia akan berjalan pada port 5000:
Node app is running on port 5000 (*)
Catatan:
(*)Untuk memastikannya, tutup Power Shell, lalu buka kembali pada folder "ls-web-system-monitor" sebagai administrator, lalu jalankan kembali aplikasi ini.

Merancang User Interface

Sekarang saatnya merancang user interface. Untuk melakukannya ada beberapa hal yang perlu dilakukan di server, dan ada pula beberapa hal yang perlu dilakukan di client.

Memberi Akses File Statis Dari Server

Agar user dapat men-download single-page HTML yang akan menampilkan user interface beserta favicon dan client side scriptnya, kita perlu mengonfigurasi Express.js terlebih dahulu.

Dengan demikian, setelah hal tersebut dilakukan, user akan bisa mengakses favicon dalam file HTML dengan cara seperti ini:

<link rel="shortcut icon" type="image/png" href="assets/images/favicon.png"/>

Atau memasukkan javascript dari file HTML dengan cara seperti ini:

<script src="scripts/jquery.min.js"></script>
<script src="scripts/js/bootstrap.min.js"></script>
<script src="scripts/vue.js"></script>
<script src="scripts/vue-resource.min.js"></script>
<script src="scripts/Chart.js"></script>


Atau memasukkan script CSS dari file HTML dengan cara seperti ini:

<link rel="stylesheet" href="scripts/css/bootstrap.min.css">
<link rel="stylesheet" href="scripts/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="assets/css/style.css">

Perhatikan bagian yang digaris bawah. Secara default, Node.js tidak mengizinkan hal seperti ini.

Dengan kata lain, melakukan request misalnya:
http://localhost:5000/scripts/vue.js
Tidak akan dianggap sebagai request yang sah, karena tidak ada request handler untuk hal tersebut (Anda bisa cek lagi request handler yang telah kita buat sebelumnya).

Kecuali jika kita meminta izin terlebih dahulu kepada server di file "server.js".

Untuk meminta izin kepada server agar server bisa menyediakan akses kepada file-file tadi, tulis kode di bawah ini di file "server.js":

//TAMBAHKAN INI
app.use('/scripts', express.static(__dirname + '/node_modules/chart.js/dist/'));
app.use('/scripts', express.static(__dirname + '/node_modules/vue/dist/'));
app.use('/scripts', express.static(__dirname + '/node_modules/vueresource/
dist/'));
app.use('/scripts', express.static(__dirname + '/node_modules/bootstrap/dist/'));
app.use('/scripts', express.static(__dirname + '/node_modules/jquery/dist/'));
app.use('/assets', express.static(__dirname + '/assets/'));
//////////////////////////////////////////

app.set('port', (process.env.PORT || 5000));// DI ATAS INI
//....

Dengan cara ini, maka folder "__dirname/node_modules/chart.js/dist/" akan dipetakan di URL sebagai:
http://localhost:5000/scripts/
Jadi, jika kita merequest:
http://localhost:5000/scripts/vue.js
Maka request itu menjadi sah dan browser akan menerima script "vue.js" sebagai response.

Hal yang sama berlaku dengan "assets". Bedanya kita akan menempatkan file "index.html", "favicon.png", "style.css", dan file snapshot di sana.

Saya sengaja membedakan request untuk package front-end yang telah kita install dengan NPM dengan file statis buatan kita di tempat berbeda agar kita tidak bingung.

Selain itu, perhatikan juga bahwa kita menggunakan variabel global "__dirname".

Variabel ini akan mengembalikan nilai string dari direktori di mana file "server.js" berada.

Jadi kita memberikan informasi pada server tentang lokasi file statis kita dengan menggabungkan "__dirname" dengan direktori di mana file target berada.

Mencoba Mengakses File Statis dari Server

Untuk memastikan bahwa cara ini berhasil, kita akan membuat response bagi request "/" berupa file "index.html".

Pertama-tama, buatlah sebuah folder bernama "assets" di dalam folder "ls-web-system-monitor".

Caranya dengan menggunakan Right Click>New>Folder. Pastikan bahwa folder "assets" berada di dalam folder "ls-web-system-monitor".

Lalu, buatlah folder bernama "css" di dalam folder "assets".

Kemudian, buatlah file baru bernama "index.html" di dalam folder "assets" dan file baru bernama "style.css" di dalam folder "css".

Isi dari file "style.css":

/*Ini Adalah File Style.css*/

Sedangkan isi dari file "index.html":

<!DOCTYPE html>
<html lang="en">
<head>
<title>Node.js System Information - App By Rakifsul</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
INI ADALAH FILE INDEX.HTML
</body>
</html>

Selain itu, kita memerlukan perubahan pada request handler "/" yang telah kita buat:

app.get('/', function (req, res) {
    res.sendfile("assets/index.html");
});

Setelah itu, jalankan aplikasi dengan:
node server.js
Maka dengan melakukan request melalui browser:
http://localhost:5000/
Akan tampil halaman web seperti ini:


Mari kita gali lebih lanjut dengan Right Click>View page source.

Nanti source dari halaman tersebut akan dibuka:


Selanjutnya cobalah klik bagian sini:


Ternyata, file "style.css" dapat diterima, yang artinya request tersebut adalah sah:


Merespon Setiap Request dalam Bentuk JSON dari Server

Setelah kita membuktikan bahwa izin untuk mengakses file statis dari server berjalan semestinya, sekarang saatnya kita membuat response dari setiap request yang telah kita definisikan.

Pada tahap ini, kita akan mengisi tiap request handler yang kita buat dengan kode-kode yang relevan di file "server.js".

Hanya pada request "/" saja kita akan membiarkan kodenya seperti sebelumnya, yakni:

app.get('/', function (req, res) {
    res.sendfile("assets/index.html");
});

Karena bagian itu tidak perlu diubah. Perubahan hanya akan dilakukan pada file "index.html" yang akan dilakukan nanti.

Response untuk Home, About, dan Contact

Untuk request "api/home", "api/about", dan "api/contact" kita hanya akan mengembalikan response berupa teks statis:

app.get('/api/home', function (req, res) {
res.json({title: "Home", message: "Selamat Datang di Node.js Web System Monitor!"});
});

app.get('/api/about', function (req, res) {
res.json({title: "About", message: "Node.js Web System Monitor dibuat oleh Lusfikar Sheba."});
});

app.get('/api/contact', function (req, res) {
res.json({title: "Contact", message: "Kontak Saya di http://www.ciyy.space atau Email ke rakifsul@gmail.com"});
});

Response tersebut akan diberikan ketika user mengakses navigasi "Home", "About", dan "Contact".

Karena tema kita kali ini adalah single page application, maka navigasi tersebut di-respond dengan JSON melalui AJAX.

Response untuk System Performance

Selanjutnya, kita akan membuat response mengenai system performance:

var cpuUsageVal = 0;
app.get('/api/os_utils_cpu_usage', function (req, res) {
    osu.cpuUsage(function(v){
        cpuUsageVal = v;
    });
    res.json({name: "CPU Usage", value: cpuUsageVal});
});

app.get('/api/os_utils_memory_usage', function (req, res) {
    res.json({name: "Memory Usage", value: osu.freememPercentage()});
});

app.get('/api/os_utils_others', function (req, res) {
    var ret = {
        platform: osu.platform(),
        cpuCount: osu.cpuCount(),
        freeMem: osu.freemem(),
        totalMem: osu.totalmem(),
        sysUpTime: osu.sysUptime(),
        processUpTime: osu.processUptime()
    };
    res.json({name: "Other Informations", value: ret});
});

Tidak ada yang membingungkan di kode tersebut, semua kode dapat dipahami dari sintaksnya.

Kecuali bagian:

var cpuUsageVal = 0;
app.get('/api/os_utils_cpu_usage', function (req, res) {
    osu.cpuUsage(function(v){
        cpuUsageVal = v;
    });
    res.json({name: "CPU Usage", value: cpuUsageVal});
});

Perhatikan bahwa di sini kita mendeklarasikan sebuah variabel bernama "cpuUsageVal".

Mengapa kita memerlukan itu?

Jawabannya adalah karena fungsi "osu.cpuUsage" mengembalikan hasilnya melalui callback, sedangkan kita harus merespon request dengan JSON sesegera mungkin.

Oleh karena itulah, dalam kode tersebut, hasil dari "osu.cpuUsage" akan disimpan dalam variabel "cpuUsageVal" sementara JSON akan direspon sesegera mungkin dengan:

res.json({name: "CPU Usage", value: cpuUsageVal});

Seandainya hasil dari "osu.cpuUsage" diberikan melalui return value, maka kita hanya perlu melakukan:

res.json({name: "CPU Usage", value: osu.cpuUsage ()});

Tapi tidak demikian bawaan dari package "os-utils" sehingga kita tidak melakukan itu.

Response untuk Mendapatkan File Snapshot

Respon yang terakhir ini agak rumit, tapi pada dasarnya kita hanya melakukan langkah-langkah ini:

  1. Mendapatkan nilai-nilai system performance di saat user melakukan request
  2. Mengubah nilai-nilai tersebut dalam bentuk JSON
  3. Mengubah nilai JSON menjadi string
  4. Menuliskan nilai tersebut dalam sebuah file bernama "snapshot.json"
  5. Merespon request dengan downloadable content, yakni file "snapshot.json" tersebut

Perhatikanlah kode-kode ini:

app.get('/api/download_snapshot', function (req, res) {
    var snapshot = {
        when: new Date(),
        cpuUsage: cpuUsageVal,
        freememPercentage: osu.freememPercentage(),
        platform: osu.platform(),
        cpuCount: osu.cpuCount(),
        freeMem: osu.freemem(),
        totalMem: osu.totalmem(),
        sysUpTime: osu.sysUptime(),
        processUpTime: osu.processUptime()
    };

    var jstr = JSON.stringify(snapshot,null,'\n');
    fs.writeFile(__dirname + '/assets/snapshot.json', jstr, function(err) {
        if(err)
            return console.error(err);
        console.log('done');
        res.download(__dirname + '/assets/snapshot.json');
    });
});

Kita mendapatkan nilai-nilai system performance dan mengubahnya dalam format JSON di sini:

var snapshot = {
    when: new Date(),
    cpuUsage: cpuUsageVal,
    freememPercentage: osu.freememPercentage(),
    platform: osu.platform(),
    cpuCount: osu.cpuCount(),
    freeMem: osu.freemem(),
    totalMem: osu.totalmem(),
    sysUpTime: osu.sysUptime(),
    processUpTime: osu.processUptime()
};

Kemudian kita mengubah JSON tersebut menjadi string di sini:

var jstr = JSON.stringify(snapshot,null,'\n');

Argument pertama diisi oleh "var snapshot" yang merupakan JSON.

Argument ke-2 digunakan sebagai whitelist dari property JSON yang dimasukkan.

Dalam kasus ini, nilainya null, yang artinya semua property dimasukkan.

Argument ke-3, yakni dengan parameter '\n', adalah dengan apa whitespace dari string JSON tersebut
diisi.

Dalam kasus ini, kita mengisinya dengan newline ('\n').

Kemudian kita menuliskan JSON string tersebut dalam sebuah file lalu memberikan downloadable content-nya kepada user:

fs.writeFile(__dirname + '/assets/snapshot.json', jstr, function(err) {
    if(err)
        return console.error(err);
    console.log('done');
    res.download(__dirname + '/assets/snapshot.json');
});

Perhatikan pada:

fs.writeFile(__dirname + '/assets/snapshot.json'...

Kita menge-save snapshot tersebut pada folder yang telah diizinkan oleh server.

Kita melakukan itu karena user harus dapat mendownload file snapshot tersebut.

Mengimplementasikan Client Side Script pada File Index.html

Setelah semua request handler diimplementasikan, sekarang saatnya membuat client side script "index.html".

Karena kita telah membuat file-nya, berarti sekarang tinggal mengubahnya agar sesuai dengan kebutuhan aplikasi system monitor ini.

Membuat atau Mengcopy Favicon.png Ke Folder Assets

Karena kita akan menggunakan favicon di sisi client, maka Anda akan membutuhkan file PNG apapun dengan ukuran 16x16 pixel.

Letakkan file PNG tersebut dalam folder "assets/images".

Beri nama "favicon.png".

Mengimpor Front-End Javascript dan CSS

Hal pertama yang perlu dilakukan adalah mengimpor front-end javascript dan CSS.

Untuk mengimpor CSS:

<link rel="stylesheet" href="scripts/css/bootstrap.min.css">
<link rel="stylesheet" href="scripts/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="assets/css/style.css">

Letakkan ini di antara tag "<head>" dan "</head>".

Sedangkan untuk mengimpor front-end javascript:

<script src="scripts/jquery.min.js"></script>
<script src="scripts/js/bootstrap.min.js"></script>
<script src="scripts/vue.js"></script>
<script src="scripts/vue-resource.min.js"></script>
<script src="scripts/Chart.js"></script>

Letakkan ini juga di antara tag "<head>" dan "</head>".

Perhatikan bahwa "vue.js", "vue-resource.min.js", dan "Chart.js" diimpor disini. Seperti yang telah saya isyaratkan di bagian awal tulisan ini.

Dengan demikian, keseluruhan file "index.html" adalah seperti ini:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>Node.js Web System Monitor - Aplikasi Buatan Lusfikar Sheba</title>

<meta name="description" content="Node.js Web System Monitor">
<meta name="author" content="Lusfikar Sheba">

<link rel="shortcut icon" type="image/png" href="assets/images/favicon.png"/>
<link rel="stylesheet" href="scripts/css/bootstrap.min.css">
<link rel="stylesheet" href="scripts/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="assets/css/style.css">
<script src="scripts/jquery.min.js"></script>
<script src="scripts/js/bootstrap.min.js"></script>
<script src="scripts/vue.js"></script>
<script src="scripts/vue-resource.min.js"></script>
<script src="scripts/Chart.js"></script>
</head>
<body>
<div id="app" class="container">
<nav class="navbar navbar-default navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Node.js System Monitor</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#" v-on:click="home">Home</a></li>
<li><a href="#" v-on:click="about">About</a></li>
<li><a href="#" v-on:click="contact">Contact</a></li>
</ul>
</div>
</div>
</nav>

<div class="jumbotron">
<h1>{{ jumboMessage.title }}</h1>
<p>{{ jumboMessage.message }}</p>
</div>

<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<b>Current {{ cpuUsage.name }}</b>
</div>
<div class="panel-body">
<canvas id="cpuUsageChartPie" width="100" height="50"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<b>{{ cpuUsage.name }} Over Time</b>
</div>
<div class="panel-body">
<canvas id="cpuUsageChart" width="100" height="50"></canvas>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<b>Current {{ memoryUsage.name }}</b>
</div>
<div class="panel-body">
<canvas id="memoryUsageChartPie" width="100" height="50"></canvas>
</div>
</div>
</div>

<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<b>{{ memoryUsage.name }} Over Time</b>
</div>
<div class="panel-body">
<canvas id="memoryUsageChart" width="100" height="50"></canvas>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<b>{{ otherInformations.name }}</b>
</div>
<table class="table table-striped">
<tbody>
<tr>
<td><b>Platform</b></td>
<td>{{ otherInformations.value.platform }}</td>
</tr>
<tr>
<td><b>CPU Count</b></td>
<td>{{ otherInformations.value.cpuCount }}</td>
</tr>
<tr>
<td><b>Free Memory</b></td>
<td>{{ otherInformations.value.freeMem }} MB</td>
</tr>
<tr>
<td><b>Total Memory</b></td>
<td>{{ otherInformations.value.totalMem }} MB</td>
</tr>
<tr>
<td><b>System Uptime</b></td>
<td>{{ otherInformations.value.sysUpTime }} second(s)</td>
</tr>
<tr>
<td><b>Process Uptime</b></td>
<td>{{ otherInformations.value.processUpTime }} second(s)</td>
</tr>
</tbody>
</table>
</div>
</div>

<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<b>Tools</b>
</div>
<div class="panel-body text-center">
<a class="btn btn-primary btn-lg" href="/api/download_snapshot">Get Snapshot!</a>
</div>
</div>
</div>
</div>

<nav class="navbar navbar-default navbar-inverse">
<div class="container-fluid">
<p class="navbar-text pull-right">Copyright &#169; Lusfikar Sheba 2017</p>
</div>
</nav>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
jumboMessage: {
title: "Home",
message: "Selamat Datang di Node.js Web System Monitor!"
},
cpuUsage: {
name: "",
value: ""
},
memoryUsage: {
name: "",
value: ""
},
otherInformations: {
name: "",
value: ""
}

},
created: function() {
this.os_utils_cpu_usage();
this.timer = setInterval(this.os_utils_cpu_usage, 1000);

this.os_utils_memory_usage();
this.timer = setInterval(this.os_utils_memory_usage, 1000);

this.os_utils_others();
this.timer = setInterval(this.os_utils_others, 1000);
},
methods: {
home: function () {
this.$http.get('/api/home').then(response => {
this.jumboMessage = response.body;
}, response => {

});
},
about: function () {
this.$http.get('/api/about').then(response => {
this.jumboMessage = response.body;
}, response => {

});
},
contact: function () {
this.$http.get('/api/contact').then(response => {
this.jumboMessage = response.body;
}, response => {

});
},
os_utils_cpu_usage: function () {
this.$http.get('/api/os_utils_cpu_usage').then(response => {
this.cpuUsage = response.body;
}, response => {

});
},
os_utils_memory_usage: function () {
this.$http.get('/api/os_utils_memory_usage').then(response => {
this.memoryUsage = response.body;
}, response => {

});
},
os_utils_others: function () {
this.$http.get('/api/os_utils_others').then(response => {
this.otherInformations = response.body;
}, response => {

});
}
}
});
</script>

<script>
var configCPUUsagePie = {
type: 'pie',
data: {
labels: [
"CPU Used",
"CPU Free"
],
datasets: [{
data: [100, 50],
backgroundColor: [
"#FF6384",
"#36A2EB"
],
hoverBackgroundColor: [
"#FF6384",
"#36A2EB"
]
}]
}
};

var cpuUsageChartPie = new Chart(document.getElementById("cpuUsageChartPie").getContext("2d"), configCPUUsagePie);

setInterval(function(){
configCPUUsagePie.data.datasets[0].data[0] = app.cpuUsage.value * 100;
configCPUUsagePie.data.datasets[0].data[1] = (1 - app.cpuUsage.value) * 100;
cpuUsageChartPie.update();
}, 1000);
</script>

<script>
var configCPUUsage = {
type: 'line',
data: {
labels: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
datasets: [{
label: "CPU Usage Over Time",
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
fill: false
}]
}
};

var cpuUsageChart = new Chart(document.getElementById("cpuUsageChart").getContext("2d"), configCPUUsage);

setInterval(function(){
configCPUUsage.data.datasets[0].data.shift();
configCPUUsage.data.datasets[0].data.push(app.cpuUsage.value * 100);
cpuUsageChart.update();
}, 1000);
</script>

<script>
var configMemoryUsagePie = {
type: 'pie',
data: {
labels: [
"Memory Used",
"Memory Free"
],
datasets: [{
data: [100, 50],
backgroundColor: [
"#FF6384",
"#36A2EB"
],
hoverBackgroundColor: [
"#FF6384",
"#36A2EB"
]
}]
}
};

var memoryUsageChartPie = new Chart(document.getElementById("memoryUsageChartPie").getContext("2d"), configMemoryUsagePie);

setInterval(function(){
configMemoryUsagePie.data.datasets[0].data[0] = app.memoryUsage.value * 100;
configMemoryUsagePie.data.datasets[0].data[1] = (1 - app.memoryUsage.value) * 100;
memoryUsageChartPie.update();
}, 1000);
</script>

<script>
var configMemoryUsage = {
type: 'line',
data: {
labels: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
datasets: [{
label: "Memory Usage Over Time",
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
fill: false
}]
}
};

var memoryUsageChart = new Chart(document.getElementById("memoryUsageChart").getContext("2d"), configMemoryUsage);

setInterval(function(){
configMemoryUsage.data.datasets[0].data.shift();
configMemoryUsage.data.datasets[0].data.push(app.memoryUsage.value * 100);
memoryUsageChart.update();
}, 1000);
</script>
</body>
</html>

Penjelasan Kode dari File Index.html

Sekilas tidak ada yang aneh dengan file "index.html" karena hanya berupa file HTML biasa.

Tapi perhatikan baris-baris kode yang terdapat pada file "index.html" ini:

<b>Current {{ cpuUsage.name }}</b>

<b>{{ cpuUsage.name }} Over Time</b>

<b>Current {{ memoryUsage.name }}</b>

<b>{{ memoryUsage.name }} Over Time</b>

<b>{{ otherInformations.name }}</b>

<tr>
<td><b>Platform</b></td>
<td>{{ otherInformations.value.platform }}</td>
</tr>

<tr>
<td><b>CPU Count</b></td>
<td>{{ otherInformations.value.cpuCount }}</td>
</tr>

<tr>
<td><b>Free Memory</b></td>
<td>{{ otherInformations.value.freeMem }} MB</td>
</tr>

<tr>
<td><b>Total Memory</b></td>
<td>{{ otherInformations.value.totalMem }} MB</td>
</tr>

<tr>
<td><b>System Uptime</b></td>
<td>{{ otherInformations.value.sysUpTime }} second(s)</td>
</tr>

<tr>
<td><b>Process Uptime</b></td>
<td>{{ otherInformations.value.processUpTime }} second(s)</td>
</tr>

Di sana terdapat kode yang diapit oleh "{{" dan "}}". Ini bukan kode HTML bawaan.

Kode ini akan di-replace oleh Vue. js menjadi suatu nilai tertentu dari variable yang diletakkan di antara "{{" dan "}}".

Jadi, "{{ otherInformations.value.freeMem }}" misalnya, akan diisi nilai memory yang tidak terpakai yang didefinisikan di member variable "otherInformations.value.freeMem".

Lalu di mana "otherInformations" berada?

Jawabnya, variabel tersebut ada di dalam object Vue dari "vue.js" yang didefinisikan pada kode berikut ini:

var app = new Vue({
    el: '#app',
    data: {
        jumboMessage: {
            title: "Home",
            message: "Selamat Datang di Node.js Web System Monitor!"
        },
        cpuUsage: {
            name: "",
            value: ""
        },
        memoryUsage: {
            name: "",
            value: ""
        },
        otherInformations: { //<<==DI SINI
            name: "",
            value: ""
        }
//....

Di samping itu, nilai "freeMem" dari "otherInformations.value.freeMem" didapatkan dari server melalui AJAX dalam fungsi ini:

os_utils_others: function () {
    this.$http.get('/api/os_utils_others').then(response => {
        this.otherInformations = response.body;
    }, response => {
    });
}

Perhatikan bahwa kita melakukan GET pada request handler "/api/os_utils_others" yang telah kita implementasikan sebelumnya.

Request tersebut direspon dengan JSON, kemudian nilainya masuk ke "otherInformations".

Akan tetapi itu semua hanya terjadi bila variable tersebut (yang diapit "{{" dan "}}") berada dalam tag HTML dengan ID "app".

Nilainya harus sama dengan nilai "el" dari object Vue:

var app = new Vue({
el: '#app', //<<==YANG INI

Maka Anda akan dapat membuktikan bahwa semua variable yang diapit dengan "{{" dan "}}" berada dalam tag HTML dengan ID "app" (merupakan child-nya), dalam hal ini berupa "div" tag:

<div id="app" class="container"> <!-- YANG INI -->
<nav class="navbar navbar-default navbar-inverse">
<div class="container-fluid">

Tag tersebut tepat berada dibawah tag "<body>" jika Anda ingin melihatnya.

Kita melakukan hal yang sama pada variable yang lain:

data: {
    jumboMessage: {
        title: "Home",
        message: "Selamat Datang di Node.js Web System Monitor!"
    },
    cpuUsage: {
        name: "",
        value: ""
    },
    memoryUsage: {
        name: "",
        value: ""
    },
    otherInformations: {
        name: "",
        value: ""
    }
},

Semua nilai ini dapat diterima dalam tag HTML dengan mengapitnya dengan "{{" dan "}}".

Hal ini disebut sebagai data binding.

Nilai awal dari data binding tersebut didapat ketika object Vue baru diciptakan, kemudian nilai update-nya diambil tiap satu detik dengan menggunakan "setInterval":

created: function() {
    this.os_utils_cpu_usage();
    this.timer = setInterval(this.os_utils_cpu_usage, 1000);
    this.os_utils_memory_usage();
    this.timer = setInterval(this.os_utils_memory_usage, 1000);
    this.os_utils_others(); //<<===YANG INI
    this.timer = setInterval(this.os_utils_others, 1000); //<<==KEMUDIAN YANG INI
},

Khusus untuk variable "cpuUsage" dan "memoryUsage", nilai data yang di-binding akan dimasukkan sebagai input bagi grafik pie dan line.

Yang pada akhirnya grafik tersebut juga akan di-update setiap satu detik dengan "setInterval".

Jadi ada 2 penggunaan "setInterval" di sini, yakni untuk request dan untuk update grafik.

Sebagai contoh, untuk grafik pie "cpuUsage":

<script>
var configCPUUsagePie = {
type: 'pie',
data: {
labels: [
"CPU Used",
"CPU Free"
],
datasets: [{
data: [100, 50],
backgroundColor: [
"#FF6384",
"#36A2EB"
],
hoverBackgroundColor: [
"#FF6384",
"#36A2EB"
]
}]
}
};

var cpuUsageChartPie = new Chart(document.getElementById("cpuUsageChartPie").getContext("2d"), configCPUUsagePie);

setInterval(function(){ //<<==SET INTERVAL LAGI
configCPUUsagePie.data.datasets[0].data[0] = app.cpuUsage.value * 100; //<<==YANG INI
configCPUUsagePie.data.datasets[0].data[1] = (1 - app.cpuUsage.value) * 100; //<<==YANG INI
cpuUsageChartPie.update();
}, 1000);
</script>

Dan untuk grafik line cpuUsage:

<script>
var configCPUUsage = {
type: 'line',
data: {
labels: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
datasets: [{
label: "CPU Usage Over Time",
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
fill: false
}]
}
};

var cpuUsageChart = new Chart(document.getElementById("cpuUsageChart").getContext("2d"), configCPUUsage);

setInterval(function(){ //<<==SET INTERVAL LAGI
configCPUUsage.data.datasets[0].data.shift();
configCPUUsage.data.datasets[0].data.push(app.cpuUsage.value * 100);//<<==YANG
INI
cpuUsageChart.update();
}, 1000);
</script>

Hal yang serupa juga berlaku bagi "memoryUsage".

Bagian "Home", "About" dan "Contact" juga diperlakukan dengan cara yang serupa, hanya saja yang diambil nilainya hanya teks-nya saja dan nilainya tidak dimasukkan ke dalam grafik.

Agar user bisa berpindah dari "Home", ke "About", dan ke "Contact" maka digunakan event "v-on:click":

<ul class="nav navbar-nav navbar-right">
<li><a href="#" v-on:click="home">Home</a></li>
<li><a href="#" v-on:click="about">About</a></li>
<li><a href="#" v-on:click="contact">Contact</a></li>

Jika link-link tersebut di-click, maka fungsi dari object Vue ini akan dipanggil:

methods: {
home: function () {
this.$http.get('/api/home').then(response => {
this.jumboMessage = response.body;
}, response => {

});
},
about: function () {
this.$http.get('/api/about').then(response => {
this.jumboMessage = response.body;
}, response => {

});
},
contact: function () {
this.$http.get('/api/contact').then(response => {
this.jumboMessage = response.body;
}, response => {

});
},

Saat ini, kita telah selesai membuat aplikasi web system monitor. Hasil akhirnya dapat kita lihat di browser dengan mengakses:
http://localhost:5000/

Tampilannya kurang lebih seperti ini:


Apabila tombol Get Snapshot diklik, maka Anda akan mendapatkan file JSON berisi snapshot yang bisa di-download.

Penutup

Sekarang, kita telah mempelajari dasar-dasar Node.js yang walaupun singkat, tapi tetap menyeluruh.

Di samping itu, kita juga telah mencoba mempraktikkan dasar-dasar Node.js dalam pembuatan aplikasi sederhana.

Saya menyadari bahwa PASTI banyak kekurangan dalam tulisan saya ini.

Oleh karena itu, saya meminta maaf jika ada yang salah dalam buku ini.

Jika ada hal yang salah dalam buku ini, jangan diikuti, tapi perbaiki.

Cara Membuat Aplikasi Clone Pastebin dengan Node.js


Salah satu hal yang paling sering dilakukan oleh pengguna komputer adalah copy dan paste.

Bagaimana tidak, hampir setiap hari ada saja sesuatu yang di-copy-paste, entah itu teks, file, gambar, video, dan lain-lain.

Tapi copy dan paste tidak lengkap tanpa adanya kemampuan berbagi atau sharing.

Untuk memberi fitur tersebut, Pastebin ( https://pastebin.com/ ) menyediakan layanan untuk menyimpan dan berbagi paste.

User yang terdaftar maupun tidak bisa mengakses fitur paste yang disediakan oleh website tersebut.

Setelah user mem-paste apa yang telah dicopy-nya, user diberi URL paste miliknya yang kemudian dapat disebar luaskan.

Walaupun ide tersebut sederhana, kita bisa melihat betapa besar fungsi dari Pastebin.

Oh iya, sebelum kita lanjut, Anda boleh download source code praktikumnya di sini:


Sayangnya, website tersebut tidak mengizinkan semua konten paste.

Ada yang tidak boleh dipaste di website tersebut, seperti:
  • Daftar email
  • Detail login
  • Source code curian
  • Data hasil hack
  • Data/informasi yang dilindungi hak cipta
  • Daftar password
  • Data perbankan
  • Data personal
  • Data pornografi
  • Link spam

Seperti yang dijelaskan pada https://pastebin.com/faq#5 :


Hehehe... kalau begitu yang boleh apa dong???

Wajarlah kalau pengguna Pastebin justru cenderung melanggar kebijakan tersebut seperti yang dilansir dari bbc (http://www.bbc.com/news/technology-17524822):

Ngomong-ngomong, walaupun ide Pastebin sederhana, unique visitor Pastebin  pada tahun 2012 mencapai 17 juta lho...

Traffic yang sangat menggiurkan bukan?

Coba, bayangkan jika kita bisa membuat website seperti itu, lalu ambil potongan kuenya 1 persen saja...

Berarti sudah dapat 170 ribu unique visitor.

Apalagi kalau kita sebagai Admin mengizinkan semua konten dan tidak memerlukan login.

Oleh karena itulah, saya kali ini akan membahas cara membuat clone dari situs Pastebin dengan Node.js agar pembaca dapat membuat Pastebin-nya sendiri dengan kebijakan konten sesuka pembaca.

Aplikasi yang akan kita buat ini bernama "Node.js Pasta".

Memang tidak 100 persen sama dengan Pastebin, tapi setidaknya fungsi utamanya sama.

Harapan saya, setidaknya pembaca dapat 1 persen kue unique visitornya.

Yang Anda Butuhkan sebelum Kita Belajar

Sebelum kita mulai, pastikan Anda telah memiliki akun di Heroku.

Heroku digunakan untuk meng-host aplikasi Node.js kita secara gratis, jadi daftarkan diri Anda sebagai free account.

Aplikasi ini memang bisa berjalan di localhost, tapi untuk mengetest tampilan sharing di social media, perlu hosting Node.js di Heroku.

Apabila Anda sudah memiliki hostingan lain atau mungkin VPS, silakan digunakan, tapi caranya tidak akan dibahas di sini.

Beberapa software yang harus Anda install terlebih dahulu adalah:

  • Node.js ( https://nodejs.org/en/download/ )
  • Visual Studio Code ( https://code.visualstudio.com/Download )
  • Git For Windows( https://git-for-windows.github.io/ )
  • Heroku Client ( https://devcenter.heroku.com/articles/heroku-cli )


Setelah semua software tersebut terinstall, lakukan pengecekan terhadap software-software tersebut.

Pertama, jalankan perintah ini di command line:
node -v

Hasil yang diharapkan adalah:
v6.11.0

Kemudian jalankan perintah ini di command line:
git --version

Hasil yang diharapkan adalah:
git version 2.10.2.windows.1

Kemudian jalankan perintah ini di command line:
heroku -v

Hasil yang diharapkan adalah:
heroku-cli/6.14.13-6ab5022 (windows-x64) node-v8.4.0

Hasil-hasil tersebut tidak harus sama persis karena tergantung versi berapa yang terinstall.

Membedah Aplikasi Pastebin

Halaman Utama (New Paste)

Pastebin memiliki dua fungsi utama: menyimpan teks yang dipaste dan memberi link untuk membagikannya di tempat lain.


Yang dilakukan oleh pengguna pastebin untuk menyimpan paste adalah dengan membuka halaman berandanya, kemudian mengisi textarea "New Paste".

Selanjutnya pada bagian "Optional Paste Settings", pengguna dapat memilih syntax highlighting dari berbagai macam alternatif yang nanti akan ditampilkan pada halaman paste.

Pengguna juga dapat memilih "Paste Expiration" untuk menentukan usia paste yang dibuat.

Pilihan "Paste Expiration" yang disediakan adalah:

  • Never
  • 10 Minutes
  • 1 Hour
  • 1 Day
  • 1 Week
  • 2 Weeks
  • 1 Month
  • 6 Month
  • 1 Year


Selain itu, pada bagian "Paste Exposure", pengguna dapat memilih:

  • Public
  • Unlisted


Public berarti paste akan tampil dalam listing "Public Pastes" di bagian kanan dan di halaman "Trends".

Unlisted berarti paste tidak akan tampil di bagian manapun dan hanya bisa dilihat oleh pengguna yang mengetahui link dari paste tersebut.

Selain itu, jika pengguna adalah pro member, mereka dapat menempatkan paste pada suatu folder dengan mengisi input "Folder".

Dan option selanjutnya adalah "Title". Di sini pengguna dapat memberi nama paste yang dia buat.

Untuk membedakan member dan mengidentifikasi pro member, Pastebin juga menyediakan form login di sebelah kanan dari "Optional Paste Settings".

Halaman Trends

Pada halaman "Trends", Pastebin menampilkan trending pastes yang ditampilkan dalam bentuk tabel.


Kolom pertama adalah "Name/Title" atau judul dari paste.

Kolom kedua adalah "Added" atau kapan paste tersebut dibuat.

Kolom ketiga adalah "Hits" yang sepertinya adalah jumlah klik dari paste tersebut.

Kolom keempat adalah "User" yakni siapa yang membuat paste tersebut.

Apabila pembuat paste adalah guest atau tidak login terlebih dahulu, maka nama pembuat paste tersebut tidak ditampilkan pada kolom ini.

Halaman Search

Halaman "Search" menampilkan paste yang dicari dengan keyword tertentu.

Tidak ada yang istimewa dari halaman ini, karena halaman ini melakukan search dengan menggunakan google custom search.

Memang cara ini sangat mudah dilakukan dan hemat resource, tapi jika halaman paste tertentu belum diindex google atau jika website ini mengalami deindex, maka hasil pencarian untuk paste tersebut tidak akan muncul.


API, Tools, FAQ dan Sisanya

Pastebin juga menyediakan API untuk programmer.

Jadi programmer bisa membuat paste atau mendapat list dari paste atau untuk keperluan lain.

Sedangkan, Tools merupakan daftar aplikasi yang menggunakan API dari Pastebin. Jika kita membuatnya, kita bisa kontak Pastebin agar aplikasi kita dilisting di halaman tersebut.

FAQ cukup jelas.

Sisanya, merupakan fitur untuk pengguna yang sudah login, jadi tidak perlu dibahas.

Merancang Node.js Pasta

Setelah kita membedah Pastebin yang asli, kita bisa mengetahui beberapa fitur utama pada Pastebin.

Beberapa fitur seperti login tidak diperlukan karena kita akan membuat versi lain yang tidak membutuhkan login.

API tidak akan disertakan di sini karena tidak terlalu berguna. Mungkin kita akan membuatnya jika pengguna clone ini sudah semakin banyak.

Jadi, fitur-fitur yang akan disediakan oleh Node.js Pasta adalah:

  • Copy-paste teks.
  • Syntax highlighting. Dalam kasus ini hanya 3 jenis: Text, HTML, CSS, dan Javascript.
  • Paste Expiration. Dalam kasus ini, kita mengubah durasinya menjadi: 1 Day, 12 Hours, 3 Hours, 30 Minutes, dan 2 Minutes.
  • Paste Exposure. Dalam kasus ini pilihannya: Public dan Unlisted.
  • Paste Title.
  • Recent Pastes. Yakni daftar paste yang baru dibuat.
  • Paste Manager tanpa login.
  • Edit dan Delete Paste dari Paste Manager.
  • Paste Manager link dan Paste link generator.
  • Trending Pastes.
  • Halaman Paste.
  • Halaman 404 Not Found. Berguna jika paste yang dibuat sudah expired atau jika pengguna salah memasukkan URL.
  • Paste sharing ke social media: Facebook, Twitter, dan Google Plus.


Rancangan User Interface

Node.js Pasta memiliki desain user interface yang sederhana.

Terdiri atas empat halaman, yakni "Home/New Paste", "Paste Manager", "Paste", dan "Trends".

Tidak ada CSS yang rumit karena kita akan menggunakan bootstrap.

Home/New Paste:


Paste Manager:


Paste:


Trends:

Package NPM yang Dibutuhkan

Untuk mengerjakan aplikasi Node.js Pasta, diperlukan beberapa modul NPM ini.

Body Parser

install: npm install body-parser --save

Module ini berfungsi untuk memparsing request body.

Expressjs

install: npm install express --save

Module ini berfungsi sebagai framework untuk routing dan sebagai wrapper untuk menjalankan fungsi Node.js sebagai web server.

EJS

install: npm install ejs --save

Module ini berfungsi sebagai template engine untuk Expressjs. Dengan module ini kita bisa menyajikan html sebagai template dan memanipulasi nilai tertentu di dalamnya secara dinamis.

Randomstring

install: npm install randomstring --save

Module ini berfungsi untuk men-generate string acak dengan parameter-parameter tertentu.

SQLite3

install: npm install sqlite3 --save

Module ini digunakan sebagai DBMS. Keuntungan menggunakan module ini adalah, kita tidak memerlukan server khusus untuk database karena hanya berupa file. Apabila Anda ingin menggantinya menjadi MySQL silakan dicoba sendiri karena tidak dibahas di sini.

Package Non-NPM yang Dibutuhkan

Selain package NPM, dibutuhkan juga package non NPM.

Sebenarnya package-package ini juga tersedia di npmjs.com, tetapi menurut saya lebih mudah menggunakannya tanpa NPM.

JQuery

Package ini adalah library javascript front-end yang berguna untuk memanipulasi HTML dan event-event-nya. Selain itu, Bootstrap juga memerlukan library ini.

Untuk mendapatkannya, download versi terbarunya di:

https://jquery.com/download/

Bootstrap

Package ini adalah library javascript dan CSS yang menyediakan berbagai elemen user interface yang cantik. Semua elemen tersebut didesain agar bersifat responsive.

Untuk mendapatkannya, download versi 3-nya di:

https://getbootstrap.com/docs/3.3/getting-started/#download

Ace

Package ini adalah library javascript untuk membuat web based code editor.

Sebenarnya kita tidak membutuhkan fungsinya sebagai code editor, tetapi hanya mengambil fungsi syntax highlightingnya saja.

Bahasa pemrograman yang didukung syntax highlightingnya sangat banyak, namun kita hanya akan mengambil empat saja: Text, HTML, CSS, dan Javascript.

Untuk mendapatkannya, download versi terbarunya di:

https://github.com/ajaxorg/ace-builds

Font Awesome

Font Awesome adalah library yang berisi icon-icon yang sangat banyak. Icon-icon ini adalah font yang berupa vector dan scalable, sehingga tidak akan pecah gambarnya walaupun dalam ukuran berapapun.

Untuk mendapatkannya, download versi terbarunya di:

http://fontawesome.io/

Memprogram Node.js Pasta

Sekarang kita telah mengetahui rancangan aplikasi Node.js Pasta dan telah mendapatkan package-package yang dibutuhkannya.

Saatnya mulai memprogram.

Tapi sebelumnya, kita buat dahulu struktur direktori project ini agar lebih rapi.

Dan ada satu hal lagi yang perlu saya sampaikan.

Jika Anda ragu apakah kode yang Anda tulis dan penempatannya sudah benar, silakan cek dan samakan dengan kode yang ada pada source code buku ini.

Struktur Direktori Project

Pertama-tama, buatlah folder bernama "ls-node-js-pasta".

Selanjutnya buatlah folder-folder lain di dalamnya sedemikian rupa sehingga strukturnya seperti gambar di bawah ini.


Anda boleh melewatkan folder "node_modules" karena ini akan dibuat secara otomatis ketika Anda menginstall module melalui NPM.

Jika Anda tidak yakin dengan struktur direktori yang Anda buat, silakan cek source code buku ini (di dalam folder "ls-node-js-pasta").

Menempatkan Package-Package Non NPM di Direktori Project

Selanjutnya tempatkanlah package-package non NPM yang telah Anda download ke dalam direktori project sesuai dengan jenis filenya.

File Javascript (extension: .js) dimasukkan ke folder "js".

File Gambar dimasukkan ke folder "img".

File Font (extension: .otf, .eot, .svg, .ttf, .woff, .woff2) dimasukkan ke folder "fonts"

File CSS (extension: .css) dimasukkan ke folder "css".

Khusus untuk gambar, silakan copy dari source code buku ini, karena file gambar tersebut tidak ada di internet.

Anda boleh menggunakan gambar Anda sendiri, asalkan resolusinya sama dengan milik saya.

Selanjutnya, kembali ke folder "ls-node-js-pasta/views".

Di sini, buatlah file kosong bernama:

  • functions.ejs
  • 404.ejs
  • home.ejs
  • manage.ejs
  • paste.ejs
  • trends.ejs


Selanjutnya buka folder "ls-node-js-pasta" dan buat file kosong bernama "index.js".

Jika Anda masih tidak yakin apakah penempatannya benar, silakan dicek di source code buku ini.

Menginstall Package NPM di Direktori Project

Sekarang kita sudah menempatkan package non NPM di folder yang benar.

Saatnya menginstall package NPM-nya.

Masih di folder "ls-node-js-pasta", jalankan perintah ini:
npm init
Selanjutnya, isi saja sesuai yang ditawarkan hingga jawaban terakhir (yes/no).

Kemudian, ketikkan perintah ini:
npm install body-parser --save
npm install express --save
npm install ejs --save
npm install randomstring --save
npm install sqlite3 --save

Dari langkah tersebut, akan dibuat file baru secara otomatis bernama package.json.

Pastikan isi filenya sama dengan ini:

{
  "name": "ls-node-js-pasta",
  "version": "1.0.0",
  "description": "Node.js Pasta adalah Node.js Pastebin Clone",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Lusfikar Sheba",
  "license": "UNLICENSED",
  "dependencies": {
    "body-parser": "^1.17.2",
    "ejs": "^2.5.7",
    "express": "^4.15.4",
    "randomstring": "^1.1.5",
    "sqlite3": "^3.1.9"
  }
}

Perhatikan pada bagian yang dicetak tebal.

Pastikan ada bagian tersebut.

Nama author bisa beda, tergantung apa yang kita isi saat melakukan npm init. Begitu pula license.

Mengimpor Package NPM

Setelah package NPM terinstall, kita bisa mengimpornya di dalam script.

Sekarang buka file "index.js' pada folder "ls-node-js-pasta".

Saat ini file tersebut masih kosong.

Isi file tersebut dengan kode ini:

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var randomstring = require("randomstring");
var sqlite3 = require('sqlite3').verbose();
var fs = require('fs');

Di sinilah kita mengimpor package-package NPM agar nanti bisa digunakan.

Perhatikan pada bagian:

var app = express();

Mengapa di bagian tersebut ada fungsi express()?

Padahal yang lain tidak ada hal semacam itu.

Hal itu disebabkan pada saat mengimport express di bagian:

var express = require('express');

Yang diimport merupakan fungsi, karena itu harus dipanggil dengan express().

Mempersiapkan Framework Expressjs

Setelah mengimpor package-package NPM ke script "index.js", kita akan mempersiapkan framework Expressjs.

Disini kita akan membuat kerangka routes dan mengonfigurasi Expressjs agar bisa berjalan semestinya.

Buka file "index.js" kemudian di bawah kode impor NPM ini:

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var randomstring = require("randomstring");
var sqlite3 = require('sqlite3').verbose();
var fs = require('fs');

, tambahkan kode ini:

app.set('view engine', 'ejs'); //YANG INI - 1
app.set('view options', { //YANG INI - 2
    rmWhitespace: true
});

app.use('/views', express.static(__dirname + '/views/')); //YANG INI - 3
app.use(bodyParser.urlencoded({ extended: true })); //YANG INI - 4

app.get('/', function (req, res) { //YANG INI - 5

});

app.get('/trends', function (req, res) {

});

app.post('/new-paste', function(req, res) {

});

app.post('/manage/save-paste/:manageID', function(req, res) { //YANG INI - 6

});

app.post('/manage/delete-paste/:manageID', function(req, res) {

});

app.get('/manage/:manageID', function (req, res) {

})

app.get('/paste/:pasteID', function (req, res) {

})

app.get('*', function(req, res){ //YANG INI - 7

});

app.set('port', (process.env.PORT || 5000)); //YANG INI - 8

app.listen(app.get('port'), function () { //YANG INI - 9
    console.log('Node app is running on port', app.get('port'));
});

Perhatikan kode bagian "//YANG INI - 1".

Di sana, kita menge-set EJS sebagai view engine.

Artinya setelah kita mengimpor EJS, kita harus memberitahu Expressjs bahwa kita mengguakan template engine tersebut.

Pada kode bagian "//YANG INI - 2", kita mengonfigurasi EJS agar menghapus whitespace.

Bisa dilihat di dokumentasinya pada ( https://github.com/mde/ejs#options )

Jika hal itu tidak dilakukan, saat kita membuat paste akan ada tambahan whitespace yang tidak diinginkan.

Pada kode bagian "//YANG INI - 3", kita memberi akses pada Expressjs agar dapat merespon file statis.

Yang dimaksud file statis di sini adalah file CSS, HTML, Gambar, Javascript, dan file apapun yang terletak pada direktori yang didaftarkan pada fungsi:

app.use('/views', express.static(__dirname + '/views/'));

Jadi, apabila kita tidak melakukan ini, request:

http://nama_host/views/js/jquery.min.js

Tidak sah dan file "jquery.min.js" tersebut tidak bisa diakses.

Agar bisa diakses, kita harus melakukan "//YANG INI - 3".

Pada kode bagian "//YANG INI - 4":

app.use(bodyParser.urlencoded({ extended: true }));

Kita memberitahu Expressjs untuk menggunakan module Body Parser untuk mem-parse application/x-www-form-urlencoded.

Jadi apabila kita memiliki form pada sebuah file HTML, dan pada form tersebut ada input dengan atribut name="inputpertama", seperti ini:

<input type="text" name="inputpertama">

Maka di script servernya nilai input tadi bisa diakses dengan:

app.post('/getinput', function(req, res) {
 console.log(req.body.inputpertama );//bisa begini...
});

Tidak hanya untuk merequest "/getinput" saja, bisa juga "/" atau "/apapun" tergantung apa yang ditulis pada argument app.post.

Anda bisa lihat pada source code buku ini bahwa terdapat beberapa penggunaan Body Parser semacam ini.


Sekarang perhatikan pada bagian "//YANG INI - 5":

app.get('/', function (req, res) {

});

Dengan kode ini, kita akan merespon request "/" atau yang biasa disebut Home.

Request semacam ini bisa juga disertai dengan variabel seperti pada bagian "//YANG INI - 6".

app.post('/manage/save-paste/:manageID', function(req, res) {

});

Perhatikan bahwa variabel diletakkan setelah "/manage/save-paste", yakni ":manageID".

Tanda titik dua pada routing di Expressjs menandakan bahwa itu adalah variabel.

Dengan cara tersebut, kita bisa mengakses:

http://nama_host/manage/save-paste/123

http://nama_host/manage/save-paste/345

http://nama_host/manage/save-paste/atauapapunjuga

Sekarang perhatikan pada kode bagian "//YANG INI - 7":

app.get('*', function(req, res){

});

Di situ ada karakter asterisk ('*').

Itu menandakan bahwa request apapun yang tidak terdaftar akan diarahkan ke situ.

Routes semacam ini berguna untuk mengarahkan halaman yang tidak ditemukan (404) ke halaman 404 yang telah disiapkan.

Pada kode bagian "//YANG INI - 8":

app.set('port', (process.env.PORT || 5000));

Kita memberitahu Expressjs bahwa server akan berjalan pada port 5000 atau pada port yang didefinisikan oleh environment variable (ditandai dengan process.env.PORT).

Hal ini berguna saat kita men-deploy aplikasi ini ke Heroku karena Heroku sendiri yang menentukan port berapa untuk aplikasi kita walaupun setelah di-deploy semua aplikasi Heroku berjalan pada port 443 secara default.

Hal tersebut disebabkan (sepertinya karena) Heroku menggunakan semacam proxy untuk mengarahkan subdomain kita di port 443 ke port yang asli di network internal mereka.

URL dengan port yang asli tadi tidak bisa diakses secara langsung dari internet.

Dan akhirnya, pada kode bagian "//YANG INI - 9":

app.listen(app.get('port'), function () {
    console.log('Node app is running on port', app.get('port'));
});

Kita memerintahkan Express.js untuk menjalankan server pada port tadi.

Membuat Database dengan SQLite3

Karena aplikasi ini bisa menyimpan paste, maka diperlukan sebuah DBMS.

Mengingat akun Heroku yang free tidak menyediakan fitur MySQL, maka saya memilih SQLite3 sebagai DBMS.

SQLite3 dapat digunakan di Heroku karena hanya berupa file dan tidak butuh server yang terpisah.

Memang ada kekurangannya, tapi untuk skala kecil SQLite3 masih dapat digunakan.

Sekarang buka kembali file "index.js".

Di bawah script untuk import module:

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var randomstring = require("randomstring");
var sqlite3 = require('sqlite3').verbose();
var fs = require('fs');

Tulis kode ini:

var db;

db = new sqlite3.Database('./db_pasta.db');//YANG INI - 1
db.run(//YANG INI - 2
`CREATE TABLE IF NOT EXISTS pastes (
    id INTEGER PRIMARY KEY,
    newpaste TEXT,
    syntaxhi TEXT,
    pasteex TEXT,
    pasteexpos TEXT,
    title TEXT,
    pasteperm TEXT,
    pastemanagerperm TEXT,
    hits INTEGER
);`, function(){
    db.each("SELECT * from pastes", function(err, row) {//YANG INI - 3
        console.log("scheduling the timeout for: " + row.title);
        setTimeout(function(){//YANG INI - 4
            console.log("deleting the row automatically...");
            db.run("DELETE FROM pastes WHERE id = '" + row.id + "'");
            console.log("deleted the row automatically...");
        }, textToMS(row.pasteex));
    });
}); 

Bila Anda tidak yakin dengan penempatannya, silakan cek file "index.js" pada source code buku ini.

Bila kita perhatikan pada script di atas, database aplikasi ini sangat sederhana.

Hanya memiliki satu tabel.

Memang demikian. Karena saya berusaha menyederhanakan clone Pastebin agar mudah dipelajari.

Jika Anda memiliki versi sendiri yang lebih kompleks tidak masalah. Anda tinggal kembangkan sendiri aplikasi ini.

Sekarang perhatikan pada kode bagian "//YANG INI - 1".

Di sana kita membuat database baru atau meng-overwrite database lama dengan nama " db_pastebin_clone.db".

File database itu berada di folder "ls-node-js-pasta".

Selanjutnya, pada kode bagian "//YANG INI - 2", kita menjalankan query:

CREATE TABLE IF NOT EXISTS pastes (
    id INTEGER PRIMARY KEY,
    newpaste TEXT,
    syntaxhi TEXT,
    pasteex TEXT,
    pasteexpos TEXT,
    title TEXT,
    pasteperm TEXT,
    pastemanagerperm TEXT,
    hits INTEGER
);

Kolom "id" adalah id dari row. Di sana tertulis "INTEGER PRIMARY KEY".

Hal itu dilakukan agar "id" bersifat auto increment.

Kolom "newpaste" menyimpan teks yang kita paste pada aplikasi ini.

Kolom "syntaxhi" menyimpan setting syntax highlighting apa yang kita gunakan untuk paste tersebut.

Kolom "pasteex" menyimpan setting mengenai berapa lama paste disimpan dalam database hingga expired dan dihapus secara otomatis.

Kolom "pasteexpos" menyimpan setting mengenai apakah paste ini bersifat public atau unlisted.

Kolom "title" adalah judul dari paste.

Kolom "pasteperm" adalah permalink dari paste yang bisa dishare dan dilihat publik.

Kolom "pastemanagerperm" adalah permalink dari paste manager yang hanya ditujukan untuk pembuat paste. Ini sebaiknya tidak dibagikan pada orang lain. Selain itu, tidak ada cara untuk menemukan permalink ini jika pembuat paste lupa.

Kolom "hits" mencatat total kunjungan pada paste. Bukan paste manager.

Nilai "hits" akan bertambah seiring bertambahnya kunjungan pada paste.

Nilai itu juga menjadi faktor penentu urutan listing dari paste-paste pada halaman "Trends".

Pada fungsi db.run tadi, kita juga memberikan callback:

db.each("SELECT * from pastes", function(err, row) {//YANG INI - 3
    console.log("scheduling the timeout for: " + row.title);
    setTimeout(function(){//YANG INI - 4
        console.log("deleting the row automatically...");
        db.run("DELETE FROM pastes WHERE id = '" + row.id + "'");
        console.log("deleted the row automatically...");
    }, textToMS(row.pasteex));
});

Pada kode bagian "//YANG INI - 3", kita menjelajahi tiap row dari tabel paste dan setiap satu row ditemukan, maka callback ini dijalankan:

console.log("scheduling the timeout for: " + row.title);
setTimeout(function(){ //YANG INI - 4
    console.log("deleting the row automatically...");
    db.run("DELETE FROM pastes WHERE id = '" + row.id + "'");
    console.log("deleted the row automatically...");
}, textToMS(row.pasteex));

Di sini kita akan menjadwalkan penghapusan setiap row dari tabel paste selama:

textToMS(row.pasteex)

Jadi setTimeout tadi akan selama itu.

Kita menggunakan textToMS karena kita akan mengonversi 1 Day, 12 Hours ... 2 Minutes menjadi jumlah milisecond dalam integer.

Kita melakukan ini pada saat aplikasi ini baru dijalankan karena boleh jadi ketika aplikasi ini berhenti entah karena crash atau faktor lain, masih ada paste yang belum dihapus karena belum memenuhi durasi lifetime-nya.

Sebenarnya cara ini kurang akurat, tetapi setidaknya memberi ide mengenai bagaimana cara membuat expirable content pada sebuah aplikasi web.

Membuat Fungsi-Fungsi Utility

Setelah kita membuat database untuk Node.js Pasta, kita akan membuat fungsi-fungsi utility-nya.

Fungsi-fungsi yang akan kita buat ini berguna untuk menyederhanakan penulisan kode sehingga tidak banyak copy paste dari satu baris ke baris yang lain.

Adapun fungsi-fungsi utility yang akan kita buat adalah:

  • textToMS
  • getBaseURL
  • getTheExcerpt


Fungsi textToMS dan getBaseURL ditulis di file "index.js" sedangkan getTheExcerpt akan ditulis pada template "functions.ejs".

Fungsi getTheExcerpt ditulis di file template karena ini merupakan utility untuk keperluan template rendering.

Fungsi textToMS

Pertama-tama, buka file "index.js", kemudian scroll hingga bagian terbawah.

Selanjutnya tulis kode ini:

function textToMS(textTime){
    if(textTime == "1 Day"){
        return 24 * 60 * 60 * 1000;
    } else if(textTime == "12 Hours") {
        return 12 * 60 * 60 * 1000;
    } else if(textTime == "3 Hours") {
        return 3 * 60 * 60 * 1000;
    } else if(textTime == "30 Minutes") {
        return 30 * 60 * 1000;
    } else if(textTime == "2 Minutes") {
        return 2 * 60 * 1000;
    }
}

Fungsi di atas digunakan untuk mengonversi teks "1 Day, 12 Hours, 3 Hours, 30 Minutes, dan 2 Minutes" menjadi integer milisecond.

Hal ini dilakukan karena pada HTML, nilai ini disajikan dalam sebuah dropdown yang value-nya berupa string.

Mengenai rumusnya cukup jelas. 1 Day misalnya, adalah 24 jam x 60 menit x 60 detik x 1000 milidetik.

Sisanya tidak jauh beda. Silakan tafsirkan sendiri.

Fungsi getBaseURL

Sekarang, di bawah fungsi textToMS pada file "index.js", tulis kode ini:

function getBaseURL(req){
    var port = app.get('port');
    var trailing = port == 80 || port == 443 ? "": (":" + port);
    return (req.secure?'https://':'http://') + req.hostname + trailing;

    //Jika Anda men-deploy aplikasi ini di heroku, replace kode
    //di atas dengan ini:
    //return "https://namasubdomainanda.herokuapp.com";
    //misalnya (yang punya saya):
    //return "https://ls-node-js-pasta.herokuapp.com";
}

Fungsi ini digunakan untuk mendapatkan base URL dari aplikasi ini.

Fungsi ini membutuhkan argument "req" yang didapatkan pada saat routing, misalnya:

app.get('/manage/:manageID', function (req, res) {
 console.log(getBaseURL(req));
})

Tetapi itu tidak mutlak diperlukan jika Anda mendeploy-nya ke Heroku seperti yang dijelaskan pada komentar di fungsi tersebut:

//Jika Anda men-deploy aplikasi ini di heroku, replace kode
//di atas dengan ini:
//return "https://namasubdomainanda.herokuapp.com";
//misalnya (yang punya saya):
//return "https://ls-node-js-pasta.herokuapp.com";

Jika Anda menjalankan aplikasi ini di localhost, Anda tidak perlu mengikuti komentar itu.

Saya memilih cara manual untuk mendapatkan base URL di Heroku, karena cara ini adalah yang paling mudah mengingat Heroku sepertinya menggunakan proxy untuk mengakses server yang sebenarnya.

Fungsi getBaseURL ini akan diperlukan ketika kita membuka halaman "Paste Manager" di mana di sana aplikasi ini akan meng-generate URL untuk mengakses halaman "Paste Manager" dan halaman "Paste" yang telah dibahas pada subbab "Rancangan User Interface".


Fungsi getTheExcerpt

Kali fungsi yang agak spesial.

Fungsi ini ditulis di file template (yang berekstensi .ejs).

Fungsi ini ditulis di file tersebut karena ini merupakan fungsi utility untuk template rendering.

Sekarang, buka file "functions.ejs", lalu tulis kode ini:

<%
getTheExcerpt = function(wholeText){
    var strLen = wholeText.length;
    if(strLen < 100){
        return wholeText.substring(0, strLen);
    }
    return wholeText.substring(0, 100);
}
%>

Yang dilakukan oleh fungsi ini adalah mengambil potongan teks dari sebuah teks penuh.

Tadi kita lihat pada bagian database bahwa kolom "newpaste" dari tabel "pastes" menyimpan teks penuh dari paste yang kita buat.

Padahal pada halaman trends kita hanya memerlukan potongan kecil dari teks tersebut.

Oleh karena itulah kita menggunakan fungsi ini, sehingga yang tampil pada row di halaman "Trends" adalah hanya potongannya seperti ini:


Membuat Halaman 404 Not Found

Sekarang kita telah menyelesaikan kira-kira 50 persen dari seluruh script dalam project ini.

Mungkin Anda masih ingat routing di file "index.js" yang masih belum diisi dengan response.

Mulai sekarang kita akan mengisinya sambil membuat user interfacenya dalam HTML, CSS, dan Javascript.

Perlu saya ingatkan bahwa mulai saat ini, sering-seringlah mengecek kesesuaian kode di buku ini dengan yang ada dalam source code buku ini.

Pertama-tama, buka file "404.ejs" dalam folder "ls-node-js-pasta/views".

File ini masih kosong, tetapi pada folder yang sama di dalam SOURCE CODE BUKU INI sudah ada isinya.
Langsung copy-paste saja dari source code tadi ke file "404.ejs" yang masih kosong ini.

Isinya kira-kira begini:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>404: ERROR NOT FOUND - Node.js Pasta</title>

        <meta name="description" content="Node.js Pasta - Content Not Found">
        <meta name="author" content="Lusfikar Sheba">
   
        <!-- YANG INI - 1 -->
        <link rel="shortcut icon" type="image/png" href="/views/img/favicon.png"/>
        <link href="/views/css/bootstrap.min.css" rel="stylesheet">
        <link href="/views/css/bootstrap-theme.min.css" rel="stylesheet">
        <script src="/views/js/jquery-3.2.1.min.js"></script>
        <script src="/views/js/bootstrap.min.js"></script>
    </head>
<body>

<div class="container">
    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/">Node.js Pasta</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
                <ul class="nav navbar-nav navbar-right">
                    <li><a href="/trends">Trends</a></li>
                    <li><div class="btn-nav"><a class="btn btn-primary btn-small navbar-btn" href="/"><span class="glyphicon glyphicon-plus-sign"></span> New Paste</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="row">
        <div class= "col-lg-12 col-md-12 col-sm-12 col-xs-12">
            <div class="jumbotron">
                <h1>404: ERROR NOT FOUND</h1>
                <p>not found not found not found not found not found not found not found not found not found .</p>
                <p>not found not found not found not found not found not found not found not found not found .</p>
                <p>not found not found not found not found not found not found not found not found not found .</p>
            </div>
        </div>
    </div>
    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <p class="navbar-text pull-right">Copyright &#169; Lusfikar Sheba 2017</p>
        </div>
    </nav>
</body>
</html>

Perhatikan pada kode bagian "//YANG INI - 1".

Di situ kita mengimpor package-package non NPM, yaitu:

  • Bootstrap
  • JQuery


Di situ kita juga mendaftarkan file gambar "favicon.png" pada folder "ls-node-js-pasta/views/img/" agar muncul di tab di browser.

Halaman ini akan muncul apabila user mengakses URL yang salah.

Agar bisa seperti itu, buka file "index.js" versi Anda (bukan yang dari source code buku ini), kemudian fokuskan perhatian Anda pada route ini:

app.get('*', function(req, res){

});

Kemudian tambahkan kode-kode ini di dalam route tersebut:

res.status(404);
res.render('404');

Sehingga menjadi:

app.get('*', function(req, res){
    res.status(404);
    res.render('404');
});

Dengan demikian, URL apapun yang tidak terdaftar di route akan diarahkan ke halaman "404".
Fungsi res.status adalah HTTP Status Code yang diberikan pada route tersebut.

Daftar lengkapnya ada di:

https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

Sedangkan fungsi res.render digunakan untuk menampilkan template "404.ejs" sebagai HTML di sisi browser pengguna.

Jadi di sini tidak perlu ditulis lengkap dengan ekstensinya (".ejs").

Sekarang Anda bisa coba jalankan aplikasi ini.

Jalankan perintah ini di command line (dengan titik di sebelah node):

node .

Kemudian buka browser anda ke:

http://localhost:5000

Maka nanti halaman 404 akan ditampilkan.

Membuat Halaman Home (New Paste)

Setelah membuat halaman 404, sekarang kita membuat halaman "Home".

Pada halaman ini, kita akan menampilkan form untuk membuat halaman pembuatan paste.

Buka file kosong "home.ejs" yang telah kita buat sebelumnya.

Halaman ini masih kosong, maka copy dari source code buku ini dari file yang sama (ada di folder views).

Maka isinya akan seperti ini:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Node.js Pasta</title>

        <meta name="description" content="Node.js Pasta. Share your pastes here!">
        <meta name="author" content="Lusfikar Sheba">

        <!-- [ Social Media meta tag ] -->
        <meta content='summary' name='twitter:card'/>
        <meta content='@ciyy_space' name='twitter:site'/>
        <meta content='@ciyy_space' name='twitter:creator'/>

        <meta content='<%=hostname%>' property='og:url'/>
        <meta content='Node.js Pasta' property='og:site_name'/>
        <meta content='Node.js Pasta' property='og:title'/>
        <meta content='Node.js Pasta' name='twitter:title'/>
        <meta content='website' property='og:type'/>
        <meta content='<%=hostname%>/views/img/ciyy-200x200.png' property='og:image'/>
        <meta content='<%=hostname%>/views/img/ciyy-200x200.png' name='twitter:image'/>
        <meta content='Node.js Pasta. Share your pastes here!' property='og:description'/>
        <meta content='Node.js Pasta. Share your pastes here!' name='twitter:description'/>
        <meta content='en_US' property='og:locale'/>
        <!-- [/ Social Media meta tag ] -->

        <link rel="shortcut icon" type="image/png" href="/views/img/favicon.png"/>
        <link href="/views/css/bootstrap.min.css" rel="stylesheet">
        <link href="/views/css/bootstrap-theme.min.css" rel="stylesheet">
        <script src="/views/js/jquery-3.2.1.min.js"></script>
        <script src="/views/js/bootstrap.min.js"></script>
    </head>
<body>
<% include ./functions %>
<div class="container">
    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/">Node.js Pasta</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
                <ul class="nav navbar-nav navbar-right">
                    <li><a href="/trends">Trends</a></li>
                    <li><div class="btn-nav"><a class="btn btn-primary btn-small navbar-btn" href="/"><span class="glyphicon glyphicon-plus-sign"></span> New Paste</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="row">
        <div class= "col-lg-12 col-md-12 col-sm-12 col-xs-12">
            <div class="jumbotron">
                <h1>Apa itu Node.js Pasta?</h1>
                <p>Node.js Pasta adalah alat untuk berbagi teks yang di-paste-kan ke website ini.</p>
                <p>Anda bisa membuat, mengubah, dan menghapus paste Anda TANPA LOGIN di sini!</p>
                <p>Jika berminat dengan website semacam ini, <a href="https://projects.co.id/public/browse_users/view/99bc11/ciyyspace" target="_blank" rel="nofollow">pekerjakan saya di sini</a> atau <a href="javascript:void(0)" target="_blank" rel="nofollow">beli bukunya di sini</a>.</p>
            </div>
        </div>
    </div>

    <form action="/new-paste" method="post">
        <div class="row">
            <div class= "col-lg-12 col-md-12 col-sm-12 col-xs-12">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title"><b>New Paste</b></h3>
                    </div>
                    <div class="panel-body">
                        <textarea id="pc-ta-newpaste" name="pcTaNewpaste" class="form-control" rows="10"></textarea>
                    </div>
                </div>
            </div>
        </div>

        <div class="row">
            <div class= "col-lg-6 col-md-6 col-sm-6 col-xs-6">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title"><b>Settings</b></h3>
                    </div>
                    <div class="panel-body">
                        <div class="form-group">
                            <label for="pc-sl-syntaxhi">Syntax Highlighting:</label>
                            <select id="pc-sl-syntaxhi" name="pcSlSyntaxhi" class="form-control">
                                <%
                                var options = [ "text", "html", "css", "javascript" ];
                                for ( var i = 0; i < options.length; i++ )
                                {
                                    %><option value="<%=options[i] %>"><%=options[i] %></option><%
                                }
                                %>
                            </select> 
                              
                            <label for="pc-sl-pasteex">Paste Expiration:</label>
                            <select id="pc-sl-pasteex" name="pcSlPasteex" class="form-control">
                                <%
                                var options = [ "1 Day", "12 Hours", "3 Hours", "30 Minutes", "2 Minutes"];
                                for ( var i = 0; i < options.length; i++ )
                                {
                                    %><option value="<%=options[i] %>"><%=options[i] %></option><%
                                }
                                %>
                            </select>

                            <label for="pc-sl-pasteexpos">Paste Exposure:</label>
                            <select id="pc-sl-pasteexpos" name="pcSlPasteexpos" class="form-control">
                                <%
                                var options = [ "Public", "Unlisted" ];
                                for ( var i = 0; i < options.length; i++ )
                                {
                                    %><option value="<%=options[i] %>"><%=options[i] %></option><%
                                }
                                %>
                            </select>

                            <label for="pc-tx-title">Title:</label>
                            <input id="pc-tx-title" type="text" name="pcTxTitle" class="form-control">
                            <br/>
                            <input id="pc-sb-newpaste" name="pcSbNewpaste" class="btn btn-danger btn-block" type="submit" value="Create New Paste" />
                        </div>
                    </div>
                </div>
            </div>

            <div class= "col-lg-6 col-md-6 col-sm-6 col-xs-6">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title"><b>Recent Pastes</b></h3>
                    </div>
                    <div class="panel-body" style="min-height: 336px; max-height: 336px; overflow-y: scroll;">
                        <div class="list-group">
                            <% if (locals.therows) { %>
                                <% therows.forEach(function(singleRow) { %>
                                    <a href="<%= hostname %>/paste/<%= singleRow.pasteperm %>" target="_blank" class="list-group-item">
                                        <h4 class="list-group-item-heading"><%= singleRow.title %></h4>
                                        <p class="list-group-item-text"><i>Expired in <%= singleRow.pasteex %>, <%= singleRow.hits %> Hit(s)</i></p>
                                        <p class="list-group-item-text"><%= getTheExcerpt(singleRow.newpaste) %></p>
                                    </a>
                                <% }); %>
                            <% } %>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </form>
    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <p class="navbar-text pull-right">Copyright &#169; Lusfikar Sheba 2017</p>
        </div>
    </nav>
</body>
</html>

Di sana ada beberapa baris kode yang bentuknya seperti ini:

<select id="pc-sl-syntaxhi" name="pcSlSyntaxhi" class="form-control">
          <%
          var options = [ "text", "html", "css", "javascript" ];
          for ( var i = 0; i < options.length; i++ )
         {
                %><option value="<%=options[i] %>"><%=options[i] %></option><%
         }
         %>
 </select> 

Kode tersebut tugasnya adalah menampilkan item dari sebuah dropdown.

Yang barusan itu untuk dropdown jenis syntax highlighting yang pilihannya "text", "html", "css", dan "javascript".

Nanti string-string tersebut akan diinjeksikan ke variabel javascript pada halaman "Paste" setelah disimpan dalam database.

Sekarang di sisi server, ingat kembali tentang route ini:

app.get('/', function (req, res) { //YANG INI - 5

});

Ubah jadi:

app.get('/', function (req, res) {
    db.all("SELECT * FROM pastes WHERE pasteexpos='Public' LIMIT 20", function(err, rows) {
        res.render('home', { 
            therows : rows, //YANG INI - 1
            hostname: getBaseURL(req) //YANG INI - 2
        });
    });
});

Di sini ketika user mengakses halaman "Home", maka server akan menarik data dari database.

Data yang diambil adalah:

SELECT * FROM pastes WHERE pasteexpos='Public' LIMIT 20

Yang artinya hanya semua kolom dari tabel pastes yang paste exposurenya "Public" dan batasi hanya 20 row.

Di sini kita menggunakan db.all jadi yang diterima pada rows di agrument callbacknya adalah semua hasil dari query di atas. Bukan satu per satu seperti db.each.

Selanjutnya saat query tersebut selesai dieksekusi, callback dipanggil dan pada akhirnya server merender halaman "Home" dengan menginjeksikan rows sebagai therows (//YANG INI - 1) dan base URL sebagai hostname (//YANG INI - 2).

Maka nilai ini pada file "home.ejs":

<meta content='<%=hostname%>' property='og:url'/>

Akan di-replace dengan hostname dari injeksi di server tadi.

Sekarang anda bisa coba jalankan aplikasi ini:

node .

Dan buka browser ke:

http://localhost:5000

Nanti halaman 404 akan digantikan halaman "Home" lengkap dengan form-nya.

Membuat Halaman Manage

Setelah selesai membuat halaman "Home" dan route-nya, kita akan membuat halaman "Manage".

Di halaman ini, user akan bisa menampilkan paste yang telah dibuat di halaman "Home" dan mengedit atau menghapusnya.

Di sini juga akan di-generate URL manager dan URL paste

Pertama-tama bukalah file "manage.ejs" dari folder "ls-node-js-pasta/views".

Kira-kira isinya seperti ini:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Manage <%= therows[0].title %> - Node.js Pasta</title>

        <meta name="description" content="Manage <%= therows[0].title %> with Node.js Pasta">
        <meta name="author" content="Lusfikar Sheba">

        <!-- [ Social Media meta tag ] -->
        <meta content='summary' name='twitter:card'/>
        <meta content='@ciyy_space' name='twitter:site'/>
        <meta content='@ciyy_space' name='twitter:creator'/>

        <meta content='<%=hostname%>/manage/<%=therows[0].pastemanagerperm %>' property='og:url'/>
        <meta content='Manage <%= therows[0].title %> - Node.js Pasta' property='og:site_name'/>
        <meta content='Manage <%= therows[0].title %> - Node.js Pasta' property='og:title'/>
        <meta content='Manage <%= therows[0].title %> - Node.js Pasta' name='twitter:title'/>
        <meta content='article' property='og:type'/>
        <meta content='<%=hostname%>/views/img/ciyy-200x200.png' property='og:image'/>
        <meta content='<%=hostname%>/views/img/ciyy-200x200.png' name='twitter:image'/>
        <meta content='Manage <%= therows[0].title %> with Node.js Pasta' property='og:description'/>
        <meta content='Manage <%= therows[0].title %> with Node.js Pasta' name='twitter:description'/>
        <meta content='en_US' property='og:locale'/>
        <!-- [/ Social Media meta tag ] -->

        <link rel="shortcut icon" type="image/png" href="/views/img/favicon.png"/>
        <link href="/views/css/bootstrap.min.css" rel="stylesheet">
        <link href="/views/css/bootstrap-theme.min.css" rel="stylesheet">
        <script src="/views/js/jquery-3.2.1.min.js"></script>
        <script src="/views/js/bootstrap.min.js"></script>
    </head>
<body>

<div class="container">
    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/">Node.js Pasta</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
                <ul class="nav navbar-nav navbar-right">
                    <li><a href="/trends">Trends</a></li>
                    <li><div class="btn-nav"><a class="btn btn-primary btn-small navbar-btn" href="/"><span class="glyphicon glyphicon-plus-sign"></span> New Paste</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <form method="post">
        <div class="row">
            <div class= "col-lg-12 col-md-12 col-sm-12 col-xs-12">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title"><b>This Paste</b></h3>
                    </div>
                    <div class="panel-body">
                        <textarea id="pc-ta-newpaste" name="pcTaNewpaste" class="form-control" rows="10">
                            <%= therows[0].newpaste %>
                        </textarea>
                    </div>
                </div>
            </div>
        </div>

        <div class="row">
            <div class= "col-lg-6 col-md-6 col-sm-6 col-xs-6">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title"><b>Settings</b></h3>
                    </div>
                    <div class="panel-body">
                        <div class="form-group">
                            <label for="pc-sl-syntaxhi">Syntax Highlighting:</label>
                            <select id="pc-sl-syntaxhi" name="pcSlSyntaxhi" class="form-control">
                                <%
                                var options = [ "text", "html", "css", "javascript" ];
                                for ( var i = 0; i < options.length; i++ )
                                {
                                    var selected = ( therows[0].syntaxhi == options[i] ) ? "selected" : "";
                                    %><option value="<%=options[i] %>" <%=selected %>><%=options[i] %></option><%
                                }
                                %>
                            </select> 
                              
                            <label for="pc-sl-pasteex">Paste Expiration:</label>
                            <select id="pc-sl-pasteex" name="pcSlPasteex" class="form-control">
                                <%
                                var options = [ "1 Day", "12 Hours", "3 Hours", "30 Minutes", "2 Minutes"];
                                for ( var i = 0; i < options.length; i++ )
                                {
                                    var selected = ( therows[0].pasteex == options[i] ) ? "selected" : "";
                                    %><option value="<%=options[i] %>" <%=selected %>><%=options[i] %></option><%
                                }
                                %>
                            </select>

                            <label for="pc-sl-pasteexpos">Paste Exposure:</label>
                            <select id="pc-sl-pasteexpos" name="pcSlPasteexpos" class="form-control">
                                <%
                                var options = [ "Public", "Unlisted" ];
                                for ( var i = 0; i < options.length; i++ )
                                {
                                    var selected = ( therows[0].pasteexpos == options[i] ) ? "selected" : "";
                                    %><option value="<%=options[i] %>" <%=selected %>><%=options[i] %></option><%
                                }
                                %>
                            </select>

                            <label for="pc-tx-title">Title:</label>
                            <input id="pc-tx-title" type="text" name="pcTxTitle" class="form-control" value="<%= therows[0].title %>">
                            <br/>
                            <input formaction="/manage/save-paste/<%=therows[0].pastemanagerperm %>" id="pc-sb-savepaste" name="pcSbSavepaste" class="btn btn-danger btn-block" type="submit" value="Save Paste" />
                            <input formaction="/manage/delete-paste/<%=therows[0].pastemanagerperm %>" id="pc-sb-delpaste" name="pcSbDelpaste" class="btn btn-danger btn-block" type="submit" value="Delete Paste" />
                        </div>
                    </div>
                </div>
            </div>

            <div class= "col-lg-6 col-md-6 col-sm-6 col-xs-6">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title"><b>Links for This Paste</b></h3>
                    </div>
                    <div class="panel-body">
                        <label for="pc-tx-managerurl">Manager URL:</label>
                        <div id="pc-tx-managerurl" class="input-group">
                            <input id="pc-in-tx-managerurl" type="text" class="form-control" value="<%=hostname%>/manage/<%=therows[0].pastemanagerperm %>">
                            <span class="input-group-btn">
                                <button id="pc-btn-copy-managerurl" class="btn btn-default" type="button">Copy!</button>
                            </span>
                        </div>

                        <label for="pc-tx-pasteurl">Paste URL:</label>
                        <div id="pc-tx-pasteurl" class="input-group">
                            <input id="pc-in-tx-pasteurl" type="text" class="form-control" value="<%=hostname%>/paste/<%=therows[0].pasteperm %>">
                            <span class="input-group-btn">
                                <button id="pc-btn-copy-pasteurl" class="btn btn-default" type="button">Copy!</button>
                            </span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </form>
    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <p class="navbar-text pull-right">Copyright &#169; Lusfikar Sheba 2017</p>
        </div>
    </nav>
    <script>
        document.getElementById("pc-btn-copy-managerurl").addEventListener("click", function() {
            var succ = copyToClipboard(document.getElementById("pc-in-tx-managerurl"));
            if(succ == true){
                alert("URL has been copied to clipboard");
            }
        });

        document.getElementById("pc-btn-copy-pasteurl").addEventListener("click", function() {
            var succ = copyToClipboard(document.getElementById("pc-in-tx-pasteurl"));
            if(succ == true){
                alert("URL has been copied to clipboard");
            }
        });
        
        function copyToClipboard(elem) {
              // create hidden text element, if it doesn't already exist
            var targetId = "_hiddenCopyText_";
            var isInput = elem.tagName === "INPUT" || elem.tagName === "TEXTAREA";
            var origSelectionStart, origSelectionEnd;
            if (isInput) {
                // can just use the original source element for the selection and copy
                target = elem;
                origSelectionStart = elem.selectionStart;
                origSelectionEnd = elem.selectionEnd;
            } else {
                // must use a temporary form element for the selection and copy
                target = document.getElementById(targetId);
                if (!target) {
                    var target = document.createElement("textarea");
                    target.style.position = "absolute";
                    target.style.left = "-9999px";
                    target.style.top = "0";
                    target.id = targetId;
                    document.body.appendChild(target);
                }
                target.textContent = elem.textContent;
            }
            // select the content
            var currentFocus = document.activeElement;
            target.focus();
            target.setSelectionRange(0, target.value.length);
            
            // copy the selection
            var succeed;
            try {
                  succeed = document.execCommand("copy");
            } catch(e) {
                succeed = false;
            }
            // restore original focus
            if (currentFocus && typeof currentFocus.focus === "function") {
                currentFocus.focus();
            }
            
            if (isInput) {
                // restore prior selection
                elem.setSelectionRange(origSelectionStart, origSelectionEnd);
            } else {
                // clear temporary content
                target.textContent = "";
            }
            return succeed;
        }
    </script>
</body>
</html>

Perhatikan pada bagian:

<title>Manage <%= therows[0].title %> - Node.js Pasta</title>

Di sini kita menuliskan judul halaman dengan cara yang mirip hostname pada halaman "Home".

Bedanya karena yang diinjeksi di server adalah row dari hasil query yang berupa array, maka di sana ada index 0 ("[0]").

Adapun ".title" di atas adalah nama kolom dari database.

Selanjutnya pada bagian ini:

<textarea id="pc-ta-newpaste" name="pcTaNewpaste" class="form-control" rows="10">
          <%= therows[0].newpaste %>
</textarea>

Kita mengisi textarea paste yang berisi konten paste dengan nilai yang telah disimpan dari database.

Jadi ketika kita mengisi "abcde" pada halaman "Home", nilai itu akan dibawa ke halaman "Manage" di bagian textarea tersebut.

Lebih jelasnya, lihat kodenya di server (index.js):

app.get('/manage/:manageID', function (req, res) {
    db.all("SELECT * FROM pastes WHERE pastemanagerperm = '" + req.params.manageID + "'", function(err, rows) {
        if(rows.length > 0){
            res.render('manage', { 
                therows : rows,
                hostname: getBaseURL(req)
            });
        }else{
            res.status(404);
            res.render('404');
        }
    });
})

Di kode tersebut, kita meminta pada database untuk menampilkan row dari tabel pastes yang manage id-nya adalah "req.params.manageID".

Atau lebih tepatnya yang dicetak tebal dan digaris bawah ini, misalnya:

http://localhost:5000/manage/yanginilho

Cukup jelas bukan? ManageID di database disimpan dalam kolom pastemanagerperm.

Setelah query tersebut dijalankan, maka halaman "Manage" akan dirender di dalam callback.

Jika query tersebut hasilnya nihil, maka buka halaman 404.

Jadi di mana nilai textarea tadi?

Jawabannya adalah di dalam variabel therows yang diinjeksikan tadi.

Selanjutnya, bagian ini akan menampilkan URL manager yang digenerate:

<div id="pc-tx-managerurl" class="input-group">
       <input id="pc-in-tx-managerurl" type="text" class="form-control" value="<%=hostname%>/manage/<%=therows[0].pastemanagerperm %>">
       <span class="input-group-btn">
                <button id="pc-btn-copy-managerurl" class="btn btn-default" type="button">Copy!</button>
       </span>
 </div>

Tepatnya bagian yang dicetak tebal tadi:

value="<%=hostname%>/manage/<%=therows[0].pastemanagerperm %>"

Hal yang sama berlaku juga pada URL paste yang digenerate.

URL tersebut bisa dicopy melalui sebuah button bernama "Copy" yang dicetak tebal tadi:

<button id="pc-btn-copy-managerurl" class="btn btn-default" type="button">Copy!</button>

Tapi tidak bisa jika tanpa javascript ini:

document.getElementById("pc-btn-copy-managerurl").addEventListener("click", function() {
        var succ = copyToClipboard(document.getElementById("pc-in-tx-managerurl"));
        if(succ == true){
            alert("URL has been copied to clipboard");
        }
});


Lalu bagaimanakah fungsi copyToClipboard? Jawabnya adalah:

function copyToClipboard(elem) {
 // create hidden text element, if it doesn't already exist
 var targetId = "_hiddenCopyText_";
 var isInput = elem.tagName === "INPUT" || elem.tagName === "TEXTAREA";
 var origSelectionStart, origSelectionEnd;
 if (isInput) {
  // can just use the original source element for the selection and copy
  target = elem;
  origSelectionStart = elem.selectionStart;
  origSelectionEnd = elem.selectionEnd;
 } else {
  // must use a temporary form element for the selection and copy
  target = document.getElementById(targetId);
  if (!target) {
   var target = document.createElement("textarea");
   target.style.position = "absolute";
   target.style.left = "-9999px";
   target.style.top = "0";
   target.id = targetId;
   document.body.appendChild(target);
  }
  target.textContent = elem.textContent;
 }
 // select the content
 var currentFocus = document.activeElement;
 target.focus();
 target.setSelectionRange(0, target.value.length);
 
 // copy the selection
 var succeed;
 try {
    succeed = document.execCommand("copy");
 } catch(e) {
  succeed = false;
 }
 // restore original focus
 if (currentFocus && typeof currentFocus.focus === "function") {
  currentFocus.focus();
 }
 
 if (isInput) {
  // restore prior selection
  elem.setSelectionRange(origSelectionStart, origSelectionEnd);
 } else {
  // clear temporary content
  target.textContent = "";
 }
 return succeed;
}

Isi dari paste tersebut akan diupdate perubahannya atau dihapus melalui form ini:

<input formaction="/manage/save-paste/<%=therows[0].pastemanagerperm %>" id="pc-sb-savepaste" name="pcSbSavepaste" class="btn btn-danger btn-block" type="submit" value="Save Paste" />
<input formaction="/manage/delete-paste/<%=therows[0].pastemanagerperm %>" id="pc-sb-delpaste" name="pcSbDelpaste" class="btn btn-danger btn-block" type="submit" value="Delete Paste" />

Di sini kita tidak menggunakan tag "form" tapi "formaction", jadi dua tombol yang berbeda bisa memiliki actionnya masing masing, yakni yang dicetak tebal tadi.

Di sisi server (index.js), penge-save-an paste terjadi seperti ini:

app.post('/manage/save-paste/:manageID', function(req, res) {
    db.run(
        "UPDATE pastes SET newpaste=?,syntaxhi=?,pasteex=?,pasteexpos=?,title=? WHERE pastemanagerperm=?",
        req.body.pcTaNewpaste,
        req.body.pcSlSyntaxhi,
        req.body.pcSlPasteex,
        req.body.pcSlPasteexpos,
        req.body.pcTxTitle == "" ? "Untitled" : req.body.pcTxTitle,
        req.params.manageID
    );

    console.log("saved: " + req.params.manageID);

    setTimeout(function(){
        console.log("deleting the row automatically...");
        db.run("DELETE FROM pastes WHERE pastemanagerperm = '" + req.params.manageID + "'");
        console.log("deleted the row automatically...");
    }, textToMS(req.body.pcSlPasteex));

    res.redirect("/manage/"+ req.params.manageID);
});

Perhatikan bahwa kita akan mengeksekusi query update ("UPDATE paste SET....".

Kemudian setelah di-update, kita akan mengeset timer kembali:

setTimeout(function(){
        console.log("deleting the row automatically...");
        db.run("DELETE FROM pastes WHERE pastemanagerperm = '" + req.params.manageID + "'");
        console.log("deleted the row automatically...");
}, textToMS(req.body.pcSlPasteex));

Dengan cara yang sama dengan bagian pembuatan database di bab sebelumnya.

Hal itu dilakukan karena di halaman "Manage" kita bisa meng-update Paste Expiration.

Selanjutnya penghapusan paste di sisi server (index.js):

app.post('/manage/delete-paste/:manageID', function(req, res) {
    db.run("DELETE FROM pastes WHERE pastemanagerperm = '" + req.params.manageID + "'");
    console.log("deleted: " + req.params.manageID);
    res.redirect("/");
});

Tidak terlalu rumit. Kita hanya perlu melakukan query ("DELETE FROM pastes ....".

Kemudian, dilanjutkan dengan redirect ke halaman "Home" (res.redirect("/");).

Membuat Halaman Paste

Kita telah melewati bagian tersulit dari aplikasi ini.

Saatnya bersantai...

Sekarang buka file "paste.ejs" dari folder "ls-node-js-pasta/views".

Isinya kira-kira seperti ini:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title><%= therows[0].title %> - Node.js Pasta</title>

        <meta name="description" content="Share <%= therows[0].title %> with Node.js Pasta">
        <meta name="author" content="Lusfikar Sheba">

        <!-- [ Social Media meta tag ] -->
        <meta content='summary' name='twitter:card'/>
        <meta content='@ciyy_space' name='twitter:site'/>
        <meta content='@ciyy_space' name='twitter:creator'/>

        <meta content='<%=hostname%>/paste/<%=therows[0].pasteperm %>' property='og:url'/>
        <meta content='<%= therows[0].title %> - Node.js Pasta' property='og:site_name'/>
        <meta content='<%= therows[0].title %> - Node.js Pasta' property='og:title'/>
        <meta content='<%= therows[0].title %> - Node.js Pasta' name='twitter:title'/>
        <meta content='article' property='og:type'/>
        <meta content='<%=hostname%>/views/img/ciyy-200x200.png' property='og:image'/>
        <meta content='<%=hostname%>/views/img/ciyy-200x200.png' name='twitter:image'/>
        <meta content='Share <%= therows[0].title %> with Node.js Pasta' property='og:description'/>
        <meta content='Share <%= therows[0].title %> with Node.js Pasta' name='twitter:description'/>
        <meta content='en_US' property='og:locale'/>
        <!-- [/ Social Media meta tag ] -->

        <link rel="shortcut icon" type="image/png" href="/views/img/favicon.png"/>
        <link href="/views/css/bootstrap.min.css" rel="stylesheet">
        <link href="/views/css/bootstrap-theme.min.css" rel="stylesheet">
        <link href="/views/css/font-awesome.min.css" rel="stylesheet">
        <script src="/views/js/jquery-3.2.1.min.js"></script>
        <script src="/views/js/bootstrap.min.js"></script>
        
        <style type="text/css" media="screen">
            #editor { 
                position: relative;
                height: 300px;
            }
        </style>
    </head>
<body>

<div class="container">
    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/">Node.js Pasta</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
                <ul class="nav navbar-nav navbar-right">
                    <li><a href="/trends">Trends</a></li>
                    <li><div class="btn-nav"><a class="btn btn-primary btn-small navbar-btn" href="/"><span class="glyphicon glyphicon-plus-sign"></span> New Paste</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="row">
        <div class= "col-lg-12 col-md-12 col-sm-12 col-xs-12">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title"><b>This Paste</b></h3>
                </div>
                <div class="panel-body">
                    <div id="editor">
                        <%= therows[0].newpaste %>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div class="row">
        <div class= "col-lg-12 col-md-12 col-sm-12 col-xs-12">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title"><b>Links for This Paste</b></h3>
                </div>
                <div class="panel-body">
                    <label for="pc-tx-pasteurl">Paste URL:</label>
                    <div id="pc-tx-pasteurl" class="input-group">
                        <input id="pc-in-tx-pasteurl" type="text" class="form-control" value="<%=hostname%>/paste/<%=therows[0].pasteperm %>">
                        <span class="input-group-btn">
                            <button id="pc-btn-copy-pasteurl" class="btn btn-default" type="button">Copy!</button>
                        </span>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div class="row">
        <div class= "col-lg-12 col-md-12 col-sm-12 col-xs-12">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title"><b>Share</b></h3>
                </div>
                <div class="panel-body">
                    <div style="text-align: center">
                        <a href="http://www.facebook.com/share.php?u=<%=hostname%>/paste/<%=therows[0].pasteperm %>&t=<%=therows[0].title %>" class="fa fa-facebook fa-5x" target="_blank"></a>
                        <a href="http://twitter.com/share?original_referer=<%=hostname%>/paste/<%=therows[0].pasteperm %>&source=web&text=<%=therows[0].title %>" class="fa fa-twitter fa-5x" target="_blank"></a>
                        <a href="https://plus.google.com/share?url=<%=hostname%>/paste/<%=therows[0].pasteperm %>" class="fa fa-google fa-5x" target="_blank"></a>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <p class="navbar-text pull-right">Copyright &#169; Lusfikar Sheba 2017</p>
        </div>
    </nav>

    <script src="/views/js/ace/ace.js" type="text/javascript" charset="utf-8"></script>
    <script>
        var editor = ace.edit("editor");
        editor.setTheme("ace/theme/github");
        editor.getSession().setMode("ace/mode/<%=therows[0].syntaxhi %>");
        editor.setReadOnly(true);
    </script>
    <script>
        document.getElementById("pc-btn-copy-pasteurl").addEventListener("click", function() {
            var succ = copyToClipboard(document.getElementById("pc-in-tx-pasteurl"));
            if(succ == true){
                alert("URL has been copied to clipboard");
            }
        });

        function copyToClipboard(elem) {
                // create hidden text element, if it doesn't already exist
            var targetId = "_hiddenCopyText_";
            var isInput = elem.tagName === "INPUT" || elem.tagName === "TEXTAREA";
            var origSelectionStart, origSelectionEnd;
            if (isInput) {
                // can just use the original source element for the selection and copy
                target = elem;
                origSelectionStart = elem.selectionStart;
                origSelectionEnd = elem.selectionEnd;
            } else {
                // must use a temporary form element for the selection and copy
                target = document.getElementById(targetId);
                if (!target) {
                    var target = document.createElement("textarea");
                    target.style.position = "absolute";
                    target.style.left = "-9999px";
                    target.style.top = "0";
                    target.id = targetId;
                    document.body.appendChild(target);
                }
                target.textContent = elem.textContent;
            }
            // select the content
            var currentFocus = document.activeElement;
            target.focus();
            target.setSelectionRange(0, target.value.length);
            
            // copy the selection
            var succeed;
            try {
                    succeed = document.execCommand("copy");
            } catch(e) {
                succeed = false;
            }
            // restore original focus
            if (currentFocus && typeof currentFocus.focus === "function") {
                currentFocus.focus();
            }
            
            if (isInput) {
                // restore prior selection
                elem.setSelectionRange(origSelectionStart, origSelectionEnd);
            } else {
                // clear temporary content
                target.textContent = "";
            }
            return succeed;
        }
    </script>
</body>
</html>

Tidak ada yang rumit di sini, fungsi  copyToClipboard yang sudah dijelaskan juga ada di sini.

Injeksi therows ke dalam file "paste.ejs" yang sudah dijelaskan juga ada.

Berarti tinggal satu hal lagi yang belum dijelaskan.

Dan itu adalah membuat twitter card dan facebook open graph.

Twitter card adalah semacam screenshot yang ditampilkan saat kita men-share sebuah halaman web ke social media Twitter.

Facebook open graph juga mirip dengan itu.

Contohnya yang seperti ini:


Screenshot-screenshot tadi saya dapatkan dari debugger milik twitter dan facebook.

Alamatnya di sini:

https://cards-dev.twitter.com/validator

https://developers.facebook.com/tools/debug/

Jadi, ketika Anda mempelajari tentang ini, lakukan percobaan dengan menggunakan website tersebut.

Dari sisi coding, yang menentukan tampilan tadi ada di bagian ini (ada di paste.ejs):

<!-- [ Social Media meta tag ] -->
<meta content='summary' name='twitter:card'/>
<meta content='@ciyy_space' name='twitter:site'/>
<meta content='@ciyy_space' name='twitter:creator'/>

<meta content='<%=hostname%>/paste/<%=therows[0].pasteperm %>' property='og:url'/>
<meta content='<%= therows[0].title %> - Node.js Pasta' property='og:site_name'/>
<meta content='<%= therows[0].title %> - Node.js Pasta' property='og:title'/>
<meta content='<%= therows[0].title %> - Node.js Pasta' name='twitter:title'/>
<meta content='article' property='og:type'/>
<meta content='<%=hostname%>/views/img/ciyy-200x200.png' property='og:image'/>
<meta content='<%=hostname%>/views/img/ciyy-200x200.png' name='twitter:image'/>
<meta content='Share <%= therows[0].title %> with Node.js Pasta' property='og:description'/>
<meta content='Share <%= therows[0].title %> with Node.js Pasta' name='twitter:description'/>
<meta content='en_US' property='og:locale'/>
<!-- [/ Social Media meta tag ] -->

Untuk twitter, yang perlu diperhatikan adalah:

<meta content='summary' name='twitter:card'/>
<meta content='<%= therows[0].title %> - Node.js Pasta' name='twitter:title'/>
<meta content='<%=hostname%>/views/img/ciyy-200x200.png' name='twitter:image'/>

Bagian pertama menentukan apakah tampilannya besar atau kecil. Dalam hal ini kecil.

Bagian kedua menentukan bagaimana title-nya.

Bagian ketiga menentukan apa gambarnya.

Untuk gambar, paling aman ambil resolusi 200x200 pixel.

Sisanya cukup jelas, yang penting di bagian meta name ada teks:

name='twitter:blablabla'

Jadi kalau:

name='twitter:description'

Ya... berarti untuk deskripsi dari link yang di-share.

Untuk Facebook, yang perlu diperhatikan adalah:

<meta content='<%=hostname%>/paste/<%=therows[0].pasteperm %>' property='og:url'/>
<meta content='<%=hostname%>/views/img/ciyy-200x200.png' property='og:image'/>

Bagian pertama harus diisi URL dari paste.

Bagian kedua gambarnya untuk yang kecil 200x200 pixel.

Ciri khas dari Facebook adalah ada teks:

property='og:blablabla'

Bentuk lainnya bisa ditafsirkan sendiri.

Membuat Halaman Trends

Saat ini, kita telah menyentuh pembahasan terakhir.

Trends berguna untuk menampilkan paste dengan hits terbanyak.

Diurutkan dari yang terbanyak ke yang tersedikit dari atas ke bawah.

Pertama-tama, buka file "trends.ejs" dari folder "ls-node-js-pasta/views".

Isinya kira-kira begini:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Trends - Node.js Pasta</title>

        <meta name="description" content="Node.js Pasta Trends!">
        <meta name="author" content="Lusfikar Sheba">

        <!-- [ Social Media meta tag ] -->
        <meta content='summary' name='twitter:card'/>
        <meta content='@ciyy_space' name='twitter:site'/>
        <meta content='@ciyy_space' name='twitter:creator'/>

        <meta content='<%=hostname%>/trends' property='og:url'/>
        <meta content='Trends - Node.js Pasta' property='og:site_name'/>
        <meta content='Trends - Node.js Pasta' property='og:title'/>
        <meta content='Trends - Node.js Pasta' name='twitter:title'/>
        <meta content='website' property='og:type'/>
        <meta content='<%=hostname%>/views/img/ciyy-200x200.png' property='og:image'/>
        <meta content='<%=hostname%>/views/img/ciyy-200x200.png' name='twitter:image'/>
        <meta content='Node.js Pasta Trends!' property='og:description'/>
        <meta content='Node.js Pasta Trends!' name='twitter:description'/>
        <meta content='en_US' property='og:locale'/>
        <!-- [/ Social Media meta tag ] -->

        <link rel="shortcut icon" type="image/png" href="/views/img/favicon.png"/>
        <link href="/views/css/bootstrap.min.css" rel="stylesheet">
        <link href="/views/css/bootstrap-theme.min.css" rel="stylesheet">
        <script src="/views/js/jquery-3.2.1.min.js"></script>
        <script src="/views/js/bootstrap.min.js"></script>
    </head>
<body>
<% include ./functions %>
<div class="container">
    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/">Node.js Pasta</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
                <ul class="nav navbar-nav navbar-right">
                    <li><a href="/trends">Trends</a></li>
                    <li><div class="btn-nav"><a class="btn btn-primary btn-small navbar-btn" href="/"><span class="glyphicon glyphicon-plus-sign"></span> New Paste</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="row">
        <div class= "col-lg-12 col-md-12 col-sm-12 col-xs-12">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title"><b>Trending Pastes</b></h3>
                </div>
                <div class="panel-body" style="min-height: 336px; max-height: 336px; overflow-y: scroll;">
                    <div class="list-group">
                        <% if (locals.therows) { %>
                            <% therows.forEach(function(singleRow) { %>
                                <a href="<%= hostname %>/paste/<%= singleRow.pasteperm %>" target="_blank" class="list-group-item">
                                    <h4 class="list-group-item-heading"><%= singleRow.title %></h4>
                                    <p class="list-group-item-text"><i>Expired in <%= singleRow.pasteex %>, <%= singleRow.hits %> Hit(s)</i></p>
                                    <p class="list-group-item-text"><%= getTheExcerpt(singleRow.newpaste) %></p>
                                </a>
                            <% }); %>
                        <% } %>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <p class="navbar-text pull-right">Copyright &#169; Lusfikar Sheba 2017</p>
        </div>
    </nav>
</body>
</html>

Tidak ada hal baru di sini, kecuali bagian:

<div class="list-group">
 <% if (locals.therows) { %>
  <% therows.forEach(function(singleRow) { %>
   <a href="<%= hostname %>/paste/<%= singleRow.pasteperm %>" target="_blank" class="list-group-item">
    <h4 class="list-group-item-heading"><%= singleRow.title %></h4>
    <p class="list-group-item-text"><i>Expired in <%= singleRow.pasteex %>, <%= singleRow.hits %> Hit(s)</i></p>
    <p class="list-group-item-text"><%= getTheExcerpt(singleRow.newpaste) %></p>
   </a>
  <% }); %>
 <% } %>
</div>

Perhatikan bagian:

<% if (locals.therows) { %>

Di sini saya melakukan pengecekan apakah rows diinjeksi atau tidak.
Karena apa yang saya tulis di sisi server ini (index.js):

app.get('/trends', function (req, res) {
    db.all("SELECT * FROM pastes WHERE pasteexpos='Public' ORDER BY hits DESC LIMIT 20", function(err, rows) {
        res.render('trends', { 
            therows : rows,
            hostname: getBaseURL(req)
        });
    });
});

Lihat di sana bahwa saya tidak melakukan pengecekan jumlah rows di server.

Bandingkan dengan sisi server halaman "Manage":

app.get('/manage/:manageID', function (req, res) {
    db.all("SELECT * FROM pastes WHERE pastemanagerperm = '" + req.params.manageID + "'", function(err, rows) {
        if(rows.length > 0){
            res.render('manage', { 
                therows : rows,
                hostname: getBaseURL(req)
            });
        }else{
            res.status(404);
            res.render('404');
        }
    });
})

Di sisi server halaman "Manage" ada "if(rows.length > 0".

Sedangkan di sisi server halaman "Trends" tidak ada dan langsung render saja.

Apabila kita tidak melakukan pengecekan sama sekali maka di halaman "Trends" akan error atau undefined.

Maka itu, saya melakukan pengecekan di dalam template "paste.ejs":

<% if (locals.therows) { %>

Selanjutnya saya melakukan loop terhadap isi dari array "therows" diwakili oleh variabel "singleRow":

<% therows.forEach(function(singleRow) { %>

Dan pada akhirnya saya mengambil excerpt dari singleRow:

<%= getTheExcerpt(singleRow.newpaste) %>

Fungsi getTheExcerpt ini telah kita definisikan pada bab sebelumnya.

Tapi itu tidak bisa digunakan tanpa ini:

<% include ./functions %>

Dengan kode tersebut, kita meng-include template "functions.ejs" agar fungsi getTheExcerpt dapat digunakan.

Mencoba Aplikasi Ini

Sekarang, kita telah selesai membahas pengerjaan aplikasi Node.js Pasta.

Mungkin ada satu atau beberapa kode yang tertinggal, Anda tinggal mengopy-paste saja dari source code buku ini.

Atau boleh juga langsung jalankan source code buku ini. Yang penting Anda mengerti cara kerjanya.

Jadi... Jalankan perintah ini:

node .

Kemudian saksikan hasilnya di browser Anda.

Men-Deploy Aplikasi Ini di Heroku

Untuk mencoba tampilan sharing di social media, kita membutuhkan sebuah hosting.

Yang kita bahas adalah heroku ( https://www.heroku.com ).

Daftarkan akun Anda terlebih dahulu.

Kemudian, buat app baru dan beri nama yang available:



Setelah selesai, Anda siap untuk menguploadnya. Pastikan Anda sudah menginstall semua software yang dibutuhkan di bab awal.

Sekarang buka command line, masuk ke folder "ls-node-js-pasta".

Jalankan perintah ini:

heroku login

Isi email dan password Anda.

Kemudian jalankan perintah ini:

heroku git:clone -a ls-node-js-pasta

git add .

git commit -am "make it better"

git push heroku master

Tunggu sampai proses upload selesai.

Setelah itu buka browser ke:

https://nama_subdomain_anda.herokuapp.com

Untuk mengupdate saja, cukup dengan perintah ini:

heroku login

git add .

git commit -am "make it better"

git push heroku master

Nama subdomain sama dengan nama App yang telah kita berikan.

Kalau Anda tidak sabar dan ingin melihat langsung aplikasi ini yang sudah jadi, kunjungi:

https://ls-node-js-pasta.herokuapp.com/

Penutup

Ya.. Sampai di sini pembahasan kita tentang cara membuat aplikasi Node.js Pasta.

Aplikasi ini masih banyak kekurangan.

Anda bisa mencoba menambahkan sendiri fitur API dan embed paste.

Setelah Anda sukses, ceritakan kepada saya di website saya ( http://www.ciyy.space ) dan belilah beberapa buku lagi agar semakin sukses.