Sebelumnya telah dilakukan pembuatan web server dengan node.js melalui sederetan kode yang diketik satu persatu. Sebetulnya pembuatan web server ini merupakan kegiatan yang berulang, artinya setiap orang yang hendak membuat web service pasti akan membuat web server. Sungguh tidak efisien jika itu dilakukan sendiri-sendiri, nah disinilah fungsi dari framework yaitu menyederhanakan pembuatan web service. Web Framework adalah sebuah kerangka yang dapat membantu mempermudah pengembangan web termasuk dalam membuat web server. Dengan menggunakan framework, penulisan kode akan lebih terstruktur, mudah dipelihara, dan gampang dikembangkan.
![]() |
| Sumber: https://hapi.dev/ |
Diantara web framework dari node.js adalah expressjs (keunggulannya ringan namun tidak memiliki standar baku) dan hapi (lebih lengkap namun diperlukan belajar yang lebih dalam). Rekomendasi dari developer untuk membuat front-end sebaiknya menggunakan framework expressjs sedangkan untuk membangun web server yang kompleks sebaiknya menggunakan framework hapi. Karena fokus pembuatan adalah REST API maka di sini akan digunakan framework hapi, langkah awalnya:
- Buat folder projek dengan nama hapi-web-server
- Buka text editor dan arahkan terminal ke folder tersebut
- Gunakan syntax npm inti --y
- Di file package.json edit menjadi:
- "scripts": {
- "start": "node server.js"
- },
- Buat file server.js dan ketik kode console.log('Halo, kita akan belajar membuat server menggunakan Hapi');
- Jalankan npm run start
- Pastikan projek telah siap digunakan
Sekarang akan dibuat HTTP server, ikuti langkahnya:
- Instal module @hapi/hapi dengan syntax npm install @hapi/hapi
- Tunggu hingga proses selesai, dan pastikan telah terdaftar di package.json
- Gunakan kode di bawah:
- const Hapi = require('@hapi/hapi');
- const init = async () => {
- const server = Hapi.server({
- port: 5000,
- host: 'localhost',
- });
- await server.start();
- console.log(`Server berjalan pada ${server.info.uri}`);
- };
- init();
- Jalankan server dengan npm run start
- Lakukan request dengan syntax curl -X GET http://localhost:5000 -i
- Tanpa perlu repot-repot koding kita dapatkan response
HTTP dibuat melalui Hapi.server() yang menerima satu parameter yaitu ServerOption yang merupakan konfigurasi dari server seperti port dan host. Menjalankan server di Hapi itu mengikuti cara asinkron sehingga digunakan async-await. Sekarang akan dibuat request (GET, POST, PUT, DELETE) dan routing, ikuti langkah di bawah:
- Buat file dengan nama routes.js, gunakan kode di bawah:
- 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;
- Pada file server.js gunakan kode di bawah
- const Hapi = require('@hapi/hapi');
- const routes = require('./route')
- const init = async () => {
- const server = Hapi.server({
- port: 5000,
- host: 'localhost',
- });
- server.route(routes)
- await server.start();
- console.log(`Server berjalan pada ${server.info.uri}`);
- };
- init();
- Pastikan file sudah di simpan lalu jalankan syntax npm run test
- Lakukan request:
- curl -X GET http://localhost:5000
- // output: Homepage
- curl -X GET http://localhost:5000/about
- // output: About page
- curl -X GET http://localhost:5000/test
- // output: Halaman tidak ditemukan
- curl -X POST http://localhost:5000
- // output: Halaman tidak dapat diakses dengan method tersebut
- Request dan routing menggunakan Hapi berhasil dilakukan
Ada beberapa hal yang perlu diketahui seperti '*' yang berarti route dapat diakses dengan seluruh method yang ada di HTTP dan '/{any*}' yang berfungsi untuk menangani permintaan yang masuk pada path yang belum ditentukan, semacam else. Nah, di dalam routing itu kan ada path, untuk website yang sifatnya komunitas seperti facebook maka akan ada jutaan path yang dibuat, untuk mengangani hal ini dilakukan teknik path parameter. Gunakan potongan kode di bawah untuk diletakkan di atas persis path '/{any*}' pada file routes.js:
- {
- method: 'GET',
- path: '/hello/{name?}',
- handler: (request, h) => {
- const { name = "stranger" } = request.params;
- return `Hello, ${name}!`;
- },
- },
Properti request.params digunakan untuk mendapatkan nilai path parameter dengan memanfaatkan object destructuring yaitu const { name = "stranger"}, stranger di sana sebagai nama default. Coba simpan file tersebut kemudian lakukan kill pada localhost:5000, lakukan npm run start dan lakukan request:
- curl -X GET http://localhost:5000/hello/joko
- // output: Hello, joko!
- curl -X GET http://localhost:5000/hello
- // output: Hello, stranger!
Selain teknik path parameter juga ada teknik query parameter yang memungkinkan dilakukan filter. Format dari teknik query parameter adalah key=value dan setiap path itu bisa memiliki lebih dari satu query. Gunakan kode di bawah pada kode yang telah diterapkan path parameter:
- {
- method: 'GET',
- path: '/hello/{name?}',
- handler: (request, h) => {
- const { name = "stranger" } = request.params;
- const { lang } = request.query;
- if(lang === 'id') {
- return `Hai, ${name}!`;
- }
- return `Hello, ${name}!`;
- },
- },
Properti request.query digunakan untuk mendapatkan nilai query lang. Kode di atas itu menggunakan satu query. Simpan file routes.js, kemudian lakukan kill pada localhost:5000, lalu npm run start dan lakukan request:
- curl -X GET http://localhost:5000/hello/joko?lang=id
- // output: Hai, joko!
- curl -X GET http://localhost:5000/hello/joko
- // output: Hello, joko!
Sebelumnya untuk mendapatkan body request di node.js perlu melakukan konversi dengan JSON.parse(), nah dengan menggunakan framework hapi sudah tidak perlu lagi karena hapi sudah melakukan pengolahan di balik layar sehingga hanya perlu dilakukan request.payload. Perhatikan potongan kode di bawah:
- server.route({
- method: 'POST',
- path: '/login',
- handler: (request, h) => {
- const { username, password } = request.payload;
- return `Welcome ${username}!`;
- },
- });
Kembali dengan memanfaatkan object destructuring const {username, password} maka handler menerima payload melalui request.payload sehingga client mengirimkan data dengan struktur:
- { "username": "harrypotter", "password": "encryptedpassword" }
Sebelum menggunakan module hapi pada method HTTP digunakan dua parameter yaitu request dan response, kemudian di module hapi berubah menjadi request dan h. Parameter h di module hapi ini disebut response toolkit yang menampung banyak method untuk menangani permintaan client. Pada kasus sederhana mungkin response toolkit tidak terlalu dibutuhkan tetapi pada kasus yang lebih kompleks maka parameter h diperlukan. Sampai di sini sudah berhasil dibuat sebuah web server dengan menggunakan framework hapi.
Selanjutkan akan diterapkan framework hapi untuk membangun web dengan arsitektur RESTful API. Ikuti langkah berikut:
- Buat folder dengan nama notes-app-back-end
- Buka folder tersebut dengan text editor dan gunakan syntax npm init --y
- Pastikan package.json sudah ada dan edit dengan kode:
- "scripts": {
- "start": "nodemon server.js"
- },
- Install tools pertama dengan syntax npm install nodemon --save-dev
- Buat file server.js dengan kode:
- console.log('Hallo kita akan membuat RESTful API');
- Lalu jalankan server dengan syntax npm run start
- Install tolls kedua dengan syntax npm install eslint --save-dev
- Kemudian lakukan konfigurasi untuk ESLint sebagai berikut:
- npm init @eslint/config
- 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 : (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.
- Jika mengalami SyntaxError: Cannot use import statement outside a module maka node.js harus di-update, gunakan syntax npm install -g update-node
- Jika berhasil maka akan muncul file baru .eslintrc.json
- Buka file package.json kemudian edit di bagian kode:
- "scripts": {
- "start": "nodemon server.js",
- "lint": "eslint ./"
- },
- Jalankan dengan syntax npm run lint
- Untuk dapat mengintegrasikan di text editor bisa dicari di Extensions pada text editor VS Code kemudian tekan tombol Install
- Arahkan kursor ke console.log() kemudian tunggu hingga lampu di bagian kanan muncul lalu klik lampu tersebut pilih opsi ESLint: Manage Library Execution
- Pilih Allow Everywhere
- Kemudian tutup VS Code dan buka kembali, jika berhasil maka ESLint telah dapat digunakan
Tools nodemon digunakan agar tidak perlu berulang-ulang menjalankan server, jadi server akan otomatis dijalankan, sedangkan tools ESLint digunakan untuk melakukan konsistensi gaya penulisan kode. Di dunia pemrograman ada beberapa gaya penulisan kode seperti AirBnb JavaScript Code Style, Google JavaScript Code Style, dan StandardJS Code Style.
Sebelum membangun sebuah proyek apapun itu maka diperlukan kriteria atau spesifikasi dari proyek tersebut. Dalam hal ini yang akan dibangun adalah proyek RESTful API untuk aplikasi catatan sederhana yang dapat melakukan penyimpanan (path ‘/notes’ dan method POST), pembacaan (path ‘/notes’ dan method ‘GET’), pengubahan (path ‘/notes/{id}’ dan method ‘PUT’), dan penghapusan (path ‘/notes/{id}’ dan method ‘DELETE’). Berikut adalah struktur data yang akan disimpan oleh server:
- {
- id: string,
- title: string,
- createdAt: string,
- updatedAt: string,
- tags: array of string,
- body: string,
- },
Untuk properti id, createdAt, dan updateAt dibuat oleh server sehingga client tidak perlu mengirimnya. Selain itu server mengembalikan response dengan status code. Seluruh data dikirim dalam bentuk JSON dan dengan format tertentu. Kemudian perhatikan struktur proyek di bawah:
- notes-app-back-end
- ├── node_modules
- ├── src
- │ ├── handler.js
- │ ├── notes.js
- │ ├── routes.js
- │ └── server.js
- ├── .eslintrc.json
- ├── package-lock.json
- └── package.json
Sekarang file server.js berada di dalam folder src oleh karena itu perlu diupdate pada package.json menjadi:
- "scripts": {
- "start": "nodemon ./src/server.js",
- "lint": "eslint ./src"
- },
Sakarang akan dimulai pembuatan projek sesuai spesifikasi tersebut:
- Buat folder sesuai struktur yang disyaratkan
- Install package hapi dengan syntax npm install @hapi/hapi
- Gunakan kode di bawah untuk file server.js
- const Hapi = require('@hapi/hapi');
- const init = async () => {
- const server = Hapi.server({
- port: 5000,
- host: 'localhost',
- });
- await server.start();
- console.log(`Server berjalan pada ${server.info.uri}`);
- };
- init();
- Jalankan nodemon dengan npm run start
- Hubungkan web server localhost:5000 dengan aplikasi client
- Pastikan web server dan aplikasi client sudah terhubung
- Pada file .eslintrc.json lakukan nonaktif aturan non-console dengan kode di bawah:
- {
- "env": {
- "commonjs": true,
- "es2021": true,
- "node": true
- },
- "extends": [
- "airbnb-base"
- ],
- "parserOptions": {
- "ecmaVersion": 12
- },
- "rules": {
- "no-console": "off"
- }
- }
- Pada file routes.js gunakan kode di bawah:
- const routes = [
- {
- method: 'POST',
- path: '/notes',
- handler: () => {},
- },
- ];
- module.exports = routes;
- Pada file notes.js gunakan kode di bawah:
- const notes = [];
- module.exports = notes;
- Pada file handler.js gunakan kode di bawah:
- const addNoteHandler = (request, h) => {
- const { title, tags, body } = request.payload
- };
- module.exports = { addNoteHandler };
- Install library nanoid dengan syntax npm install nanoid@3.x.x
- Sekarang file handler.js berubah menjadi:
- const { nanoid } = request('nanoid')
- const addNoteHandler = (request, h) => {
- const { title, tags, body } = request.payload
- const id = nanoid(16);
- const createdAt = new Date().toISOString();
- const updatedAt = createdAt;
- const newNote = {
- title, tags, body, id, createdAt, updatedAt,
- };
- notes.push(newNote);
- };
- module.exports = { addNoteHandler };
- Untuk mengetahui newNote apakah sudah masuk ke dalam array notes dapat digunakan method filter perhatikan potongan kode untuk handler.js di bawah:
- const addNoteHandler = (request, h) => {
- const { title, tags, body } = request.payload;
- const id = nanoid(16);
- const createdAt = new Date().toISOString();
- const updatedAt = createdAt;
- const newNote = {
- title, tags, body, id, createdAt, updatedAt,
- };
- notes.push(newNote);
- const isSuccess = notes.filter((note) => note.id === id).length > 0;
- };
- Kemudian masih di file handler.js tambahkan kode logika di bawah method filter tadi:
- 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;
- Sehingga secara keseluruhan kode di file handler.js menjadi:
- const { nanoid } = require('nanoid')
- const notes = require('./notes')
- const addNoteHandler = (request, h) => {
- const { title, tags, body } = request.payload
- const id = nanoid(16);
- const createdAt = new Date().toISOString();
- const updatedAt = createdAt;
- const newNote = {
- title, tags, body, id, createdAt, updatedAt,
- };
- notes.push(newNote);
- const isSuccess = notes.filter((note) => note.id === id).length > 0;
- 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;
- };
- module.exports = { addNoteHandler };
- Kemudian gunakan fungsi dari handler ini ke konfigurasi routes, gunakan kode di bawah pada server routes.js:
- const { addNoteHandler } = require('./handler');
- {
- method: 'POST',
- path: '/notes',
- handler: addNoteHandler,
- },
- Kemudian gunakan konfigurasi routes pada server, gunakan kode di bawah pada file server.js:
- const Hapi = require('@hapi/hapi');
- const routes = require('./routes');
- const init = async () => {
- const server = Hapi.server({
- port: 5000,
- host: 'localhost',
- });
- server.route(routes);
- await server.start();
- console.log(`Server berjalan pada ${server.info.uri}`);
- };
- init();
- Simpan seluruh file yang telah di edit kemudian buka aplikasi client maka masih akan terjadi error, nah untuk mengetahui error yang terjadi lakukan inspect pada aplikasi tersebut dengan klik kanan lalu inspect atau CTRL + SHIFT + I, di sana nanti akan diketahui ternyata error karena dihalangi oleh same-origin policy
- Buka file server.js dan gunakan kode di bawah:
- const server = Hapi.server({
- port: 5000,
- host: 'localhost',
- routes: {
- cors: {
- origin: ['*'],
- },
- },
- });
- Perlu diperhatikan ada beberapa browser yang melakukan kebijakan penolakan CORS
- Buat konfigurasi routes di file routes.js, gunakan kode di bawah:
- const routes = [
- {
- method: 'POST',
- path: '/notes',
- handler: addNoteHandler,
- },
- {
- method: 'GET',
- path: '/notes',
- handler: () => {},
- },
- ];
- Buat fungsi handler di file handler.js, gunakan kode di bawah:
- const getAllNotesHandler = () => ({
- status: 'success',
- data: {
- notes,
- },
- });
- module.exports = { addNoteHandler, getAllNotesHandler };
- Kembali ke file routes.js, gunakan potongan kode di bawah:
- {
- method: 'GET',
- path: '/notes',
- handler: getAllNotesHandler,
- },
- Lalu coba kembali di aplikasi client, jika di coba detailkan catatan maka akan error karena belum dibuat konfigurasi kembali di routes.js, gunakan kode di bawah pada file routes.js:
- {
- method: 'GET',
- path: '/notes/{id}',
- handler: () => {},
- },
- Lalu kembali pada handler buat fungsi:
- const getNoteByIdHandler = (request, h) => {
- const { id } = request.params;
- const note = notes.filter((n) => n.id === id)[0];
- if (note !== undefined) {
- return {
- status: 'success',
- data: {
- note,
- },
- };
- }
- const response = h.response({
- status: 'fail',
- message: 'Catatan tidak ditemukan',
- });
- response.code(404);
- return response;
- };
- Gunakan fungsi tersebut pada konfigurasi routes.js (jangan lupa impor):
- {
- method: 'GET',
- path: '/notes/{id}',
- handler: getNoteByIdHandler,
- },
- Simpan seluruh file dan jalankan ulang
- Seperti biasa akan dilakukan konfigurasi di routes.js, gunakan kode di bawah:
- {
- method: 'PUT',
- path: '/notes/{id}',
- handler: () => {},
- },
- Lalu buat fungsi di handle.js, gunakan kode di bawah:
- const editNoteByIdHandler = (request, h) => {
- const { id } = request.params;
- const { title, tags, body } = request.payload;
- const updatedAt = new Date().toISOString();
- const index = notes.findIndex((note) => note.id === id);
- if (index !== -1) {
- notes[index] = {
- ...notes[index],
- title,
- tags,
- body,
- updatedAt,
- };
- const response = h.response({
- status: 'success',
- message: 'Catatan berhasil diperbarui',
- });
- response.code(200);
- return response;
- }
- const response = h.response({
- status: 'fail',
- message: 'Gagal memperbarui catatan. Id tidak ditemukan',
- });
- response.code(404);
- return response;
- };
- Sekarang gunakan fungsi tersebut pada routes.js, gunakan kode di bawah (jangan lupa import):
- {
- method: 'PUT',
- path: '/notes/{id}',
- handler: editNoteByIdHandler,
- },
- Simpan seluruh file dan coba kembali seluruh fungsinya dengan membuka aplikasi client
Sampai di sini sudah 3 spesifikasi dibuat, yaitu menyimpan, membaca, dan mengubah, sekarang akan dilanjutkan untuk menghapus catatan. Ikuti langkah di bawah:
- Tambahkan konfigurasi pada routes.js, gunakan kode di bawah:
- {
- method: 'DELETE',
- path: '/notes/{id}',
- handler: () => {},
- },
- Kemudian buat fungsi baru pada handler.js, gunakan kode di bawah (jangan lupa export):
- const deleteNoteByIdHandler = (request, h) => {
- const { id } = request.params;
- const index = notes.findIndex((note) => note.id === id);
- if (index !== -1) {
- notes.splice(index, 1);
- const response = h.response({
- status: 'success',
- message: 'Catatan berhasil dihapus',
- });
- response.code(200);
- return response;
- }
- const response = h.response({
- status: 'fail',
- message: 'Catatan gagal dihapus. Id tidak ditemukan',
- });
- response.code(404);
- return response;
- };
- Simpan seluruh file dan jalankan ulang aplikasi client
- server.js
- const Hapi = require('@hapi/hapi')
- const routes = require('./routes')
- const init = async () => {
- const server = Hapi.server({
- port: 5000,
- host: 'localhost',
- routes: {
- cors: {
- origin: ['*'],
- },
- },
- })
- server.route(routes)
- await server.start()
- console.log(`Server berjalan pada ${server.info.uri}`)
- }
- init()
- routes.js
- const {
- addNoteHandler,
- getAllNotesHandler,
- getNoteByIdHandler,
- editNoteByIdHandler,
- deleteNoteByIdHandler
- } = require('./handler')
- const routes = [
- {
- method: 'POST',
- path: '/notes',
- handler: addNoteHandler,
- },
- {
- method: 'GET',
- path: '/notes',
- handler: getAllNotesHandler,
- },
- {
- method: 'GET',
- path: '/notes/{id}',
- handler: getNoteByIdHandler,
- },
- {
- method: 'PUT',
- path: '/notes/{id}',
- handler: editNoteByIdHandler,
- },
- {
- method: 'DELETE',
- path: '/notes/{id}',
- handler: deleteNoteByIdHandler,
- },
- ];
- module.exports = routes;
- notes.js
- const notes = [];
- module.exports = notes;
- handler.js
- const { nanoid } = require('nanoid')
- const notes = require('./notes')
- const addNoteHandler = (request, h) => {
- const { title, tags, body } = request.payload
- const id = nanoid(16);
- const createdAt = new Date().toISOString();
- const updatedAt = createdAt;
- const newNote = {
- title, tags, body, id, createdAt, updatedAt,
- };
- notes.push(newNote);
- const isSuccess = notes.filter((note) => note.id === id).length > 0;
- 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;
- };
- const getAllNotesHandler = () => ({
- status: 'success',
- data: {
- notes,
- },
- });
- const getNoteByIdHandler = (request, h) => {
- const { id } = request.params;
- const note = notes.filter((n) => n.id === id)[0];
- if (note !== undefined) {
- return {
- status: 'success',
- data: {
- note,
- },
- };
- }
- const response = h.response({
- status: 'fail',
- message: 'Catatan tidak ditemukan',
- });
- response.code(404);
- return response;
- };
- const editNoteByIdHandler = (request, h) => {
- const { id } = request.params;
- const { title, tags, body } = request.payload;
- const updatedAt = new Date().toISOString();
- const index = notes.findIndex((note) => note.id === id);
- if (index !== -1) {
- notes[index] = {
- ...notes[index],
- title,
- tags,
- body,
- updatedAt,
- };
- const response = h.response({
- status: 'success',
- message: 'Catatan berhasil diperbarui',
- });
- response.code(200);
- return response;
- }
- const response = h.response({
- status: 'fail',
- message: 'Gagal memperbarui catatan. Id tidak ditemukan',
- });
- response.code(404);
- return response;
- };
- const deleteNoteByIdHandler = (request, h) => {
- const { id } = request.params;
- const index = notes.findIndex((note) => note.id === id);
- if (index !== -1) {
- notes.splice(index, 1);
- const response = h.response({
- status: 'success',
- message: 'Catatan berhasil dihapus',
- });
- response.code(200);
- return response;
- }
- const response = h.response({
- status: 'fail',
- message: 'Catatan gagal dihapus. Id tidak ditemukan',
- });
- response.code(404);
- return response;
- };
- module.exports = {
- addNoteHandler,
- getAllNotesHandler,
- getNoteByIdHandler,
- editNoteByIdHandler,
- deleteNoteByIdHandler
- };
Sampai sini sudah dipelajari hapi, ESLint, dan cors.
ref:

Komentar