Materi 3
Materi 3
js
Menyiapkan Project
Sebelum membuat Web Server, buatlah proyek Node.js terlebih dahulu. Silakan buat
folder baru di dalam C -> javascript-projects (Windows) atau home -> javascript-
project (Linux dan Mac) dengan nama “nodejs-web-server”. Setelah itu, buka folder
tersebut menggunakan VSCode.
Mungkin Anda bertanya mengapa terdapat --y di akhir perintahnya? --y pada akhir
perintah tersebut berfungsi untuk menjawab seluruh pertanyaan yang diberikan NPM
ketika membuat proyek baru dengan jawaban/nilai default.
Jika Anda lebih suka menjawab pertanyaan-pertanyaan tersebut secara manual, silakan
hapus --y pada perintah tersebut.
Lanjut kita buat berkas JavaScript baru, karena kita hendak membuat server, maka
beri nama berkas tersebut server.js.
Kemudian buka berkas package.json dan tambahkan runner script seperti ini:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
}
Sebenarnya Anda bisa menghapus runner script test. Karena script tersebut tidak
kita gunakan. Jadi, runner script hanya memiliki nilai start saja.
"scripts": {
"start": "node server.js"
}
Simpan seluruh perubahan pada berkas yang ada. Kemudian buka terminal dan jalankan
perintah:
Bila konsol menampilkan pesan “Halo, kita akan belajar membuat server”, Selamat!
Persiapan proyek kita sudah selesai.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
HTTP module memiliki banyak member seperti objek, properti, atau method yang
berguna untuk melakukan hal-hal terkait protokol HTTP. Salah satu member yang
penting untuk kita ketahui sekarang adalah method http.createServer().
Sesuai namanya, method ini berfungsi untuk membuat HTTP server yang merupakan
instance dari http.server. Method ini menerima satu parameter custom callback yang
digunakan sebagai request listener. Di dalam request listener inilah logika untuk
menangani dan menanggapi sebuah request dituliskan.
/**
* Logika untuk menangani dan menanggapi request dituliskan pada fungsi ini
*
* @param request: objek yang berisikan informasi terkait permintaan
* @param response: objek yang digunakan untuk menanggapi permintaan
*/
const requestListener = (request, response) => {
};
Request listener memiliki 2 parameter, yakni request dan response. Seperti yang
tertulis pada contoh kode di atas, request merupakan objek yang menyimpan informasi
terkait permintaan yang dikirimkan oleh client. Di dalam objek ini kita bisa
melihat alamat yang diminta, data yang dikirim, ataupun HTTP metode yang digunakan
oleh client.
Sementara itu, response merupakan objek yang digunakan untuk menanggapi permintaan.
Melalui objek ini kita bisa menentukan data yang diberikan, format dokumen yang
digunakan, kode status, atau informasi response lainnya.
response.statusCode = 200;
response.end('<h1>Halo HTTP Server!</h1>');
};
Kode di atas merupakan contoh logika yang bisa dituliskan di dalam request
listener. Request listener akan menanggapi setiap permintaan dengan dokumen HTML,
kode status 200, dan menampilkan konten “Halo HTTP Server!”.
Lalu, bagaimana caranya agar server selalu sedia menangani permintaan yang masuk?
Setiap instance dari http.server juga memiliki method listen(). Method inilah yang
membuat http.server selalu standby untuk menangani permintaan yang masuk dari
client. Setiap kali ada permintaan yang masuk, request listener akan tereksekusi.
Namun, keempat parameter di atas bersifat opsional. Kita bisa memberikan nilai port
saja, atau kombinasi apa pun dari keempatnya. Hal itu tergantung terhadap kebutuhan
Anda. Namun lazimnya, ketika memanggil method listen() kita memberikan nilai port,
hostname, dan listeningListener.
response.statusCode = 200;
response.end('<h1>Halo HTTP Server!</h1>');
};
Setelah itu, jalankan perintah npm run start pada Terminal. Jika server berhasil
dijalankan, maka Anda akan melihat pesan ‘Server berjalan pada
http://localhost:5000’.
Untuk pengguna sistem operasi Windows, bila pop-up di bawah ini muncul, pilih saja
“Allow access”.
Selamat! Anda berhasil membuat HTTP Server pertama menggunakan Node.js. Anda bisa
coba melakukan request pada server tersebut melalui cURL seperti ini:
Terminal/CMD
curl -X GET http://localhost:5000/
Anda juga bisa mencoba langsung pada browser dengan mengunjungi halaman
http://localhost:5000/.
Good Job! Akhirnya Anda berhasil membuat web server pertama menggunakan Node.js!
Walau masih sangat sederhana, namun hal tersebut merupakan hal pertama yang
dilakukan oleh back-end developer dalam meniti karirnya.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Method/Verb Request
Web server yang sudah kita buat pada latihan sebelumnya sudah berhasil merespons
dan menampilkan data dalam dokumen HTML. Namun, tahukah Anda bahwa web server yang
kita buat belum mengenali sepenuhnya permintaan yang diberikan oleh client?
Maksudnya, meskipun client meminta dengan path atau method yang berbeda, server
akan merespons dengan data yang sama. Server kita saat ini tidak peduli permintaan
datang seperti apa, dia akan mengembalikan data yang sama. Anda bisa mencobanya
sendiri melalui cURL dengan menggunakan HTTP method yang berbeda.
Ketika mencobanya pastikan HTTP Server Anda sedang berjalan. Bila dalam keadaan
terhenti, jalankan kembali server dengan perintah npm run start pada Terminal
proyek nodejs-web-server.
Hal tersebut wajar karena kita memang belum menuliskan logika dalam menangani
permintaan dari method yang berbeda. Lalu, bagaimana caranya agar bisa melakukan
hal tersebut?
Fungsi request listener menyediakan dua parameter yakni request dan response. Fokus
ke parameter request, parameter ini merupakan instance dari http.ClientRequest yang
memiliki banyak properti di dalamnya.
Atau, Anda bisa menggunakan cara yang lebih clean dengan menggunakan object
destructuring seperti ini:
Properti method bernilai tipe method dalam bentuk string. Nilainya dapat berupa
“GET”, “POST”, “PUT”, atau method lainnya sesuai dengan yang client gunakan ketika
melakukan permintaan. Dengan memiliki nilai method, kita bisa memberikan respons
berbeda berdasarkan tipe method-nya.
Sekali lagi, tidak hanya properti method, objek request kaya akan properti dan
fungsi lain di dalamnya. Anda dapat mengeksplorasi properti atau fungsi lainnya
pada halaman dokumentasi Node.js tentang HTTP Client Request.
Pada latihan ini, kita akan melakukan konfigurasi agar server mengembalikan respons
dengan kata “hello” dalam berbagai bahasa sesuai method yang digunakan client.
Simpan perubahan pada berkas server.js; jalankan ulang server dengan perintah npm
run start; dan coba lakukan permintaan ke server dengan menggunakan method yang
berbeda melalui cURL.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Body Request
Ketika client melakukan permintaan dengan method POST atau PUT, biasanya permintaan
tersebut memiliki sebuah data yang disimpan pada body request. Data pada body bisa
berupa format teks, JSON, berkas gambar, atau format lainnya. Data tersebut
nantinya digunakan oleh server untuk diproses di database atau disimpan dalam
bentuk objek utuh.
request.on('end', () => {
body = Buffer.concat(body).toString();
});
};
Pertama, kita deklarasikan variabel body dan inisialisasikan nilainya dengan array
kosong. Ini berfungsi untuk menampung buffer pada stream.
Lalu, ketika event data terjadi pada request, kita isi array body dengan chunk
(potongan data) yang dibawa callback function pada event tersebut.
Terakhir, ketika proses stream berakhir, maka event end akan terbangkitkan. Di
sinilah kita mengubah variabel body yang sebelumnya menampung buffer menjadi data
sebenarnya dalam bentuk string melalui perintah Buffer.concat(body).toString().
Huft! Cukup melelahkan yah untuk mendapatkan data melalui teknik stream. Guna
memantapkan pemahaman, mari kita praktikan pada proyek web server sebelumnya.
Nah, di latihan kali ini kita akan coba mendapatkan data pada body request ketika
client mengirimkan request menggunakan method POST.
Buatlah web server merespons permintaan method POST dengan menampilkan sapaan dan
nama berdasarkan data body yang dikirim oleh client. Bila client mengirimkan nama
“Dicoding”, maka respons akan menampilkan “Hai, Dicoding!”.
Client akan mengirimkan data nama tersebut menggunakan format JSON seperti ini:
{ "name": "Dicoding" }
Namun sebelum itu, agar latihan lebih fokus terhadap bagaimana mendapatkan data
pada body, kita hapus dulu logika method yang sebenarnya belum kita butuhkan,
seperti PUT dan DELETE.
Jadi, silakan buka berkas server.js pada proyek nodejs-web-server dan hapuslah
logika method PUT dan DELETE. Sehingga, berkas server.js tampak lebih ringkas
seperti ini:
Selanjutnya, kita bisa mulai menuliskan logika stream di dalam blok POST.
request.on('end', () => {
body = Buffer.concat(body).toString();
response.end(`<h1>Hai, ${body}!</h1>`);
});
}
Perhatikan kode di atas! Kita memindahkan proses respons di dalam callback event
end. Hal ini diperlukan karena data body siap dikonsumsi hanya ketika event end
telah dibangkitkan. Dalam arti lain, server tidak akan mengirimkan respons bila
proses stream belum selesai.
Simpan perubahan pada berkas server.js; jalankan ulang server dengan perintah npm
run start; dan coba lakukan permintaan ke server dengan menggunakan method POST
melalui cURL seperti ini:
Tunggu, ini bukan hasil yang kita harapkan. Body masih bernilai data string JSON.
Data ini masih perlu kita olah lagi agar bisa mendapatkan nilai name yang
sebenarnya. Gunakanlah JSON.parse() untuk mengubah JSON string menjadi JavaScript
objek. Sesuaikan kembali kode pada blok POST menjadi seperti ini (lihat kode yang
ditebalkan):
request.on('end', () => {
body = Buffer.concat(body).toString();
const { name } = JSON.parse(body);
response.end(`<h1>Hai, ${name}!</h1>`);
});
}
Simpan perubahan pada berkas server.js; jalankan ulang server dengan perintah npm
run start; dan coba lagi lakukan permintaan ke server dengan menggunakan method
POST.
<h1>Hai, Dicoding!</h1>
Voila! Inilah hasil yang kita harapkan! Anda bisa kirimkan permintaan POST lain
dengan data nama Anda sendiri. Cobalah, apakah hasilnya sesuai atau tidak?
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Routing Request
Ketika menangani request, hal yang perlu kita cek selain method adalah URL atau
alamat yang dituju dari request tersebut. Sebagai contoh, ketika kita mengunjungi
dicoding.com dan dicoding.com/about, tentu hasil yang kita terima dari server akan
berbeda, bukan?
Dalam http.clientRequest, untuk mendapatkan nilai url sangatlah mudah, semudah kita
mendapatkan nilai request method yang digunakan.
Properti url akan mengembalikan nilai path secara lengkap tanpa host dan port yang
digunakan server. Contohnya, bila client meminta pada alamat
http://localhost:5000/about atau http://localhost:5000/about/, maka url akan
bernilai ‘/about’; bila meminta alamat http://localhost:5000 atau
http://localhost:5000/, maka url akan bernilai ‘/’.
Dengan mendapatkan nilai url, kita dapat merespons client sesuai dengan path yang
ia minta.
// curl http://localhost:5000/<any>
};
Kita juga bisa mengombinasikan evaluasi dengan method request. Alhasil, kita dapat
menentukan respons lebih spesifik lagi.
URL: ‘/’
Method: GET
Mengembalikan “Ini adalah homepage”.
Method: <any> (selain GET)
Mengembalikan “Halaman tidak dapat diakses dengan <any> request”.
URL: ‘/about’
Method: GET
Mengembalikan “Halo! Ini adalah halaman about”.
Method: POST (dengan melampirkan data name pada body)
Mengembalikan “Halo, <name>! Ini adalah halaman about”.
Method: <any> (selain GET dan POST)
Mengembalikan “Halaman tidak dapat diakses dengan <any> request”.
URL: <any> (selain / dan /about)
Method: <any>
Mengembalikan “Halaman tidak ditemukan!”.
Sudah paham? Huft, latihan kali ini sepertinya lebih menantang. Siapkan secangkir
kopi agar Anda tetap fokus dan mari kita mulai.
Pertama, agar kita dapat fokus pada hal routing. Beri komentar dulu kode logika di
dalam fungsi request listener yang sebelumnya kita buat.
// request.on('end', () => {
// body = Buffer.concat(body).toString();
// const { name } = JSON.parse(body);
// response.end(`<h1>Hai, ${name}!</h2>`);
// });
// }
};
Selanjutnya, kita ambil properti url dari request menggunakan teknik destructuring
object seperti mendapatkan nilai method. Lihat kode yang ditebalkan yah.
Good! Sekarang kita sudah dapat nilai url dari request. Saatnya kita menentukan
logika routing url sesuai dengan ketentuan menggunakan if else.
Nice! Coba lihat komentar TODO (yang harus dikerjakan) pada kode tersebut. Kita
akan selesaikan TODO sesuai urutan yang ada yah. Urutan tersebut sengaja disusun
dari yang paling mudah, lalu merangkak ke yang lebih sulit.
Blok else yang paling terakhir (TODO pertama) akan tereksekusi bila url bukan
bernilai ‘/’ atau ‘/about’. Berdasarkan ketentuan yang ada di atas, kita harus
merespons dengan pesan “Halaman tidak ditemukan!”. Yuk kita langsung saja tulis
responsnya.
const requestListener = (request, response) => {
response.setHeader('Content-Type', 'text/html');
response.statusCode = 200;
Good! Mari kita coba dahulu perubahan yang ada. Simpan perubahan pada berkas
server.js; jalankan ulang server dengan perintah npm run start; dan silakan lakukan
permintaan ke alamat selain ‘/’ atau ‘/about’. Seharusnya, server akan merespons
sesuai dengan pesan yang sudah kita tetapkan.
URL: ‘/’
Method: GET
Mengembalikan “Ini adalah homepage”.
Method: <any> (selain GET)
Mengembalikan “Halaman tidak dapat diakses dengan <any> request”.
URL: ‘/about’
Method: GET
Mengembalikan “Halo! Ini adalah halaman about”.
Method: POST (dengan melampirkan data name pada body)
Mengembalikan “Halo, <name>! Ini adalah halaman about”.
Method: <any> (selain GET dan POST)
Mengembalikan “Halaman tidak dapat diakses dengan <any> request”.
URL: <any> (selain / dan /about)
Method: <any>
Mengembalikan “Halaman tidak ditemukan!”.
Sekarang, kita lanjut ke ketentuan lainnya. Mari selesaikan logika untuk url ‘/’
terlebih dahulu.
Berdasarkan ketentuan yang ada, url ‘/’ hanya dapat diakses menggunakan method GET
oleh client. Jika tidak, maka server akan mengembalikan pesan “Halaman tidak dapat
diakses dengan <any> request”.
Lanjut, kita berikan respons sesuai ketentuan pada masing-masing blok if dan else.
Setelah selesai, ayo coba lagi perubahan yang kita lakukan. Simpan perubahan pada
berkas server.js; jalankan ulang server dengan perintah npm run start; dan lakukan
permintaan ke alamat ‘/’ dengan method GET dan lainnya. Harusnya sudah berjalan
sesuai dengan ketentuan yah!
URL: ‘/’
Method: GET
Mengembalikan “Ini adalah homepage”.
Method: <any> (selain GET)
Mengembalikan “Halaman tidak dapat diakses dengan <any> request”.
URL: ‘/about’
Method: GET
Mengembalikan “Halo! Ini adalah halaman about”.
Method: POST (dengan melampirkan data name pada body)
Mengembalikan “Halo, <name>! Ini adalah halaman about”.
Method: <any> (selain GET dan POST)
Mengembalikan “Halaman tidak dapat diakses dengan <any> request”.
URL: <any> (selain / dan /about)
Method: <any>
Mengembalikan “Halaman tidak ditemukan!”.
Berdasarkan ketentuan yang ada, halaman /about dapat diakses oleh client dengan
menggunakan method GET dan POST. Selain kedua method tersebut, server akan
mengembalikan pesan “Halaman tidak dapat diakses menggunakan <any> request”.
Selanjutnya, lengkapi juga respons bila client menggunakan GET dan POST sesuai
dengan ketentuan.
Tips: Agar tidak menulis ulang seluruh kode stream, salinlah kode blok POST yang
diberikan komentar. Namun, sesuaikan nilai responsnya ya.
Setelah selesai kode pada blok ‘/about’ akan tampak seperti ini:
request.on('end', () => {
body = Buffer.concat(body).toString();
const {name} = JSON.parse(body);
response.end(`<h1>Halo, ${name}! Ini adalah halaman about</h1>`);
});
} else {
response.end(`<h1>Halaman tidak dapat diakses menggunakan ${method}
request</h1>`);
}
}
Sekarang kita coba yuk! Simpan perubahan pada berkas server.js; jalankan ulang
server dengan perintah npm run start; dan coba lakukan permintaan ke alamat
‘/about’ dengan method GET, POST, dan lainnya. Harusnya sudah berjalan sesuai
dengan ketentuan yah!
URL: ‘/’
Method: GET
Mengembalikan “Ini adalah homepage”.
Method: <any> (selain GET)
Mengembalikan “Halaman tidak dapat diakses dengan <any> request”.
URL: ‘/about’
Method: GET
Mengembalikan “Halo! Ini adalah halaman about”.
Method: POST (dengan melampirkan data name pada body)
Mengembalikan “Halo, <name>! Ini adalah halaman about”.
Method: <any> (selain GET dan POST)
Mengembalikan “Halaman tidak dapat diakses dengan <any> request”.
URL: <any> (selain / dan /about)
Method: <any>
Mengembalikan “Halaman tidak ditemukan!”.
Anda bisa merapikan kode saat ini dengan menghapus komentar kode yang sudah tidak
digunakan lagi. Sehingga, sekarang berkas server.js tampak seperti ini:
request.on('end', () => {
body = Buffer.concat(body).toString();
const { name } = JSON.parse(body);
response.end(`<h1>Halo, ${name}! Ini adalah halaman about</h1>`);
});
} else {
response.end(`<h1>Halaman tidak dapat diakses menggunakan ${method}
request</h1>`);
}
} else {
response.end('<h1>Halaman tidak ditemukan!</h1>');
}
};
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Response Status
Sejauh ini kita telah membahas banyak tentang request. Kita sudah mengenal dan
menggunakan method, url, body request, kemudian memberikan respons sesuai dengan
karakteristik request yang ada.
Meskipun kita sudah bisa membuat server merespons permintaan, tapi sebenarnya kita
belum belajar lebih dalam mengenai respons. Untuk itu, mari beranjak membahas lebih
detail mengenai parameter kedua dari fungsi request listener ini.
Seperti yang sudah Anda ketahui pada modul pengenalan back-end, respons yang dibawa
oleh server dibagi menjadi tiga bagian penting. Yang pertama status line, atau bisa
kita sebut response status; yang kedua response header; dan yang ketiga response
body. Kita bahas mulai dari response status dahulu yah.
Response status merupakan salah satu bagian dari respons yang berisikan tentang
informasi apakah sebuah request berhasil atau gagal dilakukan. Status yang
diberikan berupa kode (status code) dan pesan dari kode tersebut dalam bentuk teks
(status message).
Indikasi keberhasilan request client ditentukan oleh response status code yang
dikirim oleh server. Karena itu, tentu nilai status code tak bisa sembarang kita
tetapkan. Status code haruslah bernilai 3 digit angka dengan ketentuan berikut:
Fokus terhadap poin yang ditebalkan yah karena poin itu akan sering digunakan.
Silakan eksplorasi lebih detail mengenai status code pada halaman MDN mengenai HTTP
Status.
Pada Node.js, penetapan nilai status code pada response dilakukan melalui properti
response.statusCode.
Oh ya! Dari halaman MDN yang diberikan di atas, kita juga bisa melihat bahwa status
code selalu diiringi dengan status message. Contoh 200 Ok, 400 Bad Request, dan 404
Not Found. Melalui status message ini kita dan juga client bisa paham maksud dari
status kode.
Status message memiliki nilai standar sesuai dengan response code. Namun, kita bisa
mengubahnya bila diperlukan. Untuk mengubah status message, Anda bisa gunakan
properti response.statusMessage.
Ketahuilah bahwa Anda sebaiknya tidak mengubah statusMessage dari nilai default
bila tidak diperlukan. Walaupun hanya sekadar menerjemahkannya menjadi “Tidak
ditemukan”.
Hasilnya adalah:
Lihat informasi yang diberi warna kuning. Semua respons dari server berstatus 200
OK, ini membuat client mengira bahwa request berhasil dilakukan, tapi faktanya
tidak.
Ada beberapa respons yang seharusnya bisa diberikan status yang lebih relevan.
Seperti ketika client meminta resource yang tidak ditemukan (404 Not Found) atau
menggunakan method request yang tidak tepat (400 Bad Request).
Karena Anda saat ini sudah mengetahui cara mengubah status code pada respons, mari
kita perbaiki kesalahan ini.
Silakan buka kembali berkas server.js. Kemudian, hapus kode berikut dari fungsi
request listener:
response.statusCode = 200;
Sebagai gantinya, kita tuliskan nilai status code satu per satu sebelum perintah
response.end(). Tentu, sesuaikan nilai status code dengan kasus-kasus yang ada.
Contohnya, bila halaman tidak ditemukan, beri nilai 404 pada status code; bila
halaman tidak bisa diakses menggunakan method tertentu, beri nilai 400 pada status
code; sisanya, bila request berhasil dilakukan, beri nilai 200 pada status code.
Yuk kita eksekusi!
request.on('end', () => {
body = Buffer.concat(body).toString();
const { name } = JSON.parse(body);
response.statusCode = 200;
response.end(`<h1>Halo, ${name}! Ini adalah halaman about</h1>`);
});
} else {
response.statusCode = 400;
response.end(`<h1>Halaman tidak dapat diakses menggunakan ${method}
request</h1>`);
}
} else {
response.statusCode = 404;
response.end('<h1>Halaman tidak ditemukan!</h1>');
}
};
Simpan perubahan pada berkas server.js; jalankan ulang server dengan perintah npm
run start; dan coba lakukan lagi permintaan berikut menggunakan cURL:
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Response Header
Pada web server yang sudah kita buat, ia memberikan respons dengan format dokumen
HTML. Dokumen ini digunakan oleh browser dalam menampilkan website. Anda bisa
melihat ini ketika mengakses web server melalui browser.
Pada url http://localhost:5000 server akan mengembalikan pesan “Ini adalah
homepage” atau pada url http://localhost:5000/about server akan mengembalikan pesan
“Halo! Ini adalah halaman about”. Pesan yang ditampilkan tampak besar dan tebal
karena ia dibungkus oleh elemen heading HTML.
Sebenarnya, server bisa merespons dengan memberikan data dalam tipe (MIME types)
lain, seperti XML, JSON, gambar, atau sekadar teks biasa. Apa pun MIME types yang
digunakan, web server wajib memberi tahu pada client.
Caranya, lampirkan property ‘Content-Type’ dengan nilai MIME types yang disisipkan
pada header response. Untuk menyisipkan nilai pada header response, gunakanlah
method setHeader().
Silakan eksplorasi apa saja MIME types yang bisa diberikan pada header Content-Type
di halaman Common Types dari MDN ini.
Anda bisa menetapkan data pada header sebanyak yang diinginkan. Method setHeader()
menerima dua parameter, yakni nama properti dan nilai untuk headernya.
Jika Anda menetapkan header dengan properti yang tidak standar (lihat apa saja
standard properti pada header) atau Anda buat nama propertinya secara mandiri, maka
sangat disarankan untuk menambahkan huruf X di awal nama propertinya.
Ketahuilah juga bahwa penulisan properti header dituliskan secara Proper Case atau
setiap kata diawali dengan huruf kapital dan setiap katanya dipisahkan oleh tanda
garis (-).
Saat ini web server yang kita buat menampilkan format HTML dalam mengirimkan
respons ke client. Nah, pada latihan kali ini kita akan mengubah format HTML
menjadi format JSON. Selain itu, kita akan tambahkan properti X-Powered-By pada
header untuk memberitahu client teknologi server apa yang kita gunakan.
Buka kembali berkas server.js dan lihat kode apa yang sekiranya perlu kita ubah
untuk memberi tahu server bahwa kita akan menggunakan JSON untuk responnya?
response.setHeader('Content-Type', 'text/html');
Seperti yang sudah Anda ketahui, properti header ‘Content-Type’ berfungsi untuk
memberi tahu client seperti apa ia harus menampilkan data.
Contoh dengan nilai ‘text/html’, client khususnya browser akan menampilkan data
yang dikirim oleh respons akan di-render atau ditampilkan dalam bentuk HTML. Itulah
mengapa pesan respons tampak besar ketika melakukan request menggunakan browser.
Karena kita ingin mengubah Content-Type menjadi JSON, maka ubah text/html menjadi
application/json.
response.setHeader('Content-Type', 'application/json');
response.setHeader('Content-Type', 'application/json');
response.setHeader('X-Powered-By', 'NodeJS');
Simpan perubahan pada berkas server.js; jalankan ulang server dengan perintah npm
run start; dan coba lakukan lagi permintaan ke server menggunakan cURL.
Oh ya, karena server tidak lagi mengirimkan konten dalam bentuk HTML, maka browser
tidak akan lagi menampilkan dalam bentuk HTML. Coba buka http://localhost:5000
melalui browser. Sekarang konten HTML tidak lagi ter-render.
Dengan begitu, memberikan pesan dalam format HTML sudah tidak relevan lagi. Kita
akan mengubahnya menjadi format JSON. Tapi sabar, kita tak akan lakukan itu
sekarang. Kita pelajari dahulu lebih dalam bagaimana cara mengirimkan body respons
pada server.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Response Body
Header respons menampung informasi terkait respons yang diberikan oleh server.
Informasi dapat berupa status respons, MIME types, tanggal, atau informasi lainnya
yang mungkin dibutuhkan oleh client.
Walaupun kita bisa memberikan informasi apa pun, namun tidak semua informasi cocok
disimpan di header. Informasi pada header hanya sebagai metadata atau informasi
yang menjelaskan tentang sebuah data lain (data utama).
Selain header, HTTP respons juga membawa body (Anda mengetahui ini pada materi pola
komunikasi client dan server). Di dalam body inilah data utama (atau bisa kita
sebut konten) seharusnya disimpan.
Ketahuilah bahwa objek response yang berada pada parameter fungsi request listener
adalah instance dari http.serverResponse. Di mana ia merupakan WritableStream.
Masih ingat cara menulis data pada WritableStream? Nah, cara itulah yang digunakan
untuk menuliskan data pada body response.
Seperti objek WritableStream lainnya, untuk menuliskan data pada respons kita bisa
gunakan method response.write() dan diakhiri dengan method response.end().
Seperti yang sudah Anda ketahui juga, method end() pada WritableStream dapat
digunakan untuk menulis data terakhir sebelum proses penulisan diakhiri. Jadi,
untuk kasus di atas dapat dipersingkat penulisannya menjadi seperti ini.
Ketahuilah bahwa penting untuk menuliskan status dan header response sebelum Anda
menuliskan data pada body. Karena tidak masuk akal bila Anda sudah menuliskan body,
namun belum memberikan metadata terkait data apa yang hendak dikirim.
Pastikan Anda sudah tahu dan paham apa itu dan bagaimana penulisan JSON. Bila
tidak, silakan ulas kembali materi format request dan response.
Ketentuannya begini, setiap JSON yang akan kita kirimkan harus memiliki message.
Nilai properti message akan diisi dengan pesan yang sebelumnya kita berikan dalam
format HTML. Untuk lebih jelasnya, berikut contoh response body ketika client
meminta halaman yang tidak ditemukan.
{
"message": "Halaman tidak ditemukan!"
}
Kita ubah konten yang mudah dahulu yah, lebih tepatnya ketika client mengakses
halaman yang tidak ditemukan. Silakan ubah kode ini:
Menjadi:
response.end(JSON.stringify({
message: 'Halaman tidak ditemukan!',
}));
Karena response.end() menerima string (atau buffer), maka kita perlu mengubah objek
JavaScript menjadi JSON string menggunakan JSON.stringify().
Mari kita coba dulu perubahan yang ada. Simpan perubahan pada berkas server.js;
jalankan ulang server dengan perintah npm run start; dan coba lakukan permintaan ke
alamat selain ‘/’ atau ‘/about’. Seharusnya, server akan merespons konten dengan
format JSON.
Mantap! Silakan lanjut ubah format pesan untuk respons yang lain juga yah. Hingga
fungsi request listener pada server.js tampak seperti ini:
request.on('end', () => {
body = Buffer.concat(body).toString();
const { name } = JSON.parse(body);
response.statusCode = 200;
response.end(JSON.stringify({
message: `Halo, ${name}! Ini adalah halaman about`,
}));
});
} else {
response.statusCode = 400;
response.end(JSON.stringify({
message: `Halaman tidak dapat diakses menggunakan ${method},
request`
}));
}
} else {
response.statusCode = 404;
response.end(JSON.stringify({
message: 'Halaman tidak ditemukan!',
}));
}
};
Well done! Simpan perubahan pada berkas server.js; jalankan ulang server dengan
perintah npm run start; dan coba lakukan lagi permintaan ke server menggunakan
cURL. Server saat ini akan merespon dengan JSON sepenuhnya.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Ketika membuat web server menggunakan Node.js, mungkin sebagian Anda bertanya-
tanya, “Apakah tidak ada cara yang lebih efektif lagi untuk membuat server
menggunakan Node.js? Apakah harus sesulit itu? Haruskah fungsi request listener
menampung seluruh logika? Bisakah membuat request handler secara spesifik
berdasarkan url atau method? Bisakah kita mengatur kode agar lebih efektif dan
mudah dibaca?” Wah, tenang, tenang!
Sejatinya memang seperti itulah dasar pembuatan web server menggunakan Node.js.
Node.js tidak menyediakan cara mudah bahkan untuk melakukan hal-hal umum yang
biasanya dilakukan ketika membuat web server.
Lantas, apakah cara yang sudah kita pelajari sejauh ini bisa digunakan untuk
pengembangan web server yang kompleks, seperti membangun REST API?
Tentu bisa, namun akan sulit. Sulit untuk dipelihara, sulit untuk dipahami, dan
juga sulit untuk dikembangkan. Tapi jangan berkecil hati dulu, bila mengalami
kesulitan, kita harus cari bala bantuan. Siapa yang bisa menolong kita saat ini?
Jawabannya adalah tentu developer lain! Masalah yang kita hadapi saat ini sudah
banyak dialami oleh Node.js developer lainnya.
Karena itu, baik developer, organisasi, atau bahkan instansi berlomba-lomba membuat
solusi dengan membangun framework yang dapat membantu membuat web server dengan
Node.js lebih cepat dan lebih mudah dikembangkan. Dengan bekal dasar yang sudah
dimiliki saat ini, Anda berhak untuk mengeksplorasi dan menggunakan framework yang
ada.
Tapi sabar dulu, sebelum menggunakannya, alangkah lebih baik kita pahami dahulu
lebih dalam apa itu Web Framework.
Web Framework menyediakan sekumpulan tools dan library yang dapat menyederhanakan
hal-hal yang sering dilakukan dalam pengembangan web, seperti pembuatan server,
routing, menangani permintaan, interaksi dengan database, otorisasi, hingga
meningkatkan ketahanan web dari serangan luar.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Expressjs merupakan web framework tertua dan terpopuler di Node.js saat ini.
Framework ini sangat ringan, mudah diintegrasikan dengan aplikasi web front-end,
dan penulisan kodenya tidak jauh beda dengan Node.js native.
Kelemahan Hapi adalah abstraksinya yang terlalu jauh dari Node.js native. Kita
perlu belajar secara dalam, untuk menguasai framework ini.
Penggunaan framework menjadi pilihan personal. Salah satu faktornya adalah kasus
yang hendak Anda hadapi. Ketika ingin membangun server yang sederhana, katakanlah
untuk mendukung aplikasi front-end di-render di sisi server, express adalah pilihan
yang tepat.
Namun, bila Anda ingin membangun web server yang kompleks tanpa membutuhkan effort
yang besar, Hapi adalah pilihan yang tepat.
Kita akan membangun web server dengan arsitektur REST yang kompleks ke depannya.
Agar Anda selalu “Hapi” ketika mengikuti alur belajar, kita akan gunakan Hapi dalam
membangun web server.
Ketahuilah bahwa Hapi memiliki environment yang cukup luas. Kelas ini tidak akan
mengajarkan secara dalam tentang API yang ada di Hapi, melainkan hanya fitur-fitur
yang menjadi dasar pembuatan REST API. Jadi, bila Anda ingin mendalami terkait
framework Hapi, sempatkan waktu untuk eksplorasi di dokumentasi Hapi yang
disediakan yah.
Pada materi selanjutnya kita akan belajar dasar-dasar dari Hapi sambil coba membuat
ulang web server dengan spesifikasi yang sama seperti yang kita lakukan pada
latihan sebelumnya.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Menyiapkan Project
Mari kita awali dengan membuat proyek baru. Silakan buat folder di C -> javascript-
projects (Windows) atau home -> javascript-projects (Linux dan macOS) dengan nama
“hapi-web-server”.
Buka folder menggunakan VSCode, kemudian inisialisasi proyek pada Terminal dengan
menggunakan perintah:
Lanjut, kita atur NPM runner pada package.json menjadi seperti ini:
"scripts": {
"start": "node server.js"
},
Lalu, buatlah berkas JavaScript baru dengan nama server.js. Kemudian, tuliskan kode
berikut:
server.js
console.log('Halo, kita akan belajar membuat server menggunakan Hapi');
Simpan perubahan pada berkas server.js dan coba jalankan perintah berikut pada
Terminal:
Bila Anda melihat pesan “Halo, kita akan belajar membuat server menggunakan Hapi”,
maka proyek telah siap digunakan.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Setelah proses pemasangan berhasil, barulah kita bisa menggunakan modul tersebut.
Pembuatan server menggunakan Hapi memiliki struktur kode yang berbeda dari cara
asli. Berikut adalah dasar kode dalam membuat HTTP server pada Hapi:
await server.start();
console.log(`Server berjalan pada ${server.info.uri}`);
}
init();
Setelah server berhasil berjalan, Anda bisa melihat alamat lengkap dan port di mana
server dijalankan melalui properti server.info.uri.
Pertama, kita pasang dahulu modul @hapi/hapi dengan cara eksekusi perintah berikut
pada Terminal proyek:
"dependencies": {
"@hapi/hapi": "^20.1.0"
}
Proses instalasi modul selesai! Kita lanjut ke penulisan kode pada berkas
server.js.
Silakan hapus kode yang ada pada server.js, lalu ganti dengan kode dasar dalam
pembuatan server menggunakan Hapi berikut ini:
await server.start();
console.log(`Server berjalan pada ${server.info.uri}`);
};
init();
Simpan perubahan pada berkas server.js. Kemudian jalankan perintah npm run start
pada Terminal. Jika server berhasil dijalankan, maka Anda akan melihat pesan
‘Server berjalan pada http://localhost:5000’.
Yups! Hapi secara default akan mengembalikan response “Not Found” ketika tidak ada
request handler yang menangani permintaannya. Hal ini tentu lebih baik daripada
permintaannya dibiarkan begitu saja, bukan?
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Routing pada Hapi tidak dilakukan di dalam request handler seperti cara native.
Namun, ia memanfaatkan objek route configuration yang disimpan pada method
server.route(). Lihat kode yang ditebalkan yah.
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Hello World!';
}
});
await server.start();
console.log(`Server berjalan pada ${server.info.uri}`);
};
Tunggu, request handler dituliskan di dalam route configuration? Yap benar! Handler
pada Hapi dipisahkan berdasarkan route yang ada. Setiap spesifikasi route memiliki
handler-nya masing-masing. Dengan begitu, tentu kode akan lebih mudah dikelola.
Anda bisa mengatakan selamat tinggal pada if else yang bersarang.
Lalu, bagaimana cara menetapkan lebih dari satu route configuration dalam method
server.route()? Mudah! Sebenarnya, server.route() selain dapat menerima route
configuration, ia juga dapat menerima array dari route configuration. Jadi, Anda
bisa secara mudah menentukan banyak spesifikasi route dengan seperti ini:
server.route([
{
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Homepage';
},
},
{
method: 'GET',
path: '/about',
handler: (request, h) => {
return 'About Page';
},
},
]);
await server.start();
console.log(`Server berjalan pada ${server.info.uri}`);
};
routes.js:
const routes = [
{
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Homepage';
},
},
{
method: 'GET',
path: '/about',
handler: (request, h) => {
return 'About page';
},
},
];
module.exports = routes;
server.js:
const Hapi = require('@hapi/hapi');
const routes = require('./routes');
server.route(routes);
await server.start();
console.log(`Server berjalan pada ${server.info.uri}`);
};
init();
server.route(routes);
await server.start();
console.log(`Server berjalan pada ${server.info.uri}`);
};
init();
Latihan Routing
Setelah mengetahui cara menspesifikasikan route pada Hapi, sekarang saatnya kita
terapkan apa yang sudah kita ketahui pada web server yang sudah dibuat sebelumnya.
Pada latihan kali ini, kita akan membuat routes configuration dengan ketentuan
berikut:
URL: ‘/’
Method: GET
Mengembalikan pesan “Homepage”.
Method: <any> (selain method GET)
Mengembalikan pesan “Halaman tidak dapat diakses dengan method tersebut”.
URL: ‘/about’
Method: GET
Mengembalikan pesan “About page”.
Method: <any> (selain method GET)
Mengembalikan pesan “Halaman tidak dapat diakses dengan method tersebut”.
URL: <any> (selain “/’ dan “/about”)
Method: <any>
Mengembalikan pesan “Halaman tidak ditemukan”.
Yuk mulai!
Agar kode lebih terkelompok, tulis route configuration pada berkas JavaScript
terpisah. Silakan buat berkas JavaScript baru pada proyek hapi-web-server dengan
nama “routes.js”. Kemudian, tuliskan kumpulan routes configuration dalam bentuk
array sesuai dengan ketentuan.
const routes = [
{
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Homepage';
},
},
{
method: '*',
path: '/',
handler: (request, h) => {
return 'Halaman tidak dapat diakses dengan method tersebut';
},
},
{
method: 'GET',
path: '/about',
handler: (request, h) => {
return 'About page';
},
},
{
method: '*',
path: '/about',
handler: (request, h) => {
return 'Halaman tidak dapat diakses dengan method';
},
},
{
method: '*',
path: '/{any*}',
handler: (request, h) => {
return 'Halaman tidak ditemukan';
},
},
];
module.exports = routes;
Tunggu, sepertinya ada beberapa hal baru yang belum Anda ketahui. Mari kita bedah
kode yang ditebalkan yah.
Anda bisa lihat beberapa properti method memiliki nilai ‘*’, itu artinya route
dapat diakses menggunakan seluruh method yang ada pada HTTP.
Kemudian nilai ‘/{any*}’ pada route paling akhir, ini berfungsi untuk menangani
permintaan masuk pada path yang belum Anda tentukan. Ini merupakan salah satu
teknik dalam menetapkan routing yang dinamis menggunakan Hapi.
Namun, routing dengan nilai dinamis seperti itu akan kalah kuatnya dengan nilai
yang ditetapkan secara spesifik. Contohnya bila array route configuration memiliki
nilai seperti ini:
const routes = [
{
method: '*',
path: '/',
handler: (request, h) => {
return 'Halaman tidak dapat diakses dengan method tersebut';
},
},
{
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Homepage';
},
},
];
server.route(routes);
await server.start();
console.log(`Server berjalan pada ${server.info.uri}`);
};
init();
Simpan seluruh perubahan yang ada baik pada berkas routes.js dan server.js;
jalankan ulang server dengan perintah npm run start; dan coba lakukan permintaan ke
server. Seharusnya server sudah bisa merespons sesuai dengan yang diharapkan.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Path Parameter
Mari kita berbicara mengenai teknik routing lebih lanjut. Path dalam routing bisa
dikatakan sebagai alamat yang digunakan client untuk melakukan permintaan ke
server. Alamat atau path yang dibuat biasanya merupakan teks verbal yang dapat
dimengerti oleh client. Tak jarang hanya dengan membaca path dari sebuah tautan
kita langsung mengerti apa yang client minta kepada server.
Twitter dan Github menggunakan pendekatan yang sama dalam menampilkan halaman
profil. Mereka memanfaatkan username sebagai bagian dari path untuk melakukan
permintaan ke server. Terbayang tidak sih bagaimana mereka melakukannya? Di saat
mereka memiliki pengguna yang banyak, apakah mereka menetapkan route secara satu
per satu berdasarkan username untuk setiap penggunanya? Tentu tidak!
Untuk melakukan hal tersebut, Twitter dan Github menggunakan teknik path parameter.
Di Hapi Framework teknik tersebut sangat mudah untuk diterapkan. Cukup dengan
membungkus path dengan tanda { }. Sebagai contoh:
server.route({
method: 'GET',
path: '/users/{username}',
handler: (request, h) => {
const { username } = request.params;
return `Hello, ${username}!`;
},
});
Seperti yang Anda lihat di atas, pada properti path terdapat bagian path yang
ditulis {username}. Itu berarti, server memberikan bagian teks tersebut untuk
client manfaatkan sebagai parameter.
Nantinya parameter ini akan disimpan sebagai properti pada request.params yang
dimiliki handler dengan nama sesuai yang Anda tetapkan (username). Sebagai contoh,
bila Anda melakukan permintaan ke server dengan alamat ‘/users/harry’, maka server
akan menanggapi dengan ‘Hello, harry!’.
Pada contoh kode di atas, nilai path parameter wajib diisi oleh client. Bila client
mengabaikannya dengan melakukan permintaan pada alamat ‘/users’, maka server akan
mengalami eror.
Tapi tenang, pada Hapi Anda dapat membuat path parameter bersifat opsional. Caranya
dengan menambahkan tanda “?” di akhir nama parameternya. Berikut contoh yang sama
namun dengan implementasi opsional path parameter:
server.route({
method: 'GET',
path: '/users/{username?}',
handler: (request, h) => {
const { username = 'stranger' } = request.params;
return `Hello, ${username}!`;
},
});
Anda bisa menetapkan lebih dari satu path parameter. Namun, penting untuk Anda
ketahui bahwa optional path parameter hanya dapat digunakan di akhir bagian path
saja. Artinya, jika Anda menetapkan optional path di tengah-tengah path parameter
lain contohnya /{one?}/{two}, maka path ini dianggap tidak valid oleh Hapi.
Pada latihan kali ini, kita akan membuat route baru dengan nilai path
/hello/{name?}. Bila client melampirkan nilai path parameter, server harus
mengembalikan dengan pesan “Hello, name!”. Namun bila tidak, server harus
mengembalikan dengan nilai
“Hello, stranger!”. Sudah paham? Yuk kita mulai!
Buka berkas routes.js dan tambahkan route baru seperti ini (lihat kode yang
ditebalkan).
const routes = [
{
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Homepage';
},
},
{
method: '*',
path: '/',
handler: (request, h) => {
return 'Halaman tidak dapat diakses dengan method tersebut';
},
},
{
method: 'GET',
path: '/about',
handler: (request, h) => {
return 'About page';
},
},
{
method: '*',
path: '/about',
handler: (request, h) => {
return 'Halaman tidak dapat diakses dengan method';
},
},
{
method: 'GET',
path: '/hello/{name?}',
handler: (request, h) => {
}
},
{
method: '*',
path: '/{any*}',
handler: (request, h) => {
return 'Halaman tidak ditemukan';
},
},
];
module.exports = routes;
Lalu, kembalikan fungsi handler dengan pesan sesuai dengan ketentuan yah. Sehingga,
fungsi handler tampak seperti ini:
{
method: 'GET',
path: '/hello/{name?}',
handler: (request, h) => {
const { name = "stranger" } = request.params;
return `Hello, ${name}!`;
},
},
Simpan perubahan pada berkas routes.js; coba jalankan kembali server dengan
perintah npm run start; dan lakukanlah permintaan melalui curl atau browser pada
path /hello/dicoding dan /hello.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Query Parameters
Selain path parameter, terdapat cara lain yang sering digunakan dalam mengirimkan
data melalui URL, yakni dengan query parameter. Teknik ini umum digunakan pada
permintaan yang membutuhkan kueri dari client, contohnya seperti pencarian dan
filter data.
localhost:5000?name=harry&location=bali
Contoh di atas memiliki dua query parameter. Yang pertama adalah name=harry dan
location=bali. Di Hapi, Anda bisa mendapatkan nilai dari query parameter melalui
request.query.
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
const { name, location } = request.query;
return `Hello, ${name} from ${location}`;
},
});
Pada latihan kali ini kita akan menambahkan dukungan bahasa terhadap path
/hello/{name} yang sudah kita buat. Bila path tersebut memiliki kueri lang dengan
nilai id, maka server akan menanggapi dengan pesan “Hai, {name}!”. Selain itu,
biarkan pesan tetap sama seperti latihan sebelumnya. Ayo kita mulai!
Buka berkas routes.js dan pada fungsi handler GET /hello/ {name} dapatkan nilai
kueri lang melalui properti request.query.
{
method: 'GET',
path: '/hello/{name?}',
handler: (request, h) => {
const { name = "stranger" } = request.params;
const { lang } = request.query;
Lalu, sesuaikan pesan kembalian handler berdasarkan evaluasi nilai lang seperti
ini:
{
method: 'GET',
path: '/hello/{name?}',
handler: (request, h) => {
const { name = "stranger" } = request.params;
const { lang } = request.query;
Simpan perubahan pada berkas routes.js; jalankan kembali server dengan perintah npm
run start; dan lakukan permintaan pada path /hello/dicoding dengan dan tanpa
melampirkan kueri lang=id.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Body/Payload Request
Ketika menggunakan Node.js, untuk mendapatkan data pada body request--meskipun
datanya hanya sebatas teks--kita harus berurusan dengan Readable Stream. Di mana
untuk mendapatkan data melalui stream tak semudah seperti kita menginisialisasikan
sebuah nilai pada variabel.
Good News! Ketika menggunakan Hapi, Anda tidak lagi berurusan dengan stream untuk
mendapatkan datanya. Di balik layar, Hapi secara default akan mengubah payload JSON
menjadi objek JavaScript. Dengan begitu, Anda tak lagi berurusan dengan
JSON.parse()!
Kapan pun client mengirimkan payload berupa JSON, payload tersebut dapat diakses
pada route handler melalui properti request.payload. Contohnya seperti ini:
server.route({
method: 'POST',
path: '/login',
handler: (request, h) => {
const { username, password } = request.payload;
return `Welcome ${username}!`;
},
});
Pada contoh di atas, handler menerima payload melalui request.payload. Dalam kasus
tersebut, client mengirimkan data login dengan struktur:
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Response Toolkit
Fungsi handler pada Hapi memiliki dua parameters, yakni request dan h.
Sebagaimana yang sudah banyak kita bahas sebelumnya, request parameter merupakan
objek yang menampung detail dari permintaan client, seperti path dan query
parameters, payload, headers, dan sebagainya. Ada baiknya Anda eksplorasi secara
lebih dalam apa fungsi dari parameter request pada referensi API Hapi.
Parameter yang kedua yaitu h (huruf inisial Hapi). Parameter ini merupakan response
toolkit di mana ia adalah objek yang menampung banyak sekali method yang digunakan
untuk menanggapi sebuah permintaan client. Objek ini serupa dengan objek response
pada request handler ketika kita menggunakan Node.js native.
Seperti yang sudah Anda lihat pada contoh dan latihan sebelumnya, jika hanya ingin
mengembalikan nilai pada sebuah permintaan yang datang, di Hapi Anda bisa secara
langsung mengembalikan nilai dalam bentuk teks, teks HTML, JSON, steam, atau bahkan
promise.
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return `Homepage`;
},
});
Jika kita dapat mengembalikan permintaan secara singkat, lalu apa fungsi dari h?
Kapan kita membutuhkannya?
Bila kasusnya sederhana seperti di atas, memang lebih baik Anda langsung kembalikan
dengan nilai secara eksplisit. Namun, ketahuilah bahwa dengan cara tersebut status
response selalu bernilai 200 OK. Ketika Anda butuh mengubah nilai status response,
di situlah Anda membutuhkan parameter h.
server.route({
method: 'POST',
path: '/user',
handler: (request, h) => {
return h.response('created').code(201);
},
});
Fungsi handler harus selalu mengembalikan sebuah nilai, bila Anda menggunakan h
ketika menangani permintaan, maka kembalikanlah dengan nilai h.response(). Anda
bisa lihat contoh kode di atas.
Parameter h tidak hanya berfungsi untuk menetapkan status kode respons. Melalui h,
Anda juga bisa menetapkan header response, content type, content length, dan masih
banyak lagi.
// Detailed notation
const handler = (request, h) => {
const response = h.response('success');
response.type('text/plain');
response.header('X-Custom', 'some-value');
return response;
};
// Chained notation
const handler = (request, h) => {
return h.response('success')
.type('text/plain')
.header('X-Custom', 'some-value');
};
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Masih ingat tentang arsitektur REST yang sudah dibahas pada modul pengenalan? Nah,
pada materi kali ini kita akan membuat RESTful API mulai dari awal. Pada akhirnya,
Anda diharapkan bisa membuat server dari aplikasi catatan sederhana seperti pada
video ini.
Catatan
Jika Anda akses aplikasi pada tautan di atas, aplikasi tersebut tidak akan
berfungsi karena belum ada implementasi dari sisi Back-End. Tugas kita adalah
membuat aplikasi Back-End dan membuat aplikasi catatan berfungsi dengan baik.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Menyiapkan Proyek
Seperti biasa, mari kita awali dengan membuat proyek baru. Sudah tahu kan caranya?
Jika belum, silakan ikuti langkah berikut:
Nodemon
Tools pertama adalah nodemon, ia bisa dikatakan wajib digunakan selama proses
pengembangan. Pasalnya, dengan tools ini kita tak perlu menjalankan ulang server
ketika terjadi perubahan pada berkas JavaScript. Nodemon akan mendeteksi perubahan
kode JavaScript dan mengeksekusi ulang secara otomatis.
Untuk memastikan nodemon terpasang pada proyek, Anda bisa memeriksa berkas
package.json, lebih tepatnya di objek devDependencies.
package.json
{
"name": "notes-app-back-end",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"nodemon": "^2.0.7"
}
}
Bila package berhasil terpasang, Anda bisa lihat properti nodemon dan versi yang
digunakan di sana.
Untuk mencoba nodemon, silakan buat berkas JavaScript dulu pada proyek kita dan
berikan nama “server.js”. Di dalamnya, tulislah kode berikut:
server.js
console.log('Hallo kita akan membuat RESTful API');
Kemudian di dalam package.json, buat npm runner script baru untuk menjalankan
server.js menggunakan nodemon.
"scripts": {
"start": "nodemon server.js"
},
Anda bisa menghapus runner script test karena saat ini tidak digunakan.
Nodemon berhasil mengeksekusi server.js dan akan terus mengawasi perubahan kode
yang ada. Yuhu! Kini Anda tidak perlu menjalankan ulang perintah npm run start
setiap terjadi perubahan pada berkas JavaScript. Cukup simpan perubahannya dan
nodemon akan menjalankan ulang secara otomatis.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
ESLint
Tools yang kedua adalah ESLint, ia dapat membantu atau membimbing Anda untuk selalu
menuliskan kode JavaScript dengan gaya yang konsisten. Seperti yang Anda tahu,
JavaScript tidak memiliki aturan yang baku untuk gaya penulisan kode, bahkan
penggunaan semicolon. Karena itu, terkadang kita jadi tidak konsisten dalam
menuliskannya.
ESLint dapat mengevaluasi kode yang dituliskan berdasarkan aturan yang Anda
terapkan. Anda bisa menuliskan aturannya secara mandiri atau menggunakan gaya
penulisan yang sudah ada seperti AirBnb JavaScript Code Style, Google JavaScript
Code Style, dan StandardJS Code Style. Kami sarankan Anda untuk mengikuti salah
satu code style yang ada. Mengapa begitu? Jawabannya karena code style tersebut
sudah banyak digunakan oleh JavaScript Developer di luar sana.
Sama seperti package nodemon, setelah berhasil terpasang, package eslint akan
muncul di package.json lebih tepatnya pada devDependencies.
{
"name": "notes-app-back-end",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"eslint": "^7.19.0",
"nodemon": "^2.0.7"
}
}
Sebelum digunakan, Anda perlu melakukan konfigurasi terlebih dahulu. Caranya dengan
menggunakan perintah berikut di Terminal proyek.
Kemudian Anda akan diberikan beberapa pertanyaan, silakan jawab pertanyaan yang ada
dengan jawaban berikut:
How would you like to use ESLint? -> To check, find problems, and enforce code
style.
What type of modules does your project use? -> CommonJS (require/exports).
Which framework did you use? -> None of these.
Does your project use TypeScript? -> N.
Where does your code run? -> Node (pilih menggunakan spasi).
How would you like to define a style for your project? -> Use a popular style
guide.
Which style guide do you want to follow? -> (Anda bebas memilih, sebagai contoh
pilih AirBnB).
What format do you want your config file to be in? -> JSON.
Would you like to …… (seluruh pertanyaan selanjutnya) -> Y.
Setelah menjawab seluruh pertanyaan yang diberikan, maka akan terbentuk berkas
konfigurasi eslint dengan nama .eslintrc.json.
Setelah membuat konfigurasi ESLint, selanjutnya kita gunakan ESLint untuk memeriksa
kode JavaScript yang ada pada proyek. Namun sebelum itu, kita perlu menambahkan npm
runner berikut di dalam berkas package.json:
"scripts": {
"start": "nodemon server.js",
"lint": "eslint ./"
},
Jalankan perintah npm run lint pada Terminal proyek, lalu perhatikan hasilnya.
Pada Terminal, kita dapat melihat terdapat eror dan warning (bila Anda menggunakan
AirBnB code style). Seperti inilah fungsi dari ESLint, ia akan memberi tahu alasan
dan letak kesalahan dalam penulisan kode. Tiap eror yang tampil, itu menandakan
adanya penulisan kode yang tidak sesuai dengan style guide yang sudah kita
tetapkan. Melalui ESLint ini, kita dapat mencari letak kesalahan secara akurat dan
cepat.
ESLint dapat diintegrasikan dengan berbagai text editor, termasuk VSCode. Untuk
mengaktifkan integrasi, Anda bisa menggunakan ekstensi ESLint untuk Visual Studio
Code. Bagaimana cara mengunduh dan memasangnya? Mudah saja, silakan pilih menu
extensions.
Sekarang, mari kita kembali ke berkas server.js, di sana Anda akan melihat tanda
kuning pada kode console.
Untuk pengguna Windows, ekstensi ESLint belum sepenuhnya diaktifkan. Anda perlu
mengizinkan ekstensi ESLint berjalan melalui icon ‘Lampu’ yang muncul ketika Anda
mengarahkan kursor ke kode console.
Tekan ikon lampu tersebut, kemudian pilih opsi ESLint: Manage Library Execution.
Catatan: Jika Manage Library Execution tidak muncul pada VSCode Anda, itu berarti
ESLint extensions sudah dapat digunakan. Anda bisa abaikan langkah tersebut.
Pilih “Allow Everywhere” pada pop-up yang muncul. Kemudian, tutup dan buka ulang
proyek menggunakan VSCode.
Penggunaan console dianggap sebuah warning ketika Anda menerapkan AirBnB code
style. ESLint membantu menyoroti hal tersebut.
Agar sinkron dengan gaya penulisan di ESLint, Anda bisa mengatur indentasi dan line
spacing di VSCode sesuai dengan style guide yang digunakan pada ESLint. Pengaturan
tersebut berada di bottom bar VSCode.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Kriteria Proyek
Seperti yang sudah Anda ketahui, kita akan membangun RESTful API untuk aplikasi
catatan sederhana. Di mana aplikasi tersebut berfungsi untuk menyimpan (create),
melihat (read), mengubah (update), dan menghapus (delete) catatan. Fungsionalitas
ini dikenal sebagai operasi CRUD.
Dari segi Front-End (client), kami telah membuat aplikasi web-nya. Kami juga telah
men-deploy aplikasi tersebut sehingga Anda dapat mengaksesnya melalui tautan ini:
http://notesapp-v1.dicodingacademy.com/.
Namun ketika Anda mengaksesnya, aplikasi tersebut belum bisa digunakan. Anda tidak
bisa melihat, dan menambahkan catatan apapun. Tetapi percayalah, aplikasi tersebut
akan berfungsi dengan baik ketika Anda sudah membuat RESTful API sesuai dengan
kriteria yang dibutuhkan. Lantas apa saja kriterianya?
Berikut struktur dari objek catatan yang perlu disimpan oleh server:
{
id: string,
title: string,
createdAt: string,
updatedAt: string,
tags: array of string,
body: string,
},
{
id: 'notes-V1StGXR8_Z5jdHi6B-myT',
title: 'Sejarah JavaScript',
createdAt: '2020-12-23T23:00:09.686Z',
updatedAt: '2020-12-23T23:00:09.686Z',
tags: ['NodeJS', 'JavaScript'],
body: 'JavaScript pertama kali dikembangkan oleh Brendan Eich dari Netscape di
bawah nama Mocha, yang nantinya namanya diganti menjadi LiveScript, dan akhirnya
menjadi JavaScript. Navigator sebelumnya telah mendukung Java untuk lebih bisa
dimanfaatkan para pemrogram yang non-Java.',
},
Agar web server dapat menyimpan catatan melalui aplikasi client, web server harus
menyediakan route dengan path ‘/notes’ dan method POST.
Dalam menyimpan atau menambahkan notes, client akan mengirimkan permintaan ke path
dan method tersebut dengan membawa data JSON berikut pada request body:
{
"title": "Judul Catatan",
"tags": ["Tag 1", "Tag 2"],
"body": "Konten catatan"
}
Untuk properti id, createdAt, dan updatedAt harus diolah di sisi server, jadi
client tidak akan mengirimkan itu. Server harus memastikan properti id selalu unik.
Jika permintaan client berhasil dilakukan, respons dari server harus memiliki
status code 201 (created) dan mengembalikan data dalam bentuk JSON dengan format
berikut:
{
"status": "success",
"message": "Catatan berhasil ditambahkan",
"data": {
"noteId": "V09YExygSUYogwWJ"
}
}
Nilai dari properti noteId diambil dari properti id yang dibuat secara unik.
Bila permintaan gagal dilakukan, berikan status code 500 dan kembalikan dengan data
JSON dengan format berikut:
{
"status": "error",
"message": "Catatan gagal untuk ditambahkan"
}
Ketika client melakukan permintaan pada path ‘/notes’ dengan method ‘GET’, maka
server harus mengembalikan status code 200 (ok) serta seluruh data notes dalam
bentuk array menggunakan JSON. Contohnya seperti ini:
{
"status": "success",
"data": {
"notes": [
{
"id":"notes-V1StGXR8_Z5jdHi6B-myT",
"title":"Catatan 1",
"createdAt":"2020-12-23T23:00:09.686Z",
"updatedAt":"2020-12-23T23:00:09.686Z",
"tags":[
"Tag 1",
"Tag 2"
],
"body":"Isi dari catatan 1"
},
{
"id":"notes-V1StGXR8_98apmLk3mm1",
"title":"Catatan 2",
"createdAt":"2020-12-23T23:00:09.686Z",
"updatedAt":"2020-12-23T23:00:09.686Z",
"tags":[
"Tag 1",
"Tag 2"
],
"body":"Isi dari catatan 2"
}
]
}
}
Jika belum ada catatan satu pun pada array, server bisa mengembalikan data notes
dengan nilai array kosong seperti ini:
{
"status": "success",
"data": {
"notes": []
}
}
Selain itu, client juga bisa melakukan permintaan untuk mendapatkan catatan secara
spesifik menggunakan id melalui path ‘/notes/{id}’ dengan method ‘GET’. Server
harus mengembalikan status code 200 (ok) serta nilai satu objek catatan dalam
bentuk JSON seperti berikut:
{
"status": "success",
"data": {
"note": {
"id":"notes-V1StGXR8_Z5jdHi6B-myT",
"title":"Catatan 1",
"createdAt":"2020-12-23T23:00:09.686Z",
"updatedAt":"2020-12-23T23:00:09.686Z",
"tags":[
"Tag 1",
"Tag 2"
],
"body":"Isi dari catatan 1"
}
}
}
Bila client melampirkan id catatan yang tidak ditemukan, server harus merespons
dengan status code 404, dan data dalam bentuk JSON seperti ini:
{
"status": "fail",
"message": "Catatan tidak ditemukan"
}
{
"title":"Judul Catatan Revisi",
"tags":[
"Tag 1",
"Tag 2"
],
"body":"Konten catatan"
}
Jika perubahan data berhasil dilakukan, server harus menanggapi dengan status code
200 (ok) dan membawa data JSON objek berikut pada body respons.
{
"status": "success",
"message": "Catatan berhasil diperbaharui"
}
Perubahan data catatan harus disimpan ke catatan yang sesuai dengan id yang
digunakan pada path parameter. Bila id catatan tidak ditemukan, maka server harus
merespons dengan status code 404 (not found) dan data JSON seperti ini:
{
"status": "fail",
"message": "Gagal memperbarui catatan. Id catatan tidak ditemukan"
}
{
"status": "success",
"message": "Catatan berhasil dihapus"
}
Catatan yang dihapus harus sesuai dengan id catatan yang digunakan client pada path
parameter. Bila id catatan tidak ditemukan, maka server harus mengembalikan respons
dengan status code 404 dan membawa data JSON berikut:
{
"status": "fail",
"message": "Catatan gagal dihapus. Id catatan tidak ditemukan"
}
Bagaimana, sudah jelas? Itulah kriteria yang perlu dipenuhi oleh kita dalam
mengembangkan RESTful API nanti. Untuk memastikan apakah web server yang dibuat
sudah bekerja sesuai dengan kriteria, Anda perlu mencoba menggunakan aplikasi
client yang dihubungkan dengan web server. Bagaimana caranya? Kita akan bahas itu
nanti yah.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Struktur Proyek
Sebelum praktik langsung, ada baiknya untuk menyusun struktur proyek terlebih
dahulu agar pengembangan mudah dilakukan.
Pada pengembangan web server kali ini, kita tidak ingin semua kode dituliskan dalam
satu berkas saja sebab itu akan membuat kode menjadi semrawut, susah dibaca,
apalagi dipelihara. Karena Anda sudah belajar teknik modularisasi pada Node.js,
tentu tak ada masalah untuk memisahkan kode JavaScript menjadi beberapa berkas.
Kami memegang prinsip single responsibility approach. Artinya, kita gunakan satu
berkas JavaScript untuk satu tujuan saja. Nah, di proyek kali ini, kita akan
membuat setidaknya empat buah berkas JavaScript. Apa saja berkas dan kode yang
dituliskan di dalamnya? Mari kita rincikan.
server.js : Memuat kode untuk membuat, mengonfigurasi, dan menjalankan server HTTP
menggunakan Hapi.
routes.js : Memuat kode konfigurasi routing server seperti menentukan path, method,
dan handler yang digunakan.
handler.js : Memuat seluruh fungsi-fungsi handler yang digunakan pada berkas
routes.
notes.js : Memuat data notes yang disimpan dalam bentuk array objek.
Semua berkas JavaScript yang kita buat akan disimpan di dalam folder src. Hal ini
bertujuan agar terpisah dari berkas konfigurasi proyek seperti .eslintrc.json,
package.json, package-lock.json, dan node_modules.
notes-app-back-end
├── node_modules
├── src
│ ├── handler.js
│ ├── notes.js
│ ├── routes.js
│ └── server.js
├── .eslintrc.json
├── package-lock.json
└── package.json
Yuk, kita langsung buat saja folder src beserta berkas JavaScript yang dibutuhkan
di dalamnya. Untuk berkas server.js, Anda tidak perlu membuat baru, cukup pindahkan
berkas lama ke dalam folder src ya.
Karena berkas server.js sekarang berada di dalam folder src, jangan lupa ubah
alamat berkas tersebut pada npm runner script di berkas package.json. Silakan buka
berkas tersebut dan sesuaikan nilai di dalam scripts menjadi seperti ini:
"scripts": {
"start": "nodemon ./src/server.js",
"lint": "eslint ./src"
},
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Lanjut, buka berkas server.js dan ganti kode yang ada dengan kode dalam membuat
server menggunakan Hapi seperti pada latihan sebelumnya.
await server.start();
console.log(`Server berjalan pada ${server.info.uri}`);
};
init();
Catatan: Untuk pengguna MacOS, kami menyarankan untuk tidak menggunakan port 5000,
karena versi MacOS Monterey, port tersebut sudah digunakan atau dipesan untuk
layanan AirPlay Receiver. Anda bisa mengubah nilai port yang aman untuk digunakan
(Contoh, > 8000).
Anda sudah familier dengan kodenya kan? Silakan simpan perubahan kode pada berkas
server.js, lalu jalankan server dengan nodemon melalui perintah npm run start.
Biarkan nodemon tetap berjalan agar bila terjadi perubahan kode, kita tidak perlu
menjalankan ulang server.
Silakan buka browser dan coba akses url http://localhost:5000. Jika pada browser
tampak seperti ini:
Sampai di sini Anda sudah bisa menghubungkan alamat localhost:5000 (web server)
dengan aplikasi client. Silakan pilih “Change URL”.
Lalu, isi dengan host beserta port dari web server yang Anda buat. Contohnya
“localhost:5000”
Setelah Anda melihat URL dari web server, maka web server dan aplikasi client sudah
terhubung.
Meskipun sudah terhubung, tapi halaman masih menampilkan “Error displaying notes!
Make sure you have done with the back-end or correct url.” Hal itu wajar karena
kita belum melakukan apa pun terhadap web server yang kita buat.
Jika Anda menggunakan ESLint, ada satu hal yang perlu diperhatikan. Bila ada
warning atau error yang diberikan oleh ESLint namun hal itu tidak Anda setujui atau
ingin Anda hiraukan, maka Anda bisa menonaktifkan warning atau eror tersebut.
Contohnya, bila Anda menggunakan code style AirBnB, maka penggunaan console akan
dianggap warning.
Anda bisa menonaktifkan aturan no-console pada berkas .eslintrc.json dengan
menambahkan properti no-console dengan nilai off pada rules.
{
"env": {
"commonjs": true,
"es2021": true,
"node": true
},
"extends": [
"airbnb-base"
],
"parserOptions": {
"ecmaVersion": 12
},
"rules": {
"no-console": "off"
}
}
Dengan begitu, warning dari ESLint akan hilang untuk penggunaan console.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Menyimpan Catatan
Mari kita selesaikan kriteria satu per satu. Kriteria pertama adalah web server
harus bisa menyimpan catatan yang ditambahkan dari aplikasi client. Untuk
detailnya, tentu Anda sudah tahu kan?
Saat ini, aplikasi client belum bisa menambahkan catatan. Anda bisa coba sendiri
melalui tombol “Add note” di pojok kiri bawah halaman. Ketika Anda hendak
menambahkan catatan, browser akan menampilkan pesan seperti gambar di bawah ini.
Tugas kita saat ini adalah membuat fungsi menyimpan catatan bisa berjalan dengan
baik. Yuk langsung saja.
Dari kriteria yang sudah kita ketahui sebelumnya, agar web server dapat menyimpan
catatan, ia perlu menyediakan route dengan path ‘/notes’ dan method ‘POST’. Karena
itu, ayo kita langsung saja buat route-nya.
Silakan buka berkas routes.js dan tuliskan kode route pertama kita sesuai dengan
ketentuan.
routes.js
const routes = [
{
method: 'POST',
path: '/notes',
handler: () => {},
},
];
module.exports = routes;
Untuk fungsi handler, kita akan membuatnya pada berkas yang terpisah. Untuk
sekarang, isi dulu dengan nilai fungsi kosong seperti itu.
Jangan lupa untuk menuliskan module.exports = routes, agar routes dapat digunakan
oleh berkas server.js nantinya.
Sebelum menuliskan fungsi handler, mari kita buat dulu array untuk menampung objek
catatan pada berkas notes.js. Tulislah kode berikut:
notes.js
const notes = [];
module.exports = notes;
Lanjut kita buat fungsi handler untuk route ini. Silakan buka berkas handler.js dan
buat fungsi dengan nama “addNoteHandler”.
};
Masih ingatkan bahwa fungsi handler pada Hapi memiliki dua parameters? Jadi, jangan
lupa untuk menambahkan parameter tersebut setiap kali membuat fungsi handler.
Lalu untuk mengekspor fungsi handler ini, kita gunakan objek literals yah. Ini
bertujuan untuk memudahkan ekspor lebih dari satu nilai pada satu berkas
JavaScript.
};
module.exports = { addNoteHandler };
Langkah selanjutnya, mari kita tuliskan logika untuk menyimpan catatan dari client
ke dalam array notes.
Client mengirim data catatan (title, tags, dan body) yang akan disimpan dalam
bentuk JSON melalui body request. Masih ingatkan cara mendapatkan body request di
Hapi? Yap! Menggunakan properti request.payload. Yuk mari kita ambil datanya.
Selain itu, objek notes yang perlu kita simpan harus memiliki struktur seperti ini:
{
id: string,
title: string,
createdAt: string,
updatedAt: string,
tags: array of string,
body: string,
},
Kita hanya mendapatkan nilai title, tags, dan body dari client, itu berarti sisanya
kita perlu olah sendiri. Mari kita pikirkan dari properti id dulu.
Kriteria menyebutkan, properti id merupakan string dan harus unik, kita akan
menggunakan bantuan library pihak ketiga untuk menghasilkan nilainya. nanoid
merupakan salah satu library yang populer untuk menangani ini. Jadi, silakan pasang
library tersebut dengan perintah.
Catatan: Pastikan Anda memasang nanoid dengan versi 3.x.x. Karena jika menggunakan
versi terbaru, nanoid tidak dapat digunakan dengan format module CommonJS.
Untuk menggunakannya cukup mudah, kita hanya perlu memanggil method nanoid() dan
memberikan parameter number yang merupakan ukuran dari string-nya.
const id = nanoid(16);
};
const id = nanoid(16);
};
const id = nanoid(16);
const createdAt = new Date().toISOString();
const updatedAt = createdAt;
};
Kita sudah memiliki properti dari objek catatan secara lengkap. Sekarang, saatnya
kita masukan nilai-nilai tersebut ke dalam array notes menggunakan method push().
const id = nanoid(16);
const createdAt = new Date().toISOString();
const updatedAt = createdAt;
const newNote = {
title, tags, body, id, createdAt, updatedAt,
};
notes.push(newNote);
};
Jangan lupa impor array notes pada berkas handler.js.
const id = nanoid(16);
const createdAt = new Date().toISOString();
const updatedAt = createdAt;
const newNote = {
title, tags, body, id, createdAt, updatedAt,
};
notes.push(newNote);
};
Lalu, bagaimana menentukan apakah newNote sudah masuk ke dalam array notes? Mudah
saja! Kita bisa memanfaatkan method filter() berdasarkan id catatan untuk
mengetahuinya. Kurang lebih implementasinya seperti ini:
const id = nanoid(16);
const createdAt = new Date().toISOString();
const updatedAt = createdAt;
const newNote = {
title, tags, body, id, createdAt, updatedAt,
};
notes.push(newNote);
Kemudian, kita gunakan isSuccess untuk menentukan respons yang diberikan server.
Jika isSuccess bernilai true, maka beri respons berhasil. Jika false, maka beri
respons gagal.
const id = nanoid(16);
const createdAt = new Date().toISOString();
const updatedAt = createdAt;
const newNote = {
title, tags, body, id, createdAt, updatedAt,
};
notes.push(newNote);
if (isSuccess) {
const response = h.response({
status: 'success',
message: 'Catatan berhasil ditambahkan',
data: {
noteId: id,
},
});
response.code(201);
return response;
}
const response = h.response({
status: 'fail',
message: 'Catatan gagal ditambahkan',
});
response.code(500);
return response;
};
Fungsi handler selesai! Huft, panjang juga yah untuk menyelesaikan kriteria
pertama. Eits! Ini belum berakhir, perjalanan kita masih cukup jauh. Kita harus
tetap semangat!
Selanjutnya, mari kita gunakan fungsi handler ini pada konfigurasi route-nya.
Silakan buka routes.js, lalu ganti fungsi handler menjadi seperti ini:
{
method: 'POST',
path: '/notes',
handler: addNoteHandler,
},
Setelah itu, mari gunakan route configuration pada server. Silakan buka berkas
server.js, kemudian tambahkan kode yang diberi tanda tebal yah.
server.route(routes);
await server.start();
console.log(`Server berjalan pada ${server.info.uri}`);
};
init();
Terakhir, simpan seluruh perubahan pada semua berkas JavaScript yang ada. Kemudian,
coba kembali akses fitur tambah catatan pada aplikasi client. Apakah sekarang sudah
berfungsi?
Wah! Ternyata masih saja eror. Ketahuilah pada web apps untuk mengetahui penyebab
eror terjadi, kita bisa melihatnya melalui browser console. Silakan buka browser
console dengan menekan CTRL + SHIFT + I dan Anda akan melihat pesan eror di sana.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Same-Origin Policy
Server dapat menampung sebuah website, aplikasi, gambar, video, dan masih banyak
lagi. Ketika server menampung website, mungkin beberapa data gambar, video,
stylesheet biasanya diambil dari alamat server lain atau origin yang berbeda.
Contohnya stylesheet yang diambil dari Bootstrap CDN ataupun gambar yang diperoleh
dari server Unsplash. Hal ini wajar dan biasa dilakukan.
Namun apakah Anda tahu bahwa tidak semua data bisa diambil dari origin yang
berbeda? Contohnya data JSON yang didapatkan melalui teknik XMLHTTPRequest atau
fetch. Jika website meminta sesuatu menggunakan teknik tersebut dari luar origin-
nya, maka permintaan tersebut akan ditolak. Itu disebabkan oleh kebijakan same-
origin. Kasus ini terjadi pada aplikasi client dan web server yang kita buat.
Origin terdiri dari tiga hal: protokol, host, dan port number. Origin dari aplikasi
client kita adalah
http://notesapp-v1.dicodingacademy.com
Selama aplikasi client mengakses data dari origin yang sama, hal itu dapat
dilakukan. Namun bila ada salah satu saja yang berbeda contohnya port 8001, maka
permintaan itu akan ditolak.
Dengan begitu jelas yah, apa penyebab gagalnya aplikasi client ketika melakukan
permintaan ke web server yang kita buat. Sudah jelas keduanya memiliki origin yang
berbeda. Origin web server kita saat ini adalah http://localhost:5000/
Lalu, apa solusi agar keduanya dapat berinteraksi? Tenang, untungnya ada mekanisme
yang dapat membuat mereka saling berinteraksi. Mekanisme tersebut disebut Cross-
origin resource sharing (CORS). Pertanyaannya, bagaimana cara menerapkannya?
Cukup mudah! Pada web server, kita hanya perlu memberikan nilai header ‘Access-
Control-Allow-Origin’ dengan nilai origin luar yang akan mengkonsumsi datanya
(aplikasi client).
response.header('Access-Control-Allow-Origin', 'http://notesapp-
v1.dicodingacademy.com');
return response;
Atau Anda bisa menggunakan tanda * pada nilai origin untuk memperbolehkan data
dikonsumsi oleh seluruh origin.
const response = h.response({ error: false, message: 'Catatan berhasil ditambahkan'
});
response.header('Access-Control-Allow-Origin', '*');
return response;
Good news! Penerapannya akan jauh lebih mudah bila Anda menggunakan Hapi. Dengan
Hapi, CORS dapat ditetapkan pada spesifik route dengan menambahkan properti
options.cors di konfigurasi route. Contohnya seperti ini:
{
method: 'POST',
path: '/notes',
handler: addNoteHandler,
options: {
cors: {
origin: ['*'],
},
},
},
Bila ingin cakupannya lebih luas alias CORS diaktifkan di seluruh route yang ada di
server, Anda bisa tetapkan CORS pada konfigurasi ketika hendak membuat server
dengan menambahkan properti routes.cors. Contohnya seperti ini:
Sudah cukup jelas? Kalo begitu, ayo kita terapkan CORS pada web server kita.
Silakan buka berkas server.js, lalu tambahkan CORS pada konfigurasi pembuatan
server seperti yang sudah Anda pelajari.
Simpan perubahan berkas server.js, pastikan server masih berjalan, dan silakan coba
masukan kembali catatan menggunakan aplikasi client. Percayalah, sekarang harusnya
bisa berjalan dengan baik.
Jika setelah memasukan catatan Anda dikembalikan ke halaman utama tanpa peringatan
apa pun, itu artinya Anda berhasil menambahkan catatan. Selamat yah!
Tapi sayang sekali, walaupun berhasil tersimpan, catatan tersebut masih belum dapat
kita lihat. Agar catatan dapat kita lihat, ayo kita selesaikan kriteria kedua!
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Menampilkan Catatan
Kita beranjak ke kriteria kedua, yakni menampilkan seluruh atau secara spesifik
catatan yang disimpan pada server. Sepertinya kriteria ini akan lebih mudah dari
kriteria sebelumnya. Kalau begitu kita langsung saja yah.
Pertama, kita buat konfigurasi route terlebih dahulu pada berkas routes.js.
Tetapkan path dengan nilai ‘/notes’ dan method dengan nilai ‘GET’. Untuk handler,
kita berikan dulu fungsi kosong.
const routes = [
{
method: 'POST',
path: '/notes',
handler: addNoteHandler,
},
{
method: 'GET',
path: '/notes',
handler: () => {},
},
];
Lanjut kita buat fungsi handler-nya pada berkas handler.js. Buat fungsi dengan nama
getAllNotesHandler dan kembalikan data dengan nilai notes di dalamnya.
Yap! Semudah itu untuk handler mendapatkan seluruh catatan. Anda juga tidak perlu
menuliskan parameter request dan h karena ia tidak digunakan.
Jangan lupa untuk ekspor nilai getAllNotesHandler agar dapat digunakan di routes.js
{
method: 'GET',
path: '/notes',
handler: getAllNotesHandler,
},
Simpan seluruh perubahan yang ada, dan coba kembali buka aplikasi client.
Wah ada pesan baru. “Please try to add some note(s)”. Sepertinya ini akan berhasil,
silakan coba masukan note baru.
Jika aplikasi Notes Apps masih mengalami kendala same-origin, hal itu mungkin
disebabkan oleh ketentuan keamanan baru yang ditetapkan oleh Google Chrome.
Solusinya, Anda bisa temukkan pada diskusi [Masalah CORS] Aplikasi Client tidak
berubah.
Voila! Akhirnya catatan yang kita masukan tampak yah. Coba masuk ke halaman detail
dengan memilih catatan tersebut.
Yah, eror lagi. Tentu, karena kita belum membuat route untuk mendapatkan catatan
secara spesifik. Ayo kita selesaikan juga.
Kembali ke berkas routes.js, kemudian tambahkan route dengan path ‘/notes/{id}’ dan
method ‘GET’. Untuk handler isi dengan fungsi kosong dulu.
const routes = [
{
method: 'POST',
path: '/notes',
handler: addNoteHandler,
},
{
method: 'GET',
path: '/notes',
handler: getAllNotesHandler,
},
{
method: 'GET',
path: '/notes/{id}',
handler: () => {},
},
];
Lanjut kita buat fungsi handler-nya. Buka berkas handler.js, lalu buat fungsi
dengan nama getNoteByIdHandler.
};
Di dalam fungsi ini kita harus mengembalikan objek catatan secara spesifik
berdasarkan id yang digunakan oleh path parameter.
Pertama, kita dapatkan dulu nilai id dari request.params.
Setelah mendapatkan nilai id, dapatkan objek note dengan id tersebut dari objek
array notes. Manfaatkan method array filter() untuk mendapatkan objeknya.
Kita kembalikan fungsi handler dengan data beserta objek note di dalamnya. Namun
sebelum itu, pastikan dulu objek note tidak bernilai undefined. Bila undefined,
kembalikan dengan respons gagal.
{
method: 'GET',
path: '/notes/{id}',
handler: getNoteByIdHandler,
},
Simpan seluruh perubahan yang ada dan coba kembali aplikasi client-nya.
Dalam mencobanya, mungkin Anda perlu menambahkan kembali notes karena kita hanya
menyimpannya di array. Di mana data tersebut akan hilang setiap kali server
dijalankan ulang oleh nodemon.
Well done! Sekarang aplikasi sudah bisa menampilkan detail catatan. Di mana di
halaman ini ada tombol “Edit Note”. Bila menekan tombol tersebut, kita akan
diarahkan ke halaman edit note. Tapi halaman tersebut masih belum berfungsi. Nah,
pada materi selanjutnya kita akan buat halaman tersebut berfungsi yah.
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Mengubah Catatan
Dua kriteria sudah terpenuhi, kini sebagian dari fitur aplikasi sudah dapat
digunakan. Hanya tinggal sedikit lagi perjalanan kita untuk melengkapi
fungsionalitasnya. Sudah siap menyelesaikan kriteria ketiga? Ayo kita mulai.
Kriteria ketiga adalah web server harus bisa mengubah catatan yang disimpan, baik
perubahan pada title, tags, atau body. Ketika melakukan perubahan, client akan
mengirimkan permintaan ke route ‘/notes/{id}’ dengan method ‘PUT’ dan membawa objek
catatan terbaru pada body request. Yuk langsung saja kita eksekusi.
Seperti biasa, kita awali dengan membuat konfigurasi route-nya dulu. Silakan buka
kembali berkas routes.js, lalu buat route dengan path ‘/notes/{id}’, method ‘PUT’,
dan handler dengan nilai fungsi kosong.
{
method: 'PUT',
path: '/notes/{id}',
handler: () => {},
},
Yuk kita buat fungsi handler-nya pada berkas handler.js. Kita beri nama fungsi
tersebut dengan editNoteByIdHandler ya.
const editNoteByIdHandler = (request, h) => {
};
Catatan yang diubah akan diterapkan sesuai dengan id yang digunakan pada route
parameter. Jadi, kita perlu mendapatkan nilai id-nya terlebih dahulu.
Setelah itu, kita dapatkan data notes terbaru yang dikirimkan oleh client melalui
body request.
Selain itu, tentu kita perlu perbarui juga nilai dari properti updatedAt. Jadi,
dapatkan nilai terbaru dengan menggunakan new Date().toISOString().
Great! Data terbaru sudah siap, saatnya mengubah catatan lama dengan data terbaru.
Kita akan mengubahnya dengan memanfaatkan indexing array, silakan gunakan teknik
lain bila menurut Anda lebih baik yah.
Pertama, dapatkan dulu index array pada objek catatan sesuai id yang ditentukan.
Untuk melakukannya, gunakanlah method array findIndex().
Bila note dengan id yang dicari ditemukan, maka index akan bernilai array index
dari objek catatan yang dicari. Namun bila tidak ditemukan, maka index bernilai -1.
Jadi, kita bisa menentukan gagal atau tidaknya permintaan dari nilai index
menggunakan if else.
Catatan: Spread operator pada kode di atas digunakan untuk mempertahankan nilai
notes[index] yang tidak perlu diubah. Jika Anda butuh mengingat kembali bagaimana
spread operator bekerja, silakan simak pada dokumentasi yang dijelaskan MDN: Spread
Syntax.
module.exports = {
addNoteHandler,
getAllNotesHandler,
getNoteByIdHandler,
editNoteByIdHandler,
};
{
method: 'PUT',
path: '/notes/{id}',
handler: editNoteByIdHandler,
},
const {
addNoteHandler,
getAllNotesHandler,
getNoteByIdHandler,
editNoteByIdHandler,
} = require('./handler');
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------
Menghapus Catatan
Tinggal selangkah lagi untuk memenuhi seluruh kriteria yang ada. Saatnya kita
menyelesaikan kriteria terakhir, yakni menghapus catatan. Yuk langsung saja.
Buka kembali berkas routes.js. Tambahkan konfigurasi route dengan nilai path
‘/notes/{id}’, method ‘DELETE’, dan handler dengan fungsi kosong seperti ini:
{
method: 'DELETE',
path: '/notes/{id}',
handler: () => {},
},
};
Setelah itu, saatnya kita menuliskan logikanya. Sama seperti mengubah catatan. Kita
akan memanfaatkan index untuk menghapus catatan.
Pertama, kita dapatkan dulu nilai id yang dikirim melalui path parameters.
const deleteNoteByIdHandler = (request, h) => {
const { id } = request.params;
};
Selanjutnya, dapatkan index dari objek catatan sesuai dengan id yang didapat.
Lakukan pengecekan terhadap nilai index, pastikan nilainya tidak -1 bila hendak
menghapus catatan. Nah, untuk menghapus data pada array berdasarkan index, gunakan
method array splice().
Bila index bernilai -1, maka kembalikan handler dengan respons gagal.
module.exports = {
addNoteHandler,
getAllNotesHandler,
getNoteByIdHandler,
editNoteByIdHandler,
deleteNoteByIdHandler,
};
Saatnya kita gunakan fungsi handler pada konfigurasi route. Buka berkas routes.js,
lalu tambahkan fungsi handler-nya.
{
method: 'DELETE',
path: '/notes/{id}',
handler: deleteNoteByIdHandler,
},
const {
addNoteHandler,
getAllNotesHandler,
getNoteByIdHandler,
editNoteByIdHandler,
deleteNoteByIdHandler,
} = require('./handler');
Simpan seluruh perubahan. Setelah itu, silakan coba lagi aplikasi client-nya. Jika
semua berhasil diterapkan, seharusnya fitur hapus catatan sudah berfungsi dengan
baik yah!
Well done! Sebuah kemajuan yang luar biasa! Siap melangkah ke tantangan berikutnya?
Yuk kita lanjutkan perjalanannya!
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
----------------