Anda di halaman 1dari 1067

Table of Contents

Kata pengantar .............................................................................................................. 8

License Buku .................................................................................................................. 9

Tentang Buku ............................................................................................................... 10

Diagram Alur Aplikasi ................................................................................................... 11

Struktur dan Relasi Database ....................................................................................... 16

Wirefrime UI Desain Website ........................................................................................ 17

Persiapan dan Persyaratan Tools ................................................................................. 21

Membuat Project Baru Laravel Dengan Composer ....................................................... 26

Membuat Helpers di Laravel ........................................................................................ 29

Upgrade Versi Laravel .................................................................................................. 34

Koneksi Database di Laravel ........................................................................................ 35

Membuat Model dan Migration ..................................................................................... 37

Eloquent Relationships ................................................................................................. 62

Eloquent Accessors, Mutators & Casting ...................................................................... 94

Membuat Data Seeder User ....................................................................................... 105

Apa itu API ? ............................................................................................................... 108

Apa itu JWT (Json Web Token) ? ................................................................................. 110

Installasi dan Konfigurasi JWT untuk Admin ............................................................... 112

Membuat Restful API Login Admin ............................................................................. 117

Membuat Restful API Dashboard Admin ..................................................................... 132

Membuat Restful API CRUD Categories ...................................................................... 141

Membuat Restful API CRUD Products ......................................................................... 164

Membuat Restful API Invoices Admin ......................................................................... 191

Membuat Restful API Customers ................................................................................ 200

Membuat Restful API CRUD Sliders ............................................................................ 207

Membuat Restful API CRUD Users .............................................................................. 221

Membuat Restful API Register Customer .................................................................... 241


Konfigurasi JWT untuk Customer ................................................................................ 248

Membuat Restful API Login Customer ........................................................................ 254

Membuat Restful API Dashboard Customer ................................................................ 268

Membuat Restful API Invoices Customer .................................................................... 273

Membuat Restful API Review Product ......................................................................... 280

Membuat Restful API Categories Web ........................................................................ 288

Membuat Restful API Products Web ........................................................................... 297

Membuat Restful API Sliders Web .............................................................................. 304

Installasi dan Konfigurasi Raja Ongkir ........................................................................ 309

Membuat Restful API Raja Ongkir ............................................................................... 319

Membuat Restful API Carts ......................................................................................... 331

Installasi dan Konfigurasi Midtrans ............................................................................. 348

Membuat Restful API Checkout .................................................................................. 355

Membuat Restful API Notifikasi Handler Payment Gateway ....................................... 368

Apa itu Nuxt.js ? ......................................................................................................... 376

Installasi Nuxt.js ......................................................................................................... 378

Memahami Struktur Folder di Nuxt.js ......................................................................... 382

Rendering ................................................................................................................... 385

Target Deployment .................................................................................................... 386

Routing ....................................................................................................................... 387

Meta Tags dan SEO .................................................................................................... 390

Data Fetching ............................................................................................................. 393

Berkenalan Dengan Vuex ........................................................................................... 397

Membuat Project Baru di Nuxt.js ................................................................................ 404

Kustomisasi Progress Bar di Nuxt.js ........................................................................... 408

Konfigurasi SSR dan Target Deployment ................................................................... 410

Integrasi Dengan CSS dan JavaScript External (Template Dashboard CoreUI) ........... 413

Installasi dan Konfigurasi Nuxt Auth .......................................................................... 419

Membuat Middleware Role ......................................................................................... 427

Installasi dan Konfigurasi Vue Star Rating .................................................................. 432


Installasi dan Konfigurasi Chart.js .............................................................................. 435

Installasi CKEDITOR dan Sweet Alert 2 ....................................................................... 438

Membuat Global Helpers dengan Mixins .................................................................... 440

Membuat Proses Login Admin .................................................................................... 443

Menampilkan Data Products ...................................................................................... 454

Membuat Proses Insert Data Product ......................................................................... 470

Membuat Proses Edit dan Update Data Product ......................................................... 492

Membuat Proses Delete Data Product ........................................................................ 513

konfigurasi Vuex Admin Invoice ................................................................................. 525

Menampilkan Data Invoices ....................................................................................... 529

Menampilkan Detail Data Invoice ............................................................................... 547

Konfigurasi Vuex Admin Customer ............................................................................. 557

Menampilkan Data Customer ..................................................................................... 561

Konfigurasi Vuex Admin Slider ................................................................................... 577

Membuat Layout Admin ............................................................................................. 581

Menampilkan Data Sliders .......................................................................................... 590

Membuat Proses Insert Data Slider ............................................................................ 601

Membuat Proses Delete Data Slider ........................................................................... 612

Konfigurasi Vuex Admin User ..................................................................................... 622

Menampilkan Data Users ........................................................................................... 626

Membuat Proses Insert Data User .............................................................................. 642

Membuat Proses Edit dan Update Data User ............................................................. 653

Membuat Proses Delete Data User ............................................................................. 669

Membuat Halaman Dashboard ................................................................................... 681

Konfigurasi Vuex Admin Category .............................................................................. 688

Menampilkan Data Categories ................................................................................... 694

Membuat Proses Insert Data Category ....................................................................... 709

Membuat Proses Edit dan Update Data Category ...................................................... 720

Membuat Proses Delete Data Category ..................................................................... 736

Konfigurasi Vuex Admin Product ................................................................................ 748


Membuat Component Hader dan Footer .................................................................... 752

Membuat Fitur Rating dan Review ............................................................................. 757

Membuat Layout Default ............................................................................................ 773

Membuat Proses Register Customer .......................................................................... 777

Membuat Proses Login Customer ............................................................................... 788

Membuat Halaman Dashboard Customer .................................................................. 796

Konfigurasi Vuex Customer Invoice ............................................................................ 805

Menampilkan Data Invoice Customer ......................................................................... 809

Menampilkan Detail Data Invoice Customer .............................................................. 826

Menampilkan Snap Payment Midtrans ....................................................................... 841

Installasi dan Konfigurasi PWA ................................................................................... 849

Membuat Vuex Web Category .................................................................................... 855

Menampilkan Detail Data Product .............................................................................. 858

Membuat Fitur Pencarian Product .............................................................................. 868

Membuat Vuex Web Cart ........................................................................................... 882

Membuat Proses Add To Cart.md ............................................................................... 891

Menampilkan Data Cart di Header ............................................................................. 899

Menampilkan Cart Setelah Login dan Menghapus Cart Setelah Logout ..................... 905

Menampilkan Halaman Cart ....................................................................................... 909

Membuat Fungsi Remove Cart ................................................................................... 920

Membuat Vuex Web RajaOngkir ................................................................................. 929

Menghitung Biaya Ongkos Kirim ................................................................................ 936

Menampilkan Categories di Header ........................................................................... 958

Membuat Proses Checkout ......................................................................................... 963

Melakukan Pembayaran Dengan Midtrans ................................................................. 984

Membuat Vuex Web Slider ......................................................................................... 988

Membuat dan Menampilkan Component Slider .......................................................... 991

Membuat Vuex Web Product ...................................................................................... 998

Menampilkan Products di Homepage ....................................................................... 1002

Menampilkan Index Data Products ........................................................................... 1008


Menampilkan Index Data Categories ....................................................................... 1019

Menampilkan Data Product Berdasarkan Category .................................................. 1024

Deployment Project Laravel di cPanel (Shared Hosting) .......................................... 1033

Deploy Project Nuxt.js di Vercel (SSR) ..................................................................... 1052

Konfigurasi Notifikasi Handler Midtrans ................................................................... 1063

Source Code ............................................................................................................. 1066

Penutup .................................................................................................................... 1067


7
Kata pengantar

Bismillahirrahmannirrahiim.

Assalamu'alaikum Warahmatullahi Wabarakatuh.

Alhamdulillah, segala puji dan syukur penulis panjatkan kehadirat Tuhan Yang Maha Esa.
Karena berkat limpahan karunia-Nya, kami dapat menyelesaikan penulisan buku
Membangun Website Toko Online Dengan Laravel, Nuxt.js dan Payment Gateway.
Di Dalam penyusunan buku tersebut, penulis telah berusaha semaksimal mungkin sesuai
dengan kemampuan penulis demi penyelesaian buku ini dengan baik.

Penulis menyadari jika masih terdapat kekurangan ataupun suatu kesalahan dalam
penyusunan buku ini sehingga penulis mengharapkan kritik ataupun saran yang bersifat
positif untuk perbaikan di masa yang akan datang dari seluruh pembaca.

Maka dengan kerendahan hati penulis hanya bisa menyampaikan ucapan terima kasih
kepada semua pihak yang terlibat dalam proses penyelesaian ini.

Sekian semoga buku ini dapat bermanfaat dan mudah dipahami bagi penulis khususnya
serta para pembaca pada umumnya.

Wassalamu'alaikum Warahmatullahi Wabarakatuh.

Raihlah ilmu, dan untuk meraih ilmu belajarlah tenang dan sabar. - Umar bin Khattab

8
License Buku

Buku ini menggunakan license personal, yang artinya buku dan isi di dalamnya hanya boleh
digunakan dan di baca untuk seseorang yang sudah membelinya. Selain pemilik license dari
buku ini tidak diperbolehkan menggunakan apalagi sampai menyebar luaskan tanpa izin
dari penulis.

Dan untuk pemilik license dari buku ini juga tidak di perbolehkan menyebarkan dan
memperjual belikan lagi kepada seseorang.

CATATAN : jika terjadi pembajakan, maka akan di tindak lanjuti secara hukum.

9
Tentang Buku

Buku ini akan mengajarkan kita semua bagaimana cara menjadi seorang Full-Stack Web
Developer dengan cara membangun sebuah website toko online menggunakan Laravel
sebagai backend dan Nuxt.js sebagai frontend. Dimana Laravel akan berperan membuat
layanan web service berupa Rest API. Sedangkan Nuxt.js akan berperan untuk mengelola
dan menggunakan Rest API tersebut.

Kita juga akan belajar mengimplementasikan layanan dari RajaOngkir untuk mendapatkan
biaya ongkos kirim secara realtime dan akurat. Dan juga kita akan belajar tentang integrasi
dengan layanan Payment Gateway (Midtrans), fungsinya untuk melakukan pembayaran
secara otomatis di dalam website yang kita buat.

Di dalam buku ini akan dibagi menjadi 3 bab utama, yaitu :

1. Membangun web service Rest API menggunakan Laravel.


2. Mengintegrasikan Rest API di dalam Nuxt.js.
3. Deployment ke tahap production (online).

Di bab pertama, kita akan belajar banyak hal di dalam Laravel, seperti membuat project
baru menggunakan Composer. Membuat Relationships untuk menghubungkan beberapa
table menjadi satu kesatuaan. Memanipulasi data menggunakan Accessor, Mutator dan
Casting. Implementasi JWT (Json Web Token) untuk proses otentikasi di Rest API. Integrasi
RajaOngkir dan Payment Gateway.

Di bab kedua, Kita akaan belajar membuat project Nuxt.js baru. Membuat proses otentikasi.
Penerapan middleware. Kustomisasi middleware untuk multiple level user. Menampilkan
statistik dan grafik penjualan. Menampilkan data dengan teknik SSR (server side rendering),
sehingga website tersebut baik disisi SEO (search engine optimization).

Di dalam Nuxt.js kita juga akan belajar bagaimana melakukan integrasi dengan external
template dashboard. Dan kita akan integrasikan juga menggunakan Bootstrap Vue agar
kita dapat menggunakan beberapa component Bootstrap untuk mempermudah dalam
pengembangan project sekala besar.

Di bab terakhir, setelah semua project berhasil diselesaikan maka akan dilanjutkan belajar
bagaimana cara melakukan proses deployment. Untuk Laravel akan di deploy ke dalam
cPanel (shared hosting) dan untuk Nuxt.js akan di deploy ke dalam Vercel dengan teknik
SSR dan menerapkan konsep CI/CD atau biasa disebut dengan Continue Integration dan
Contrinue Development.

Ilmu itu seperti air. Jika ia tidak bergerak, maka ia akan menjadi keruh lalu membusuk -
Imam Syafi'i

10
Diagram Alur Aplikasi

Sebelum membangun sebuah aplikasi tentu saja kita butuh sebuah diagram untuk
mengetahui alur program yang akan di kembangkan. Dengan menggunakan sebuah
diagram kita bisa lebih tau dan paham bagaimana sebuah data berjalan di dalam aplikasi.

Dengan menjabarkan beberapa fitur-fitur dan menggunakan sebuah diagram, maka seorang
software developer akan sangat terbantu saat proses pengembangan aplikasi dan apalagi
aplikasi yang dikembangkan memiliki sekala yang sangat besar.

Di dalam studi kasus membangun website toko online kali ini kita akan memisahkan menjadi
3 diagram , yaitu admin, customer dan web.

Diagram Admin

11
Dari diagram Admin di atas kita memliki sebuah endpoint dengan menggunakan konsep

12
otentikasi yang artinya kita perlu melakukan sebuah login terlebih dahulu dan disini untuk
otentikasi kita menggunakan JWT atau Json Web Token.

Setelah kita berhasil melakukan proses otentikasi, maka sekarang kita dapat mengakses
beberapa endpoint lainnya, seperti /categories, /products, /invoices dan lain-lain.
Dan disini kita bisa melakukan proses CRUD atau Create, Read, update dan Delete sebuah
data ke dalam database.

Jadi kesimpulannya, endpoint yang ada di atas merupakan jenis endpoint private yang mana
hanya bisa diakses jika kita sudah melakukan proses otentikasi terlebih dahulu.

Diagram Customer

Untuk diagram Customer di atas, sama seperti yang ada di dalam Admin, yaitu kita
menggunakan jenis endpoint yang bersifat private, dimana endpoint yang ada di dalamnya
hanya dapat diakses apabila kita sudah melakukan proses otentikasi terlebih dahulu.

Di dalam diagram Customer di atas, kita akan menampilkan beberapa data, yang pertama
adalah /dashboard yang nantinya akan kita gunakan untuk menampilkan Rest API berupa

13
statistik data. Dan untuk /invoices akan digunakan untuk menampilkan list data invoice
yang pernah dibuat oleh customer tersebut.

Dan di dalam endpoint detail invoice, kita juga akan menampilkan list data order atau
produk yang telah dibeli. Kemudian kita juga dapat memberikan review atau ulasan dari
produk tersebut di dalam endpoint /reviews.

Diagram Web

Untuk diagaram Web akan berbeda dari sebelum-sebelumnya, karena semua endpoint yang
ada disini akan bersifat public kecuali endpoint /cart. Dimana semua endpoint kecuali
/cart dapat di akses secara public tanpa perlu melakukan proses otentikasi terlebih
dahulu.

14
Karena endpoint tersebut akan digunakan untuk menampilkan data di halaman depan
website. Dan untuk /cart akan kita atur menjadi private dengan tujuan hanya bisa di akses
jika customer telah melakukan proses otentikasi terlebih dahulu, karena di dalam data cart
nanti akan ada ID dari customer yang melakukan proses Add To Cart.

15
Struktur dan Relasi Database

Pembuatan sebuah struktur database beserta relasi merupakan hal wajib yang perlu
dilakukan oleh seorang software developer atau programmer sebelum melakukan proses
development atau koding.

Permasalahan yang sering terjadi adalah para software developer atau programmer yang
baru belajar melakukan koding langsung. Dan membuat database berserta relasinya dengan
berjalannya waktu, dan tentu saja hal ini tidak dibenarkan, karena bisa membuat aplikasi
yang dikerjakan sangat lama dan bisa berubah-ubah sewaktu-waktu.

Dan disini untuk website toko online yang akan kita bangun nanti sudah kita persiapkan
struktur berserta relasi databasenya dan tentu saja karena kita menggunakan Laravel, maka
kita tidak perlu membuat struktur dan relasi database tersebut secara manual, karena kita
akan mengunakan fitur Migration untuk melakukan generate table-table dan Eloquent
Relationships untuk membuat relasi antar table di dalam Laravel.

Berikut ini struktur dan relasi yang akan kita buat nanti di dalam pengembangan website
toko online menggunakan Laravel, Nuxt.js dan Payment Gateway (Midtrans).

CATATAN!: jangan buat terlebih dahulu untuk table-table dari gambar di atas, karena kita
akan membuatnya nanti melalui migration di Laravel.

16
Wirefrime UI Desain Website

Wirefrime adalah salah satu tahap yang sangat penting ketika kita mengembangkan sebuah
aplikasi maupun website, dengan adanya wirefrime kita bisa membuat rancangan desain
dan user exprience atau biasanya disebut UI/UX sebelum masuk ke tahap development
atau pengembangan.

Wirefrime merupakan sebuah kerangkan yang berfungsi untuk membuat tata letak suatu
desain aplikasi atau website agar sesuai dengan apa yang akan di kerjakan, umumnya tugas
ini di kerjakan oleh seorang UI/UX desainer. Umunya wirefrime berupa beberapa komponen
seperti Header, Sidebar, Content dan Footer.

Disini kita akan melihat wirefrime dari aplikasi atau website Toko Online yang akan kita
buat nantinya, ini hanya sebuah gambaran dari website yang akan kita buat nanti. berikut
ini beberapa wirefrime yang akan kita implementasikan ke dalam website nanti.

Homepage
Pada halaman homepage ini kita akan menampilkan beberapa data, seperti sliders dan
products. Kurang lebih seperti berikut ini :

17
Detail Product
Dan untuk halaman detail product disini kita akan menampilkan informasi mengenai
produk yang akan di jual, seperti gambar, judul, deskripsi dan keterangan stok maupun
berat.

18
Dan kita juga akan menampilkan ulasan dan rating yang diberikan oleh customer setelah
membeli produk tersebut. Kurang lebih seperti berikut ini :

Cart / Checkout
Halaman cart / checkout digunakan untuk menampilkan beberapa produk yang akan di
beli, dimana disini kita akan menampilkan beberapa informasi, seperti nama produk,
quantity dan harga dari produk tersebut (termasuk harga diskon).

Setelah itu, kita juga akan menampilkan form untuk mengisi beberapa data yang

19
dibutuhkan untuk proses pengiriman, seperti provinsi, kota/kabupaten.

Kita juga akan melakukan proses perhitungan biaya ongkos kirim menggunakan Raja
Ongkir, dan untuk kurir yang akan di pakai seperti JNE, TIKI dan POS. Kurang lebih seperti
berikut ini :

20
Persiapan dan Persyaratan Tools

Setelah kita mengetahui diagram alur aplikasi, struktur database dan gambaran dari
website-nya, maka sekarang kita akan lanjutkan untuk mempersiapkan beberapa tools yang
akan kita gunakan untuk mengembangkan website toko online tersebut. Beberapa tools
tersebut seperti Text Editor, Web Server, PHP, Node.js, Database, Composer dan lain-lain.

Langkah 1 - Installasi Text Editor


Untuk Text Editor disini akan direkomendasikan menggunakan Visual Studi Code, teman-
teman bisa mengunduhnya melalui link berikut ini : https://code.visualstudio.com/, silahkan
disesuaikan dengan sistem operasi yang digunakan.

Setelah Visual Studi Code berhasil terinsall, sekarang saya akan rekomendasikan beberapa
plugin yang akan kita gunakan dalam pengembangan aplikasi menggunakan Laravel dan
Nuxt.js

Plugin Visual Studio Code untuk Laravel

https://marketplace.visualstudio.com/items?itemName=amirmarmul.laravel-blade-vsc
ode
https://marketplace.visualstudio.com/items?itemName=austenc.laravel-blade-spacer
https://marketplace.visualstudio.com/items?itemName=codingyu.laravel-goto-view
https://marketplace.visualstudio.com/items?itemName=stef-k.laravel-goto-controller
https://marketplace.visualstudio.com/items?itemName=amiralizadeh9480.laravel-extr
a-intellisense

Plugin Visual Studi Code untuk JavaScript, Vue.js / Nuxt.js

https://marketplace.visualstudio.com/items?itemName=mgmcdermott.vscode-langua
ge-babel
https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html
https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint
https://marketplace.visualstudio.com/items?itemName=svipas.prettier-plus
https://marketplace.visualstudio.com/items?itemName=octref.vetur
https://marketplace.visualstudio.com/items?itemName=jcbuisson.vue
https://marketplace.visualstudio.com/items?itemName=hollowtree.vue-snippets

Plugin Visual Studi Code untuk PHP

https://marketplace.visualstudio.com/items?itemName=hakcorp.php-awesome-snippe
ts
https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephen
se-client

21
https://marketplace.visualstudio.com/items?itemName=MehediDracula.php-namespac
e-resolver
https://marketplace.visualstudio.com/items?itemName=rexshi.phpdoc-comment-vsco
de-plugin
https://marketplace.visualstudio.com/items?itemName=ronvanderheijden.phpdoc-gen
erator

Langkah 2 - Installasi Web Server


Untuk Web Server, PHP dan Database disini kita bisa menggunakan tools yang sudah di
bundle menjadi 1, seperti XAMPP atau Laragon.

Disini kita bisa memilih menggunakan XAMPP atau Laragon, sesuai dengan selera masing-
masing, kelebihan menggunakan Laragon dibandingkan XAMPP adalah dapat berganti-
ganti versi PHP dengan lebih mudah dan hal tersebut belum bisa dilakukan oleh XAMPP
untuk saat ini.

Dan kekurangan Laragon sendiri belum memiliki Database Manager seperti phpMyAdmin,
tapi kita kita bisa menginstallnya sendiri dengan mudah.

Disini disarakan menggunakan PHP versi 7.4 di dalam XAMPP ataupun Laragon. Berikut ini
link yang bisa teman-teman pakai untuk mengunduh aplikasi-nya.

Link Unduh XAMPP

Windows:
https://www.apachefriends.org/xampp-files/7.4.20/xampp-windows-x64-7.4.20-0-VC15
-installer.exe
Linux :
https://www.apachefriends.org/xampp-files/7.4.20/xampp-linux-x64-7.4.20-0-installer.r
un
Mac OSX :
https://www.apachefriends.org/xampp-files/7.4.20/xampp-osx-7.4.20-0-installer.dmg

Link Unduh Laragon

https://laragon.org/download/

Langkah 3 - Installasi Node.js


Untuk installasi Node.js sangat mudah sekali, kita bisa mengunduh binary file dari situs
resminya atau bisa menggunakan command line. Untuk installasi silahkan bisa
membukanya di link berikut ini : https://nodejs.org/en/download/

22
Silahkan pilih versi Node.js yang paling terbaru atau menggunakan versi LTS atau Long
Time Support. Untuk mengetahui apakah Node.js sudah terinstall di komputer kita, silahkan
jalankan perintah di bawah ini di terminal/CMD:

node -v

npm -v

Langkah 4 - Installasi Composer


Jika menggunakan XAMPP, maka untuk Composer kita harus melakukan installasi manual,
karena tidak ikut di dalam aplikasi XAMPP tersebut, akan tetapi jika menggunakan
Homestead maka kita tidak perlu melakukan installasi Composer, karena secara default,
paket Composer sudah tersedia di dalamnya. Berikut ini link untuk melakukan installasi
Composer dan silahkan di sesuaikan dengan sistem operasi yang digunakan.

Installation - Linux / Unix / macOS :


https://getcomposer.org/doc/00-intro.md#installation-linux-unix-macos
Installation - Windows : https://getcomposer.org/doc/00-intro.md#installation-windows

Untuk memverifikasi apakah Composer sudah berhasil terinstall di dalam komputer, kita
bisa menjalankan perintah berikut ini di dalam CMD/terminal :

composer

23
Langkah 5 - Installasi Postman
Postman merupakan salah satu tool yang wajib dimiliki oleh seorang backend developer.
Postman sendiri merupakan sebuah tool yang digunakan untuk melakukan API testing.
Postman sendiri memiliki fitur yang dapat digunakan secara personal maupun bersamaan
(teamwork).

Enaknya lagi, Postman juga dapat melakukan generate sebuah dokumentasi Api dari
proses testing API yang pernah dilakukan. Karena di dalam buku ini kita akan membuat dan
menguji sebuah Rest API, maka kita perlu melakukan installasi Postman terlebih dahulu.

Untuk installasi Postman bisa disesuaikan dengan sistem operasi yang digunakan dan
untuk mendapatkan tool tersebut kita bisa masuk di link berikut ini :
https://www.postman.com/downloads/

Silahkan di install dan sesuaikan dengan sistem operasi yang digunakan, jika berhasil maka
kurang lebih tampilannya seperti berikut ini :

24
25
Membuat Project Baru Laravel Dengan Composer

Jika kita lihat di dokumentasi resmi dari Laravel ada banyak sekali cara untuk membuat
project baru, yaitu menggunakan Sails Service (Based Docker), Laravel Installer,
Composer Create Project.

Di dalam buku ini kita akan belajar membuat project Laravel baru menggunakan perintah
composer create-project. Dengan perintah ini kita dapat melakukan installasi dengan
menyertakan spesifik versi Laravel yang digunakan.

Langkah 1 - Membuat Project Laravel


Pada tahap ini kita semua akan belajar bagaimana cara membuat project Larvel baru
menggunakan Composer, sekarang silahkan masuk ke dalam folder dimana kita akan
menyimpan project tersebut, jika menggunakan XAMPP umumnya berada di dalam folder
htdocs. Kemudian jalankan perintah berikut ini di dalam terminal/CMD :

composer create-project laravel/laravel:^8.0 backend-ecommerce

Perintah di atas akan membuat sebuah project Laravel baru dengan nama backend-
ecommerce dan versi yang akan digunakan adalah 8.x.x. Silahkan tunggu proses installasi
sampai selesai dan pastikan komputer terhubung dengan internet, karena semua paket
akan diunduh secara online.

26
Langkah 2 - Menjalankan Project Laravel
Setelah proses installasi project selesai, sekarang kita akan belajar bagaimana cara
menjalankan project Laravel tersebut, silahkan jalankan perintah berikut ini :

cd backend-ecommerce

Perintah cd digunakan untuk melakukan enter atau masuk ke dalam sebuah direktori/folder,
dalam contoh di atas kita masuk ke dalam folder backend-ecommerce, dimana folder ini
merupakan project Laravel-nya. Setelah itu jalankan perintah berikut ini :

php artisan serve

Jika perintah di atas berhasil dijalankan, maka project kita akan di jalankan di dalam
localhost menggunakan port 8000, kita bisa membukanya di dalam web browser dengan
mengetikkan http://localhost:8000, jika berhasil, maka kurang lebih tampilannya seperti
berikut ini :

Jika sudah muncul hasil seperti gambar di atas, maka kita telah berhasil melakukan installasi
dan menjalankan project Laravel-nya.

27
Langkah 3 - Menjalankan Storage Link
Sekarang kita akan melakukan proses symlink folder storage di dalam Laravel, dimana
folder storage ini agar dapat di akses melalui folder public. Silahkan jalankan perintah
berikut ini di terminal/CMD:

php artisan storage:link

Jika berhasil, maka teman-teman akan melihat file/folder dengan nama storage di dalam
folder public.

28
Membuat Helpers di Laravel

Helpers merupakan sebuah fungsi yang sering digunakan di dalam aplikasi dan sifatnya
global, jadi bisa dipanggil dimana saja di dalam aplikasi. Contohnya jika kita ingin mengubah
format mata uang menjadi Rupiah di dalam template, mengubah taanggal menjadi format
Indonesia dan lain-lain. Di dalam Laravel sendiri ada berbagai macam cara pembuatan
helpers, dan disini kita akan belajar bagaimana cara membuat helpers di Laravel dengan
benar sesuai aturan best practice dan mudah tentunya.

Langkah 1 - Membuat Helpers


Sekarang, silahkan buka terlebih dahulu project-nya menggunakan text editor, bisa VS
Code dan lain-lain, ini akan mempermudah kita dalam proses development.

Setelah project berhasil dibuka menggunakan Text Editor seperti gambar di atas, sekarang
kita lanjutkan untuk proses bagaimana cara membuat helpers-nya.

Silahkan buat folder baru dengan nama Helpers di dalam folder app, setelah itu di dalam
folder Helpers silahkan buat file baru dengan nama helpers.php dan masukkan kode
berikut ini :

29
<?php

if (! function_exists('moneyFormat')) {
/**
* moneyFormat
*
* @param mixed $str
* @return void
*/
function moneyFormat($str) {
return 'Rp. ' . number_format($str, '0', '', '.');
}
}

Dari penambahan kode helpers di atas, kita menambahkan function yang bernama
moneyFormat. Fungsinya akan digunakan untuk mengubah sebuah nilai integer atau angka
menjadi format mata uang yang dipisahkan berdasarkan satuan menggunakan notasi .. Dan
menambahkan kata Rp. di depannya. Contohnya, jika kita ingin mem-format angka 10000
maka hasilnya akan menjadi Rp. 10.000.

30
<?php

$angka = 10000;

//format angka dengan helper


$hasil = moneyFormat($angka);

echo $hasil;

//hasilnya akan menampilkan seperti berikut ini : Rp. 10.000

?>

Di dalam function moneyFormat kita menggunakan fungsi bawaan dari PHP yaitu
number_format, dimana di dalam fungsi tersebut memiliki 4 parameter, kurang lebih
seperti berikut ini :

number_format(angka, angka_di_belakang_koma, pemisah_desimal,


pemisah_ribuan);

angka - merupakan parameter pertama, parameter ini berisi nilai angka yang akan di
format.
angka_dibelakan_koma - parameter ini bersifat opsional dan digunakan untuk
menentukan jumlah angka desimal (angka dibelakang koma) yang dibutuhkan.
Apabila tidak diisi, akan dianggap sebagai 0.
pemisah_desimal - parameter ini akan berisi tanda untuk dijadikan pemisah angka
desimal, disini bisa kita kosongkan saja.
pemisah_ribuan - parameter ini yang akan digunakan untuk memisahkan angka
ribuan dengan menggunakan notasi ..

Langkah 2 -Register Helpers


Agar helpers dapat digunakan secara global di dalam project, maka kita harus melakukan
register di dalam file composer.json. Silahkan buka file composer.json yang berada di
dalam root project, kemudian pada bagian autoload tambahkan kode seperti berikut ini :

31
"autoload": {
......
"files": [
"app/Helpers/helpers.php"
]
},

Kurang lebih seperti berikut ini :

Setelah itu, simpan file composer.json dan jalankan perintah berikut ini :

composer dump-autoload

32
Dan sekarang, helpers kita sudah bisa digunakan secara global di dalam project.

33
Upgrade Versi Laravel

Halaman ini akan di update ketika ada pembaruan versi Laravel

34
Koneksi Database di Laravel

Karena website yang kita akan bangun bersifat dinamis, maka kita perlu menggunakan
sebuah database untuk menyimpan data tersebut, dimana data tersebut dapat kita
manipulasi sesuai dengan keinginan, seperti menambah, edit dan hapus. Di Laravel untuk
menghubungkan ke dalam database sangatlah mudah, kita cukup mendefinisikan beberapa
konfigurasi di dalam file .env, maka website kita sudah bisa terhubung dengan database.

Langkah 1 - Konfigurasi Koneksi Database di Laravel


Sekarang kita akan belajar bagaimana cara menghubungkan project website toko online
dengan database, silahkan buka file .env dan cari kode berikut ini :

DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

Dan ubahlah menjadi seperti berikut ini :

DB_DATABASE=db_backend_ecommerce
DB_USERNAME=root
DB_PASSWORD=

35
Di atas, kita atur untuk DB_DATABASE menggunakan db_backend_ecommerce dan untuk
DB_PASSWORD silahkan disesuaikan dengan konfigurasi dari masing-masing MySQL yang
digunakan, jika menggunakan XAMPP, secara default adalah kosong atau kita tidak perlu
mengisinya.

Sekarang kita lanjutkan membuat databasenya melalui PhpMyAdmin. Silahkan aktifkan


fitur MySQL di XAMPP (Jika menggunakan XAMPP) dan buka link berkut ini di dalam web
browser : http://localhost/phpmyadmin dan silahkan buat database baru dengan nama
db_backend_ecommerce, kurang lebih seperti berikut ini :

36
Membuat Model dan Migration

Di dalam kebanyakan framework pasti menggunakan arsitektur/pola yang disebut MVC atau
kepanjangan dari (Model, View dan Controller). Laravel juga menerapkan arsitektur/pola
MVC di dalam core systemnya, dengan menggunakan arsitektur ini maka proses
pembuatan aplikasi akan dipisahkan berdasarkan fungsinya masing-masing. Model
merupakan bagian yang digunakan untuk menangani query data dari database. View
digunakan untuk menampilkan sesuatu di layar web browser, biasanya berupa kode-kode
seperti HTML, CSS dan JavaScript. Dan sedangkan untuk Controller digunakan untuk
menangani logika dan menghubungkan antara Model dengan View.

Migration merupakan version control untuk database, dimana kita bisa membuat schema
database dan membagikannya dengan tim yang lain secara mudah. Jika sebelumnya kita
membuat attribute secara manual di dalam database, maka dengan migration kita tidak
perlu melakukan hal seperti itu lagi, seperti membuat table, menambahkan attribute,
mengubah tipe data dan lain-lain.

Pada bab perancangan dan materi struktur database, kita sudah melihat beberapa table-
table yang akan kita gunakan nantinya dan kali ini kita akan belajar bagaimana cara
membuat Model dan juga Migration untuk melakukan generate table-table tersebut ke
dalam database.

Langkah 1 - Membuat Model dan Migration Customer


Di dalam Laravel untuk membuat sebuah Model dan Migration bisa dilakukan secara
otomatis menggunakan perintah command line, ini akan sangat berguna sekali jika kita
sedang mengembangkan sebuah website atau aplikasi dengan waktu yang singkat.

37
Sekarang kita coba untuk membuat sebuah Model dan juga Migration untuk table
customers, silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan
berada di dalam project Laravel-nya :

php artisan make:model Customer -m

Perintah di atas, digunakan untuk melakukan generate sebuah Model dengan nama
Customer dan karena kita menambahkan flag -m, maka kita juga akan menyertakan
sebuah Migration-nya.

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 2 file baru di dalam
project Laravel, yang berad di dalam folder :

app/Models/Customer.php
database/migrations/2021_07_08_005044_create_customers_table.php

CATATAN : untuk nama file migration akan diambil dari tanggal pembuatannya.

Setelah berhasil melakukan generate Model dan Migration, maka sekarang kita lanjutkan
untuk melakukan beberapa penambahan kode di dalamnya.

Pertama kita akan belajar untuk menambahkan beberapa attribute di dalam table
customers, silahkan buka file migrationnya di
database/migrations/2021_07_08_005044_create_customers_table.php dan
pada function up silahkan diubah menjadi seperti berikut ini :

public function up()


{
Schema::create('customers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}

Dari perubahan kode di atas, kita menambahkan 4 attribute, yaitu :

38
name - menggunakan tipe data string.
email - menggunakan tipe data string dan bersifat unique, artinya isi dari
attribute ini tidak boleh ada yang sama.
email_verified_at - menggunakan tipe data timestamp dan di atur menjadi
nullable, artinya attribute ini tidak wajib diisi.
password - menggunakan tipe data string.

Setelah menambahkan beberapa attribute di file migration, sekarang kita lanjutkan di dalam
Model Customer, yaitu menambahkan mass assigment, ini bertujuan agar attribute yang
sudah kita tambahkan di atas dapat melakukan manipulasi data, seperti proses insert,
update dan delete. Silahkan buka file app/Models/Customer.php dan ubah semua kode-
nya menjadi seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Customer extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'email', 'email_verified_at', 'password',
'remember_token'
];
}

Di atas kita menambahkan properti $fillable, dimana properti tersebut digunakan untuk
mass assigment dari attribute yang sudah kita tambahkan di file migration.

Langkah 2 - Membuat Model dan Migration Category


Sekarang mari kita lanjutkan lagi untuk membuat Model dan juga Migration untuk Category.
Silahkan jalankan perntah berikut ini di dalam terminal/CMD dan tentunya di dalam project
Laravel-nya :

39
php artisan make:model Category -m

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 2 file baru yang
berada di dalam folder :

app/Models/Category.php
database/migrations/2021_07_08_013658_create_categories_table.php

Seperti sebelumnya, pertama kita akan lakukan penambahan beberapa attribute terlebih
dahulu di dalam file migration. Silahkan buka file
database/migrations/2021_07_08_013658_create_categories_table.php dan
pada function up silahkan ubah kode-nya menjadi seperti berikut ini :

public function up()


{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug');
$table->string('image');
$table->timestamps();
});
}

Dari perubahan kode di atas, kitaa menambahkan 3 attribute, yaitu :

name - menggunakan tipe data string


slug - menggunakan tipe data string
image - menggunakan tipe data string

Setelah berhasil menambahkan beberapa attribute di dalam file migration, maka sekarang
kita lanjutkan untuk menambahkan properti $fillable di dalam model. Silahkan buka file
app/Models/Category.php dan ubah semua kode-nya menjadi seperti berikut ini :

40
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'slug', 'image'
];
}

Langkah 3 - Membuat Model dan Migration Product


Kita lanjutkan untuk membuat Model dan juga Migration untuk table products, sekarang
silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:model Product -m

Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru yang berada di dalam
folder :

app/Models/Product.php
database/migrations/2021_07_09_004905_create_products_table.php

Pertama, kita akan menambahkan beberapa attribute dulu di dalam file migration, attribute
ini yang nantinya akan di generate di dalam table products.

Silahkan buka file


database/migrations/2021_07_09_004905_create_products_table.php dan
pada function up ubah kode-nya menjadi seperti berikut ini :

41
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('image');
$table->string('title');
$table->string('slug');
$table->unsignedBigInteger('category_id');
$table->unsignedBigInteger('user_id');
$table->text('description');
$table->integer('weight');
$table->bigInteger('price');
$table->integer('stock');
$table->integer('discount');
$table->timestamps();

//relationship category
$table->foreign('category_id')->references('id')->on('categories');

//relationship user
$table->foreign('user_id')->references('id')->on('users');
});
}

Dari perubahan kode di atas, kita menambahkan 10 attribute baru, yaitu :

image - menggunakan tipe data string.


title - menggunakan tipe data string.
slug - mengggunakan tipe data string.
category_id - menggunakan jenis unsignedBigInteger, yang artinya attribute
ini akan digunakan untuk melakukan relasi dengan table categories.
user_id - menggunakan jenis unsignedBigInteger, yang artinya attribute ini
akan digunakan untuk melakukan relasi dengan table users.
description - menggunakan tipe data text.
weight - menggunakan tipe data integer.
price - menggunakan tipe data bigInteger.
stock - menggunakan tipe data integer.
discount - menggunakan tipe data integer.

Setelah itu, kita juga menambahkan 2 relasi, yaitu :

42
//relationship category
$table->foreign('category_id')->references('id')->on('categories');

Di atas, kita mereferensikan attribute category_id sebagai foreign key ke dalam attribute
id yang ada di dalam table categories.

//relationship user
$table->foreign('user_id')->references('id')->on('users');

Di atas, kita mereferensikan attribute user_id sebagai foreign key ke dalam attribute id
yang ada di dalam table users.

Setelah berhasil menambahkan beberapa attribute di dalam file migration, maka sekarang
kita lanjutkan untuk menambahkan properti $fillable atau yang biasa disebut dengan
Mass Assigment di dalam Model. Silahkan buka file app/Models/Product.php dan ubah
semua kode-nya menjadi seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'title', 'slug', 'category_id', 'user_id',
'description', 'weight', 'price', 'stock', 'discount'
];
}

43
Langkah 4 - Membuat Model dan Migration Cart
Kita lanjutkan untuk membuat Model dan Migration untuk Cart, silahkan jalankan perintah
berikut ini di dalam terminal/CMD dan tentunya di dalam project Laravel.

php artisan make:model Cart -m

Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru yang berada di dalam
folder :

app/Models/Cart.php
database/migrations/2021_07_09_013817_create_carts_table.php

Pertama, tentu saja kita akan lakukan seperti langkah-langkah sebelumnya, yaitu
menambahakan beberapa attribute di dalam file migration, silahkan buka file
database/migrations/2021_07_09_013817_create_carts_table.php dan pada
function up ubah menjadi seperti berikut ini :

public function up()


{
Schema::create('carts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('product_id');
$table->unsignedBigInteger('customer_id');
$table->integer('qty');
$table->bigInteger('price');
$table->integer('weight');
$table->timestamps();

//relationship product
$table->foreign('product_id')->references('id')->on('products');

//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');
});
}

Dari perubahan kode di atas, kita menambahkan 5 attribute baru, yaitu :

product_id - menggunakan jenis unsignedBigInteger, yang artinya attribute ini


akan digunakan untuk melakukan relasi dengan table products.

44
customer_id - menggunakan jenis unsignedBigInteger, yang artinya attribute
ini akan digunakan untuk melakukan relasi dengan table customers.
qty - menggunakan tipe data integer.
price - menggunakan tipe data bigInteger.
weight - menggunakan tipe data integer.

Di atas kita juga menambahkan 2 relasi, yaitu ke dalam table products dan customers,
kurang lebih seperti berikut ini :

//relationship product
$table->foreign('product_id')->references('id')->on('products');

Di atas, kita mereferensikan attribute product_id ke dalam attribute id yang ada di dalam
table products.

//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');

Di atas, kita mereferensikan attribute customer_id ke dalam attribute id yang ada di


dalam table customers.

Setelah berhasil membuat beberapa attribute dan relasi di dalam file migration, maka
sekarang kita lanjutkan untuk menambahkan Mass Assigment di dalam Model. Silahkan
buka file app/Models/Cart.php dan ubah semua kode-nya menjadi seperti berikut ini :

45
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Cart extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'product_id', 'customer_id', 'qty', 'price', 'weight'
];
}

Langkah 5 - Membuat Model dan Migration Province


Sekarang kita lanjutkan untuk membuat Model dan juga Migration untuk Province, silahkan
jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:model Province -m

Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru yang berada di dalam
folder :

app/Models/Province.php
database/migrations/2021_07_09_015943_create_provinces_table.php

Pertama, kita akan menambahkan beberapa attribute terlebih dahulu di dalam file migration
provinces, silahkan buka file
database/migrations/2021_07_09_015943_create_provinces_table.php dan
pada function up silahkan ubah kode-nya menjadi seperti berikut ini :

46
public function up()
{
Schema::create('provinces', function (Blueprint $table) {
$table->id();
$table->integer('province_id');
$table->string('name');
$table->timestamps();
});
}

Dari perubahan kode di atas, kita menambahkan 2 attribute baru, yaitu :

province_id - menggunakan tipe data integer.


name - menggunakan tipe data string.

Setelah itu, kita lanjutkan untuk menambahkan Mass Assigment di dalam Model province,
silahkan buka file app/Models/Province.php dan ubah semua kode-nya menjadi seperti
berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Province extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'province_id', 'name'
];
}

47
Langkah 6 - Membuat Model dan Migration City
Setelah berhasil membuat Model dan Migration untuk Province, maka sekarang kita
lanjutkan untuk membuatnya untuk City, silahkan jalankan perintah berikut ini di dalam
terminaal/CMD :

php artisan make:model City -m

Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru yang berada di dalam
folder :

app/Models/City.php
database/migrations/2021_07_09_021738_create_cities_table.php

Pertama, kita akan menambahkan beberapa attribute terlebih dahulu di dalam file
migration, silahkan buka file
database/migrations/2021_07_09_021738_create_cities_table.php dan ubah
pada function up menjadi seperti berikut ini :

public function up()


{
Schema::create('cities', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('province_id');
$table->integer('city_id');
$table->string('name');
$table->timestamps();
//relationship province
$table->foreign('province_id')->references('id')->on('provinces');
});
}

Dari perubahan kode di atas, kita menambahkan 3 attribute baru, yaitu :

province_id - menggunakan jenis unsignedBigInteger, yang artinya attribute


ini akan digunakan untuk melakukan relasi dengan table provinces.
city_id - menggunakan tipe data integer.
name- menggunakan tipe data string.

Dan di atas kita juga menambahkan 1 relasi ke dalam table provinces, yaitu :

48
//relationship province
$table->foreign('province_id')->references('id')->on('provinces');

Dari kode di atas, attribute province_id kita jadikan sebagai foreign key dan kita
referensikan ke dalam attribute id yang ada di dalam table provinces.

Setelah berhasil menambahkan beberapa attribute dan relasi di dalam file migration, maka
sekarang kita lanjutkan untuk menambahkan properti $fillable atau Mass Assigment di
dalam Moedel. Silahkan buka file app/Models/City.php dan ubah semua kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class City extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'province_id', 'city_id', 'name'
];
}

Langkah 7 - Membuat Model dan Migration Invoice


Kita lanjutkan untuk membuat Model dan juga Migration untuk Invoice, silahkan jalankan
perintah berikut ini di dalam terminal/CMD :

49
php artisan make:model Invoice -m

Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru di dalam folder :

app/Models/Invoice.php
database/migrations/2021_07_09_025035_create_invoices_table.php

Pertama, kita akan lakukan sedikit perubahan dan di dalam file migration, yaitu
menambahkan beberapa attribute di dalamnya. Silahkan buka file
database/migrations/2021_07_09_025035_create_invoices_table.php dan
pada function up ubah kode-nya menjadi seperti berikut ini :

50
public function up()
{
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->string('invoice');
$table->unsignedBigInteger('customer_id');
$table->string('courier');
$table->string('courier_service');
$table->bigInteger('courier_cost');
$table->integer('weight');
$table->string('name');
$table->string('phone');
$table->unsignedBigInteger('city_id');
$table->unsignedBigInteger('province_id');
$table->text('address');
$table->enum('status', array('pending', 'success', 'expired',
'failed'));
$table->bigInteger('grand_total');
$table->string('snap_token')->nullable();
$table->timestamps();

//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');

//relationship city
$table->foreign('city_id')->references('id')->on('cities');

//relationship province
$table->foreign('province_id')->references('id')->on('provinces');
});
}

Dari perubahan di atas, kita menambahkan 14 attribute baru di dalamnya, yaitu :

invoice - menggunakan tipe data string.


customer_id - menggunakan jenis unsignedBigInteger, yang artinya attribute
ini akan digunakan untuk melakukan relasi dengan table customers.
courier - menggunakan tipe data string.
courier_service - menggunakan tipe data string.
courier_cost - menggunakan tipe data bingInteger.
weight - menggunakan tipe data integer.
name - menggunakan tipe data string.
phone - menggunakan tipe data string.
city_id - menggunakan jenis unsignedBigInteger, yang artinya attribute ini

51
akan digunakan untuk melakukan relasi dengan table cities.
province_id - menggunakan jenis unsignedBigInteger, yang artinya attribute
ini akan digunakan untuk melakukan relasi dengan table provinces.
address - menggunakan tipe data text.
status - menggunakan tipe data enum dan default nilai-nya adalah pending,
success, expired, failed.
grand_total - menggunakan tipe data bigInteger.
snap_token - menggunakan tipe data string dan di set menjadi nullable, yang
artinya tidak wajib diisi.

Setelah menambahkan attribute, kita juga menambahkan 3 relasi, yaitu :

//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');

Di atas, attribute customer_id dijadikan sebagai foreign key dan mereferensikan ke dalam
id yang ada di dalam table customers.

//relationship city
$table->foreign('city_id')->references('id')->on('cities');

Di atas, attribute city_id dijadikan sebagai foreign key dan akan di hubungkan ke dalam
attribute id yang ada di dalam table cities.

//relationship province
$table->foreign('province_id')->references('id')->on('provinces');

Di atas, attribute province_id dijadikan sebagai foreign key dan mereferensikan ke dalam
id yang ada di dalam table provinces.

Setelah berhasil membuat relasi dan menambahkan beberapa attribute di dalam file
migration, maka sekarang kita akan lanjutkan untuk membuat Mass Assigment di dalam
model. Silahkan buka file app/Models/Invoice.php dan ubah semua kode-nya menjadi
seperti berikut ini :

52
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Invoice extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'customer_id', 'courier', 'courier_service',
'courier_cost', 'weight', 'name', 'phone', 'city_id', 'province_id',
'address', 'status', 'grand_total', 'snap_token'
];
}

Langkah 8 - Membuat Model dan Migration Order


Setelah Model dan Migration Invoice selesai, sekarang kita lanjutkan untuk membuat Model
dan Migration untuk Order. Order disini akan digunakan untuk menyimpan data-data produk
yang di beli yang berdasarkan invoice tertentu, jadi nantinya 1 invoice bisa memiliki banyak
data order.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan jangan lupa harus berada
di dalam project Laravel.

php artisan make:model Order -m

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 2 file baru yang
berada di dalam folder :

app/Models/Order.php
database/migrations/2021_07_10_001434_create_orders_table.php

53
Pertama, tentu saja kita akan menambahkan beberapa attribute terlebih dahulu di dalam
file migration. Silahkan buka file
database/migrations/2021_07_10_001434_create_orders_table.php dan pada
function up ubah menjadi seperti berikut ini :

public function up()


{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('invoice_id');
$table->unsignedBigInteger('product_id');
$table->integer('qty');
$table->bigInteger('price');
$table->timestamps();

//relationship invoice
$table->foreign('invoice_id')->references('id')->on('invoices');

//relationship product
$table->foreign('product_id')->references('id')->on('products');
});
}

Dari perubahan kode di atas, kita menambahkan 4 attribute baru, yaitu :

invoice_id - menggunakan jenis unsignedBigInteger, yang artinya attribute ini


akan digunakan untuk melakukan relasi dengan table invoices.
product_id - menggunakan jenis unsignedBigInteger, yang artinya attribute ini
akan digunakan untuk melakukan relasi dengan table products.
qty - menggunakan tipe data integer.
price - menggunakan tipe data bigInteger.

Kemudian, di dalam penambahan kode di atas, kita juga membuat 2 relasi baru, yaitu :

//relationship invoice
$table->foreign('invoice_id')->references('id')->on('invoices');

Artinya, attribute invoice_id dijadikan sebagai foreign key dan mereferensikan attribute
id yang ada di dalam table invoices.

54
//relationship product
$table->foreign('product_id')->references('id')->on('products');

Artinya, attribute product_id dijadikan sebagai foreign key dan mereferensikan attribute
id yang ada di dalam table products.

Setelah semua attribute berhasil di tambahkan di dalam file migration, maka sekarang kita
lanjutkan untuk menambahkan Mass Assigment atau properti $fillable di dalam Model.
Silahkan buka file app/Models/Order.php dan ubah semua kode-nya menjadi seperti
berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Order extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice_id', 'product_id', 'qty', 'price'
];
}

Langkah 9 - Membuat Model dan Migration Review


Sekarang kita lanjutkan membuat Model dan Migration untuk Review, silahkan jalankan
perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di dalam project
Laravel.

55
php artisan make:model Review -m

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 2 file baru di dalam
folder :

app/Models/Review.php
database/migrations/2021_07_10_003132_create_reviews_table.php

Setelah berhasil melakukan generate file, maka sekarang kita lanjutkan untuk
menambahkan beberapa attribute di dalam file migration. Silahkan buka file
database/migrations/2021_07_10_003132_create_reviews_table.php dan pada
function up ubah kode-nya menjadi seperti berikut ini :

public function up()


{
Schema::create('reviews', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('product_id');
$table->unsignedBigInteger('order_id');
$table->unsignedBigInteger('customer_id');
$table->integer('rating');
$table->text('review');
$table->timestamps();

//relationship product
$table->foreign('product_id')->references('id')->on('products');

//relationship order
$table->foreign('order_id')->references('id')->on('orders');
//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');
});
}

Dari perubahan kode di atas, kita menambahkan 5 attribute, yaitu :

product_id - menggunakan jenis unsignedBigInteger, yang artinya attribute ini


akan digunakan untuk melakukan relasi dengan table products.
order_id - menggunakan jenis unsignedBigInteger, yang artinya attribute ini
akan digunakan untuk melakukan relasi dengan table orders.
customer_id - menggunakan jenis unsignedBigInteger, yang artinya attribute
ini akan digunakan untuk melakukan relasi dengan table customers.

56
rating -menggunakan tipe data integer.
review - menggunakan tipe data text.

kemudian kita menambahkan 3 relasi, yaitu :

//relationship product
$table->foreign('product_id')->references('id')->on('products');

Di atas, kita atur agar attribute product_id menjadi foreign key dan mereferensikan ke
attribute id yang ada di dalam table products.

//relationship order
$table->foreign('order_id')->references('id')->on('orders');

Di atas, kita atur agar attribute order_id menjadi foreign key dan mereferensikan ke
attribute id yang ada di dalam table orders.

//relationship customer
$table->foreign('customer_id')->references('id')->on('customers');

Di atas, kita atur agar attribute customer_id menjadi foreign key dan mereferensikan ke
attribute id yang ada di dalam table customers.

Setelah berhasil menambahkan beberapa attribute dan relasi, maka kita akan
menambahkan Mass Assigment di dalam Model, yang bertujuan agar attribute yang sudah
kita definisikan dapat melakukan manipulasi data di dalam database. Silahkan buka file
app/Models/Review.php dan ubah semua kode-nya menjadi seperti berikut ini :

57
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Review extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'rating', 'review', 'product_id', 'order_id', 'customer_id'
];
}

Langkah 10 - Membuat Model dan Migration Slider


Terakhir, kita akan membuat Model dan Migration untuk Slider, silahkan jalankan perintah
berikut ini di dalam terimal/CMD dan pastikan sudah berada di dalam project Laravel.

php artisan make:model Slider -m

Jika perintah di atas berhasil, maka kita akan mendapatkan 2 file baru yang berada di dalam
folder :

app/Model/Slider.php
database/migrations/2021_07_10_014717_create_sliders_table.php

Pertama, tentu saja kita akan melakukan penambahan beberapa attribute di dalam file
migration, silahkan buka file
database/migrations/2021_07_10_014717_create_sliders_table.php dan pada
function up ubah menjadi seperti berikut ini :

58
public function up()
{
Schema::create('sliders', function (Blueprint $table) {
$table->id();
$table->string('image');
$table->string('link');
$table->timestamps();
});
}

Dari perubahan kode di atas, kita menambahkan 2 attribute baru, yaitu :

image - menggunakan tipe data string.


link - menggunakan tipe data string.

Setelah berhasil menambahkan attribute, maka selanjutkanya kita akan menambahkan


Mass Assigment di dalam Model, silahkan buka file app/Models/Slider.php dan ubah
semua kode-nya menjadi seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Slider extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'link'
];
}

59
Langkah 11 - Menjalankan Migration Database
Setelah kita berhasil membuat beberapa file migration untuk kebutuhan table dari website
yang akan kita bangun, sekarang kita akan menjalankan perintah migrate, yang mana
perintah tersebut akan melakukan generate dari file-file migrasi ke dalam sebuah table
beserta attribute di dalamnya.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan migrate

Jika berhasil kita akan mendapatkan beberapa table di dalam database, kurang lebih seperti
berikut ini :

60
61
Eloquent Relationships

Saat seorang software developer mengembangkan aplikasi yang cukup besar, tentu saja
akan membutuhkan banyak table di dalamnya dan memungkinkan juga data di dalam table
tersebut akan dipisah-pisah lagi ke table yang lain, itu biasanya disebut dengan normalisasi
database. Seperti one-to-one, one-to-many dan many-to-many.

Karena dalam membangun aplikasi tidak mungkin kita akan menyimpan sebuah data di
dalam 1 table, apalagi aplikasi yang di kembangkan sangat besar, maka dengan relasi atau
normalisasi kita bisa memecahkan problem tersebut, yaitu memisahkan menjadi beberapa
table yang saling berhubungan.

Di dalam Laravel kita akan sangat terbantu dengan adanya fitur Eloquent Relationship,
yang bertujuan untuk mempermudah pada software developer dalam melakukan
normalisasi database. Berikut ini contoh normalisasi dari database.

One to one
One to one relationship terjadi jika data di dalam table A hanya boleh memiliki satu data
di dalam table B (A has one B) dan data B hanya boleh dimiliki oleh data di table A (B
Belongs to A).

Dalam kasus ini, kita cukup memberikan sebuah foreign key di dalam table B, dimana
foreign key ini akan diisi dengan ID yang ada di dalam table A. Contohnya seperti berikut ini
:

table users

62
id name

1 Fika Ridaul Maulayya

2 Rizqi Maulana

3 Yudi Purwanto

table phones

id no_telephone user_id

1 085785852224 2

2 081200046719 1

3 089223918038 3

field user_id di dalam table phones akan menyimpan nilai id dari table users, dengan
begitu maka relasi one-to-one bisa terpenuhi.

One to many
One-to-many relathionship memiliki struktur relasi yang hampir sama dengan one-to-
one, perbedaanya adalah satu data di dalam table A boleh memiliki banyak data di dalam
table B. Contoh sederhananya adalah satu data post bisa memiliki banyak komentar.

Perbedaan antara One to Many dan One to Many (Inverse) bisa di ibaratkan contoh
seperti ini :

One to Many - sebuah post hanya bisa memanggil data komentar dan data
komentar tidak bisa memanggil induk dari data post, yang artinya hanya satu arah
saja.
One to Many (Inverse) - sebuah post bisa memanggil data komentar, kemudian
data komentar juga dapat memanggil data induk post, yang artinya bisa dua arah.

63
Jika kita lihat dari gambar diatas antara relasi one-to-one dan one-to-many tidak ada
perbedaan sama sekali, perbedaanya adalah di dalam foreign key pada table comments
tidak bersifat unique, sehingga dapat menyimpan data yang sama, kalau di dalam relasi
one-to-one foreign key bersifat unique, artinya tidak ada data yang boleh sama.

table posts

id title

1 Judul Post 1

2 Judul Post 2

3 Judul Post 3

table comments

id comment post_id

1 Mantap 2

2 Sukses selalu SantriKoding 2

3 Terima Kasih 3

4 Sukses tanpa ada error 1

5 Mantap bener tutorialnya 3

field foreign key post_id di dalam table comments dapat menyimpan id yang sama dari
table posts, dengan begini maka relasi one-to-many dapat terpenuhi.

64
Many to many
Many-to-many relationships merupakan relasi dimana data di dalam table A bisa memiliki
banyak data di dalam table B dan juga sebaliknya, data di dalam table B bisa memiliki
banyak data di dalam table A. Disini kita tidak dapat melakukan relasi langsung antara table
A ke dalam table B, maka dari itu kita harus memiliki satu table lagi untuk menghubungkan
dari kedua table tersebut, biasanya table ini di sebut dengan pivot table. Kurang lebih
seperti berikut ini :

Dari gambar di atas bisa kita lihat bersama-sama, kita memiliki satu table lagi yang
bernama post_tag, table inilah yang disebut dengan pivot table, dimana akan
difungsikan untuk menyimpan id dari masing-masing table yang berhubungan.

table posts

id title

1 Judul Post 1

2 Judul Post 2

table tags

id name

1 Laravel

2 Vue Js

3 React Js

65
id name

4 Node Js

table post_tag (Pivot Table)

id post_id tag_id

1 1 3

2 1 4

3 2 1

4 1 1

5 2 2

Di dalam table post_tag atau pivot table terdapat dua foreign key yaitu post_id
dimana ini akan menyimpan id dari table posts dan tag_id akan menyimpan id dari
table tags. Dengan begini maka bisa dikatakan sebagai relasi many-to-many.

Setelah mengetahui gambaran dasar tentang normalisasi database, maka sekarang kita
akan coba implementasikan ke dalam project yang sedang kita kembangkan. Berikut ini
relasi yang akan kita buat :

1. ONE TO MANY - antara table users dengan table products, yang artinya 1 data
user bisa memiliki banyak data product.
2. ONE TO MANY (Inverse) - antara table categories dengan table products,
yang artinya 1 data category bisa memiliki banyak data product. Kita jadikan
Inverse, agar kita juga dapat memanggil data induk yaitu category dari data product
(dua arah).
3. ONE TO MANY - antara table invoices dengan table orders, yang artinya 1 data
invoice bisa memiliki banyak data order.
4. ONE TO MANY (Belongs To) - antara table products dengan table carts, disini
kita hanya memanggil data product dari cart. Dan kita tidak perlu membuat relasi
many dari product ke cart.
5. ONE TO MANY (Belongs To) - antara table customers dengan table carts, disini
kita hanya memanggil data customer dari cart. Dan kita tidak perlu membuat relasi
many dari customer ke cart.
6. ONE TO MANY (Inverse) - antara table customers dengan table invoices, yang
artinya 1 data customer bisa memiliki banyak data invoice. Kita jadikan Inverse,
agar kita juga dapat memanggil data induk yaitu customer dari data invoice (dua
arah).
7. ONE TO MANY (Belongs To) - antara table cities dengan table invoices, disini
kita hanya memanggil data city dari invoice. Dan kita tidak perlu membuat relasi
many dari invoice ke city.

66
8. ONE TO MANY (Belongs To) - antara table provinces dengan table invoices,
disini kita hanya memanggil data province dari invoice. Dan kita tidak perlu membuat
relasi many dari invoice ke province.
9. ONE TO MANY (Inverse) - antara table provinces dengan table cities, yang
artinya 1 data province bisa memiliki banyak data city. Kita jadikan Inverse, agar
kita juga dapat memanggil data induk yaitu province dari data city (dua arah).
10. ONE TO MANY - antara table orders dengan table reviews, yang artinya 1 data
order bisa memiliki banyak data review.
11. ONE TO MANY (Inverse) - antara table products dengan table reviews, yang
artinya 1 data product bisa memiliki banyak data review. Kita jadikan Inverse, agar
kita juga dapat memanggil data induk yaitu product dari data review (dua arah).
12. ONE TO MANY (Inverse) - antara table customers dengan table reviews, yang
artinya 1 data customer bisa memiliki banyak data review. Kita jadikan Inverse,
agar kita juga dapat memanggil data induk yaitu customer dari data review (dua
arah).
13. ONE TO MANY (Belongs To) - antara table orders dengan table products, disini
kita hanya memanggil data product dari order. Dan kita tidak perlu membuat relasi
many dari product ke order.

Langkah 1 - ONE TO MANY - antara table users dengan table


products

Pertama, kita akan mendefinisikan relasi many dari table users ke table products.
Silahkan buka file app/Models/User.php dan ubah kode-nya menjadi seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable


{
use HasFactory, Notifiable;

/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',

67
'email',
'password',
];

/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];

/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];

/**
* prooducts
*
* @return void
*/
public function products()
{
return $this->hasMany(Product::class);
}
}

Dari perubahan di atas, kita menambahkan method baru yang bernama products. Dimana
method tersebut berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi
Many ke Model Product atau table products.

Langkah 2 - ONE TO MANY (Inverse) - antara table categories


dengan table products
Sekarang, kita akan membuat relasi one-to-many antara table categories dan products,
disini pertama kita akan mendefinisikan relasi many terlebih dahulu di dalam Model

68
Category.

Silahkan buka file app/Models/Category.php dan ubah kode-nya menjadi seoerti berikut
ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'slug', 'image'
];

/**
* products
*
* @return void
*/
public function products()
{
return $this->hasMany(Product::class);
}
}

Di atas, kita menambahkan satu method baru, yaitu products. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Product atau table products.

Karena kita akan jadikan sebagai Inverse, dimana product juga bisa memanggil data
induk-nya yaitu category, yang artinya bisa dua arah. Silahkan buka file
app/Models/Product.php dan ubah semua kode-nya menjadi seperti berikut ini :

69
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'title', 'slug', 'category_id', 'user_id',
'description', 'weight', 'price', 'stock', 'discount'
];

/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}
}

Di atas, kita menambahkan method category yang di fungsikan untuk mendeklarasikan


bahwa model Category atau table categories ini dimiliki dan terhubung dengan Model
Product atau table products.

Langkah 3 - ONE TO MANY - antara table invoices dengan table


orders

Kita lanjutkan lagi untuk membuat relasi one-to-many antara table invoices dan table
orders, silahkan buka file app/Models/Invoice.php dan ubah semua kode-nya menjadi
seperti berikut ini :

70
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Invoice extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'customer_id', 'courier', 'courier_service',
'courier_cost', 'weight', 'name', 'phone', 'city_id', 'province_id',
'address', 'status', 'grand_total', 'snap_token'
];

/**
* order
*
* @return void
*/
public function orders()
{
return $this->hasMany(Order::class);
}
}

Di atas, kita menambahkan satu method baru, yaitu orders. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Order atau table orders.

Langkah 4 - ONE TO MANY (Belongs To) - antara table products


dengan table carts
Disini kita tidak perlu membuat relasi many dari product ke cart, melainkan hanya membuat
relasi belongs to / Inverse agar kita dapat memanggil data product dari cart.

71
Silahkan buka file app/Models/Cart.php dan ubah kode-nya menjadi seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Cart extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'product_id', 'customer_id', 'qty', 'price', 'weight'
];

/**
* product
*
* @return void
*/
public function product()
{
return $this->belongsTo(Product::class);
}
}

Di atas, kita menambahkan method product yang di fungsikan untuk mendeklarasikan


bahwa model Product atau table products ini dimiliki dan terhubung dengan Model Cart
atau table carts.

Langkah 5 - ONE TO MANY (Belongs To) - antara table customers


dengan table carts
Seperti sebelumnya, kita akan membuat relasi one-to-many antara table customers
dengan table carts dan disini kita tidak perlu membuat relasi many dari customer ke cart,

72
melainkan hanya membuat relasi belongs to / Inverse agar kita dapat memanggil
data customer dari cart.

Silahkan buka file app/Models/Cart.php dan ubah semua kode-nya menjadi seperti
berikut ini :

73
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Cart extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'product_id', 'customer_id', 'qty', 'price', 'weight'
];

/**
* product
*
* @return void
*/
public function product()
{
return $this->belongsTo(Product::class);
}

/**
* customer
*
* @return void
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
}

Di atas, kita menambahkan method customer yang di fungsikan untuk mendeklarasikan


bahwa model Customer atau table customers ini dimiliki dan terhubung dengan Model
Cart atau table carts.

74
Langkah 6 - ONE TO MANY (Inverse) - antara table customers
dengan table invoices
Sekarang kita lanjutkan untuk membuat relasi many dan inverse antara customer dan
invoice. Pertama kita akan membuat relasi many terlebih dahulu dari customer ke invoice,
setelah itu kita atur inverse atau belongs to-nya dari invoice ke customer.

Silahkan buka file app/Models/Customer.php dan ubah semua kode-nya menjadi seperti
berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Customer extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'email', 'email_verified_at', 'password',
'remember_token'
];

/**
* invoice
*
* @return void
*/
public function invoices()
{
return $this->hasMany(Invoice::class);
}
}

75
Di atas, kita menambahkan satu method baru, yaitu invoices. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Invoice atau table invoices.

Kemudian kita akan membuat proses inverse agar kita juga dapat memanggil customer
melalui data invoice. Silahkan buka file app/Models/Invoice.php kemudian ubah kode-
nya menjadi seperti berikut ini :

76
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Invoice extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'customer_id', 'courier', 'courier_service',
'courier_cost', 'weight', 'name', 'phone', 'city_id', 'province_id',
'address', 'status', 'grand_total', 'snap_token'
];

/**
* order
*
* @return void
*/
public function orders()
{
return $this->hasMany(Order::class);
}

/**
* customer
*
* @return void
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
}

Di atas, kita menambahkan method customer yang di fungsikan untuk mendeklarasikan

77
bahwa model Customer atau table customers ini dimiliki dan terhubung dengan Model
Invoice atau table invoices.

Langkah 7 - ONE TO MANY (Belongs To) - antara table cities


dengan table invoices
Sekarang kita akan membuat relasi one-to-many antara table cities dengan table
invoices dan disini kita tidak perlu membuat relasi many dari city ke invoice, melainkan
hanya membuat relasi belongs to / Inverse agar kita dapat memanggil data city dari
invoice.

Silahkan buka file app/Models/Invoice.php dan ubah semua kode-nya menjadi seperti
berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Invoice extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'customer_id', 'courier', 'courier_service',
'courier_cost', 'weight', 'name', 'phone', 'city_id', 'province_id',
'address', 'status', 'grand_total', 'snap_token'
];

/**
* order
*
* @return void
*/
public function orders()
{
return $this->hasMany(Order::class);

78
}

/**
* customer
*
* @return void
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}

/**
* city
*
* @return void
*/
public function city()
{
return $this->belongsTo(City::class, 'city_id', 'city_id');
}
}

Di atas, kita menambahkan method city yang di fungsikan untuk mendeklarasikan bahwa
model City atau table cities ini dimiliki dan terhubung dengan Model Invoice atau
table invoices.

Di atas karena foreign key dan primary key menggunakan attribute yang bernama
city_id, maka di dalam method-nya kita juga set. Jika menggunakan custom primary
key atau foreign key, kita bisa lakukan seperti berikut ini :

//custom foreign key dan primary key


return $this->belongsTo(Model::class, 'foreign_key', 'primary_key');

//custom foreign key


return $this->hasMany(Model::class, 'foreign_key');

Langkah 8 - ONE TO MANY (Belongs To) - antara table provinces


dengan table invoices
Sama seperti sebelumnya, kita hanya akan melakukan konfigurasi inverse-nya saja di dalam

79
invoice, agar kita dapat memanggil data induk-nya, yaitu province.

Silahkan buka file app/Models/Invoice.php dan ubah semua kode-nya menjadi seperti
berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Invoice extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice', 'customer_id', 'courier', 'courier_service',
'courier_cost', 'weight', 'name', 'phone', 'city_id', 'province_id',
'address', 'status', 'grand_total', 'snap_token'
];

/**
* order
*
* @return void
*/
public function orders()
{
return $this->hasMany(Order::class);
}

/**
* customer
*
* @return void
*/
public function customer()
{
return $this->belongsTo(Customer::class);

80
}

/**
* city
*
* @return void
*/
public function city()
{
return $this->belongsTo(City::class, 'city_id', 'city_id');
}

/**
* province
*
* @return void
*/
public function province()
{
return $this->belongsTo(Province::class, 'province_id',
'province_id');
}
}

Di atas, kita menambahkan method province yang di fungsikan untuk mendeklarasikan


bahwa model Province atau table provinces ini dimiliki dan terhubung dengan Model
Invoice atau table invoices.

Langkah 9 - ONE TO MANY (Inverse) - antara table provinces


dengan table cities
Kita lanjutkan untuk membuat relasi one-to-many (Inverse) antara table provinces
dan cities. Pertama kita akan membuat relasi many terlebih dahulu dari province ke city,
setelah itu kita membuat inverse / belongs to-nya.

Silahkan buka file app/Models/Province.php dan ubah semua kode-nya menjadi seperti
berikut ini :

81
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Province extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'province_id', 'name'
];

/**
* cities
*
* @return void
*/
public function cities()
{
return $this->hasMany(City::class, 'province_id');
}
}

Di atas, kita menambahkan satu method baru, yaitu cities. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model City
atau table cities.

Kemudian kita akan membuat proses Inverse agar kita juga dapat memanggil province
melalui data city. Silahkan buka file app/Models/City.php kemudian ubah kode-nya
menjadi seperti berikut ini :

82
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class City extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'province_id', 'city_id', 'name'
];

/**
* province
*
* @return void
*/
public function province()
{
return $this->belongsTo(Province::class, 'province_id');
}
}

Di atas, kita menambahkan method province yang di fungsikan untuk mendeklarasikan


bahwa model Province atau table provinces ini dimiliki dan terhubung dengan Model
City atau table cities.

Langkah 10 - ONE TO MANY - antara table orders dengan table


reviews

Sekarang kita akan membuat relasi one-to-many dari table orders ke table reviews.
Silahkan buka file app/Models/Order.php dan ubah semua kode-nya menjadi seperti
berikut ini :

83
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Order extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice_id', 'product_id', 'qty', 'price'
];

/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}
}

Di atas, kita menambahkan satu method baru, yaitu reviews. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model
Review atau table reviews.

Langkah 11 - ONE TO MANY (Inverse) - antara table products


dengan table reviews
Sekarang kita akan membuat relasi one-to-many (Inverse) antara table products dan
reviews. Pertama kita akan membuat relasi many terlebih dahulu dan akan dilanjutkan
inverse-nya.

84
Silahkan buka file app/Models/Product.php dan ubah semua kode-nya menjadi seperti
berikut ini :

85
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'title', 'slug', 'category_id', 'user_id',
'description', 'weight', 'price', 'stock', 'discount'
];

/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}

/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}
}

Di atas, kita menambahkan satu method baru, yaitu reviews. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model

86
Review atau table reviews.

Setelah itu, sekarang kita tinggal membuat proses inverse atau belongs to-nya. Agar data
review juga dapat mengakses data induknya yaitu product.

Silahkan buka file app/Models/Review.php dan ubah semua kode-nya menjadi seperti
berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Review extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'rating', 'review', 'product_id', 'order_id', 'customer_id'
];

/**
* product
*
* @return void
*/
public function product()
{
return $this->belongsTo(Product::class);
}
}

Di atas, kita menambahkan method product yang di fungsikan untuk mendeklarasikan


bahwa model Product atau table products ini dimiliki dan terhubung dengan Model
Review atau table reviews.

87
Langkah 12 - ONE TO MANY (Inverse) - antara table customers
dengan table reviews
Sama seperti sebelumnya, yaitu membuat relasi one-to-many (Inverse), disini kita
akan menghubungkan antara table customers dan reviews.

Silahkan buka file app/Models/Customer.php dan ubah kode-nya menjadi seperti berikut
ini :

88
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Customer extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'email', 'email_verified_at', 'password',
'remember_token'
];

/**
* invoice
*
* @return void
*/
public function invoices()
{
return $this->hasMany(Invoice::class);
}

/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}
}

Di atas, kita menambahkan satu method baru, yaitu reviews. Dimana method tersebut
berfungsi memberitahukan ke sistem, bahwa kita akan membuat relasi Many ke Model

89
Review atau table reviews.

Setelah itu, kita akan menambahkan inverse atau belongs to di dalam model Review.
Silahkan buka file tersebut di dalam app/Models/Review.php dan ubah semua kode-nya
menjadi seperti berikut ini :

90
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Review extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'rating', 'review', 'product_id', 'order_id', 'customer_id'
];

/**
* product
*
* @return void
*/
public function product()
{
return $this->belongsTo(Product::class);
}

/**
* customer
*
* @return void
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
}

Di atas, kita menambahkan method customer yang di fungsikan untuk mendeklarasikan


bahwa model Customer atau table customers ini dimiliki dan terhubung dengan Model
Review atau table reviews.

91
Langkah 13 - ONE TO MANY (Belongs To) - antara table orders
dengan table products
Terakhir, kita akan membuat relasi one-to-many (Belongs To), jadi kita akan
memanggil data product melalui data order. Silahkan buka file app/Models/Order.php
dan ubah semua kode-nya menjadi seperti berikut ini :

92
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Order extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'invoice_id', 'product_id', 'qty', 'price'
];

/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}

/**
* product
*
* @return void
*/
public function product()
{
return $this->belongsTo(Product::class);
}
}

Di atas, kita menambahkan method product yang di fungsikan untuk mendeklarasikan


bahwa model Product atau table products ini dimiliki dan terhubung dengan Model
Order atau table orders.

93
Eloquent Accessors, Mutators & Casting

aravel memiliki fitur yang bernama Mutator, Accessor dan Casting, fitur-fitur ini digunakan
untuk melakukan manipulasi data di dalam attribute database dengan sangat mudah.
Contoh sederhanya misalnya kita ingin melakukan insert data dengan enkripsi ke dalam
database dan melakukan deskripsi saat menampilkan dari database secara otomatis, maka
kita bisa menggunakan fitur ini.

Mendefinisikan sebuah Accessor


Accessor digunakan untuk melakukan manipulasi nilai attribute saat diakses, untuk
mendefinisikan sebuah accessor kurang lebih seperti berikut ini :

get{namaAttribute}Attribute

namaAttribute merupakan nama attribute yang akan di format dengan accessor,


misalnya kita ingin membuat attribute name saat diakses menjadi huruf besar atau
uppercase kita bisa mendefinisikan sebuah accessor di dalam Model kurang lebih seperti
berikut ini :

94
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model


{
/**
* Get name to Uppercase
*
* @param string $value
* @return string
*/
public function getNameAttribute($value)
{
return strtoupper($value);
}
}

Di atas, kita membuat sebuah function dengan nama getNameAttribute dan di dalamnya
kita lakukan return dengan menjadikannya sebagai huruf besar menggunakan strtouper.

Jika kita asumsikan mempunya data user dengan attribute name Fika Ridaul Maulayya,
dan jika kita coba panggil kurang lebih seperti berikut ini :

use App\Models\User;

$user = User::find(1);

$name = $user->name;

Jika di jalankan, maka akan menghasilkan seperti ini : FIKA RIDAUL MAULAYYA.

Mendefinisikan sebuah Mutator


Mutator digunakan untuk melakukan manipulasi nilai attribute saat di set, untuk
mendefinisikan mutator kurang lebih seperti berikut ini :

95
set{namaAttribute}Attribute

namaAttribute merupakan nama attribute yang akan di set menggunakan mutator,


misalnya kita ingin melakukan insert data dengan merubahnya menjadi huruf kecil atau
lowercase, kita bisa mendefinisikan sebuah mutator di dalam Model seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model


{
/**
* Set the user's first name.
*
* @param string $value
* @return void
*/
public function setNameAttribute($value)
{
$this->attributes['name'] = strtolower($value);
}
}

Setiap kita melakukan insert data name ke dalam database, maka akan di format menjadi
huruf kecil semuanya.

Mendefinisikan sebuah Casting


Attribute Casting hampir memiliki fungsi yang sama dengan accessor dan mutator, tapi
kita tidak perlu melakukan definisi sebuah method di dalam Model. Disini kita hanya
mendefinisikan sebuah properti dengan nama $cast dan bisa melakukan konversi dari
sebuah attribute ke dalam tipe data umum, seperti :

array
boolean
collection
date
datetime

96
decimal:<digits>
double
encrypted
encrypted:array
encrypted:collection
encrypted:object
float
integer
object
real
string
timestamp

Dalam kasus ini kita ambil contoh untuk melakukan konversi attribute
email_verified_at yang memiliki tipe data timestap di database, kemudian kita ubah
menjadi datetime . Kurang lebih seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model


{
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}

Setelah kita belajar tentang Laravel Mutator, Accessor dan Casting, sekarang kita akan
lanjutkan untuk menerapkan Accessor di dalam project yang sedang kita kembangkan, disini
kebetulan kita hanya butuh untuk Accessor saja.

Langkah 1 - Menambahkan Accessor di Model Category

Sekarang kita akan belajar menambahkan Accessor di dalam Model Category untuk
memanipulasi attribute image, disini kita akan membuat agar saat kita memanggil attribute

97
image akan mendapatkan hasil full-path dari attribute tersebut.

Silahkan buka file app/Models/Category.php kemudian ubah kode-nya menjadi seperti


berikut ini :

98
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'slug', 'image'
];

/**
* products
*
* @return void
*/
public function products()
{
return $this->hasMany(Product::class);
}

/**
* getImageAttribute
*
* @param mixed $image
* @return void
*/
public function getImageAttribute($image)
{
return asset('storage/categories/' . $image);
}
}

Di atas kita menambahkan 1 method baru, yaitu getImageAttribute, dimana di


dalamnya kita akan melakukan return atau mengembalikan ke dalam folder

99
storage/categories/. Jadi jika kita memanggil attribute image makan akan otomatis
menghasilkan output seperti berikut ini :

domain.com/storage/categories/nama_file_image.png

Tapi, jika kita tidak menggunakan fitur Accessor, maka hasilnya akan seperti berikut ini :

nama_file_image.png

Langkah 2 - Menambahkan Accessor di Model Customer

Sekarang kita lanjutkan untuk menambahkan Accessor di dalam Model Customer, disini kita
akan memanipulasi attribute created_at saat ditampilkan menjadi format tanggal
Indonesia yang mudah di baca.

Silahkan buka file app/Models/Customer.php kemudian ubah kode-nya menjadi seperti


berikut ini :

<?php

namespace App\Models;

use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Customer extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'name', 'email', 'email', 'email_verified_at', 'password',
'remember_token'
];
/**

100
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];

/**
* invoice
*
* @return void
*/
public function invoices()
{
return $this->hasMany(Invoice::class);
}

/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}

/**
* getCreatedAtAttribute
*
* @param mixed $date
* @return void
*/
public function getCreatedAtAttribute($date)
{
$value = Carbon::parse($date);
$parse = $value->locale('id');
return $parse->translatedFormat('l, d F Y');
}
}

Di atas kita menambahkan 1 method baru, yaitu getCreatedAtAttribute, method


tersebut kita gunakan untuk mengubah isi dari attribute created_at saat di tampilkan.

101
Disini kita menggunakan bantuan Carbon dan kita set dengan locale id, yang artinya
adalah format Indonesia.

Langkah 3 - Menambahkan Accessor di Model Product

Disini kita akan membuat 2 accessor, yang pertama untuk manipulate attribute image dan
yang satunya untuk manipulate attribute dari hasil response JSON, dimana hasil response
tersebut yaitu berupa data average yang nantinya akan digunakan untuk menampilkan
rating atau ulasan.

Silahkan buka file app/Models/Product.php dan ubah semua kode-nya menjadi seperti
berikut ini :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'title', 'slug', 'category_id', 'user_id',
'description', 'weight', 'price', 'stock', 'discount'
];

/**
* category
*
* @return void
*/
public function category()
{
return $this->belongsTo(Category::class);
}

102
/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}

/**
* getImageAttribute
*
* @param mixed $image
* @return void
*/
public function getImageAttribute($image)
{
return asset('storage/products/' . $image);
}
/**
* getReviewsAvgRatingAttribute
*
* @param mixed $value
* @return void
*/
public function getReviewsAvgRatingAttribute($value) {
return $value ? substr($value, 0, 3) : 0;
}
}

Di atas, kita menambahkan 2 method baru, yaitu getImageAttribute yang digunakan


untuk menampilkan full-path gambar dari product. Dan
getReviewsAvgRatingAttribute untuk menampilkan jumlah average dari rating
product. attribute reviews_avg_rating akan di hasilkan dari eloquent dan akan di
tampilkan di dalam format JSON nantin-nya.

Langkah 4 - Menambahkan Accessor di Model Slider

Terakhir, kita akan menambahkan Accessor di dalam Model Slider, fungsinya sama yaitu
untuk memanipulasi attribute image agar dapat menghasilkan full-path gambar.

Silahkan buka file app/Models/Slider.php kemudian ubah kode-nya menjadi seperti


berikut ini :

103
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Slider extends Model


{
use HasFactory;

/**
* fillable
*
* @var array
*/
protected $fillable = [
'image', 'link'
];

/**
* getImageAttribute
*
* @param mixed $image
* @return void
*/
public function getImageAttribute($image)
{
return asset('storage/sliders/' . $image);
}
}

Di atas kita menambahkan 1 method baru yang bernama getImageAttribute dan di


dalamnya akan melakukan return atau mengembalikan ke dalam folder storage/sliders.

104
Membuat Data Seeder User

Laravel memiliki fitur yang bernama Database Seeding, fitur ini bisa kita manfaatkan
untuk membuat dummy data ke dalam database. Dan disini kita akan manfaatkan fitur ini
untuk membuat dummy data user, yang mana akan digunakan untuk melakukan proses
authentication di dalam halaman admin.

Langkah 1 - Membuat Class Seeder User


Untuk membuat sebuah seeder kita bisa menggunakan perintah Artisan, yaitu
make:seeder dan diikuti dengan nama seeder yang ingin dibuat. Sekarang, silahkan
jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:seeder UserSeeder

Jika perintah diatas berhasil dijalankan, maka kita akan mendapatkan 1 file baru di dalam
folder database/seeders/UserSeeder.php. Silahkan buka file tersebut dan ubah kode-
nya menjadi seperti berikut ini :

105
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;

class UserSeeder extends Seeder


{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('users')->insert([
'name' => 'Administrator',
'email' => 'admin@gmail.com',
'password' => Hash::make('password'),
]);
}
}

Di atas, kita import Facades DB, ini digunakan untuk melakukan insert data user ke dalam
database dengan Query Builder .

use Illuminate\Support\Facades\DB;

Selanjutnya, kita juga import Facades Hash, ini akan digunakan untuk melakukan hash
password ke dalam database.

use Illuminate\Support\Facades\Hash;

Dan di dalam function run kita melakukan insert data user ke dalam table users
menggunakan Query Builder dan jika teman-teman perhatikan untuk password kita
tambahkan Facades Hash untuk melakukan hashing password ke dalam database.

106
Langkah 2 - Menjalankan Seeder
Sekarang, kita akan menjalankan UserSeeder agar data user di atas di masukkan ke dalam
table users. Silahkan jalankan perintah di bawah ini di dalam terminal/CMD :

composer dump-autoload

php artisan db:seed --class=UserSeeder

Jika berhasil, maka kita bisa melihat ada data user baru di dalam table users. Kurang lebih
seperti berikut ini :

107
Apa itu API ?

API atau Application Programming Interface merupakan sebuah interface yang dapat
menghubungkan antara satu aplikasi dengan aplikasi yang lain dengan lebih mudah.
Dengan API kita dapat melakukan pertukaraan data dari aplikasi yang berbeda, baik dalam
satu platform maupun berbeda platform. [1]

Apa itu RESTful API ?


RESTful API / REST API merupakan implementasi dari API (Application Programming
Interface). REST (Representional State Transfer) adalah suatu arsitektur metode komunikasi
yang menggunakan protokol HTTP untuk pertukaran data dan metode ini sering diterapkan
dalam pengembangan aplikasi.

Dimana tujuannya adalah untuk menjadikan sistem yang memiliki performa yang baik,
cepat dan mudah untuk di kembangkan (scale) terutama dalam pertukaran dan komunikasi
data. [2]

Di dalam RESTful API terdapaat beberapa bagian yang harus dipenuhi. Diantaranya adalah :

1. URL

RESTful API diakses menggunakan protokol HTTP. Penamaan dan struktur URL yang
konsisten akan menghasilkan API yang baik dan mudah untuk dimengerti developer.
URL API biasa disebut endpoint dalam pemanggilannya.

2. HTTP Verbs

108
Setiap request yang dilakukan terdapat metode yang dipakai agar server mengerti
apa yang sedang di request client

GET : adalah metode HTTP Request yang paling simpel, metode ini digunakan
untuk membaca atau mendapatkan data dari sumber.
POST : adalah metode HTTP Request yang digunakan untuk membuat data baru
dengan menyisipkan data dalam body saat request dilakukan.
PUT : adalah metode HTTP Request yang biasanya digunakan untuk melakukan
update data resource.
DELETE : adalah metode HTTP Request yang digunakan untuk menghapus
suatu data pada resource.

3. HTTP Response Code HTTP response code adalah kode standarisasi dalam
menginformasikan hasil request kepada client. Secara umum terdapat 3 kelompok
yang biasa kita jumpai pada RESTful API yaitu :

2XX : adalah response code yang menampilkan bahwa request berhasil.


4XX : adalah response code yang menampilkan bahwa request mengalami
kesalahan pada sisi client.
5XX : adalah response code yang menampilkan bahwa request mengalami
kesalahan pada sisi server.

4. Format Response Setiap request yang dilakukan client akan menerima data
response dari server, response tersebut biasanya berupa data XML ataupun JSON.
Setelah mendapatkan data response tersebut barulah client bisa menggunakannya
dengan cara memparsing data tersebut dan diolah sesuai kebutuhan.

Dalam buku ini kita akan belajar bagaimana cara bertukar data dari satu aplikasi (Laravel)
ke aplikasi yang lain (Nuxt.js) dengan menggunakan API. Dan untuk pertukaran data
tersebut kita akan menggunakan data berupa JSON.

[1] : https://www.niagahoster.co.id/blog/api-adalah/#Apa_itu_API

[2] : https://jogjaweb.co.id/blog/pengertian-restfull-api

109
Apa itu JWT (Json Web Token) ?

JSON Web Token (JWT) adalah standar terbuka (RFC 7519) yang mendefinisikan cara
kompak dan mandiri untuk mentransmisikan informasi dengan aman antar pihak sebagai
objek JSON. Informasi ini dapat diverifikasi dan dipercaya karena ditandatangani secara
digital. JWT dapat ditandatangani menggunakan sebuah secreet (dengan algoritme HMAC)
atau pasangan kunci publik / pribadi menggunakan RSA atau ECDSA.
(https://jwt.io/introduction/)

Sederhananya JWT merupakan Web Token yang berupa JSON dan umumnya digunakan
untuk proses authentication. Dimana untuk mendapatkan suatu data di dalam server kita
harus menyertakan token tersebut sebagai kuncinya.

Kapan kita harus menggunakan JSON Web Token?

Authentication

Ketika pengguna melakukan authentication dan mendapatkan token, maka setiap


permintaan berikutnya akan menyertakan token tersebut, dan memungkinkan
pengguna untuk mengakses route, service, dan resources yang diizinkan.

Pertukaran Informasi

JSON Web Token adalah cara yang baik untuk mengirimkan informasi antar pihak
dengan aman. Dengan token yang sudah ditandatangani dengan algoritme RSA, maka
kita bisa tahu siapa yang melakukan request tersebut.

Untuk mempelajari tentang JWT lebih lanjut, kita bisa melihatnya di dalam website resminya
di https://jwt.io/. Dan untuk implementasi JWT di dalam project kali ini, kita akan
menggunakan package dari https://jwt-auth.readthedocs.io/en/develop/laravel-installation/.

Dan ini akan kita implementasikan untuk membuat Authentication di dalam project Nuxt Js
nanti. Di materi berikutnya kita bersama-sama akan belajar begaimana cara installasi dan

110
konfigurasi package tersebut di dalam project kita.

111
Installasi dan Konfigurasi JWT untuk Admin

Di dalam materi kali ini kita akan belajar bagaimana cara melakukan proses installasi dan
konfigurasi otentikasi JWT untuk admin dan untuk otentikasi customer akan kita bahas di
mater-materi selanjutnya.

Pertama, kita akan lakukan installasi JWT di dalam project Laravel, kemudian melakukan
beberapa konfigurasi di dalam Model yang akan digunakan sebagai otentikasi berbasis JWT
nantinya.

Langakah 1 - Installasi JWT (Json Web Token)


Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan berada di dalam
project yang sedang kita kembangkan.

composer require tymon/jwt-auth:1.0.2

Silahkan tunggu proses installasi sampai selesai. Dan pastikan harus terhubung dengan
internet, karena package ini akan di unduh secara online.

Langkah 2 - Publish Konfigurasi File


Setelah proses installasi selesai, sekarang silahkan lakukan publish file konfigurasi. Jalankan
perintah berikut ini di dalam terminal/CMD :

php artisan vendor:publish --


provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file di dalam folder
config/jwt.php. Di dalam file tersebut kita bisa sesuaikan konfigurasi dari JWT jika
memang dibutuhkan, misalnya mengubah waktu expired token, dan lain-lain.

Langkah 3 - Generate Secreet Key


Silahkan jalankan perintah di bawah ini di terminal/CMD untuk membuat secreet key di JWT :

112
php artisan jwt:secret

Jika perintah di atas berhasil, maka kita akan mendapatkan variable yang isinya adalah
secreet key dari JWT di dalam file .env.

JWT_SECRET=

Langkah 4 - Konfigurasi Guard API untuk Admin


Secara default untuk Guard api di Laravel menggunakan driver token, oleh sebab itu kita
harus melakukan perubahan terlebih dahulu ke dalam JWT atau Json Web Token.

Silahkan buka file config/auth.php kemudian cari kode berikut ini :

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],

Kemudian ubah kode-nya menjadi seperti berikut ini :

113
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

'api_admin' => [ // <-- atur menjadi


"api_admin"
'driver' => 'jwt', // <-- atur menjadi "jwt"
'provider' => 'users',
'hash' => false,
],
],

Di atas, pertama kita ubah nama guard yang semula api menjadi api_admin, kemudian
untuk driver-nya yang semula token kita ubah menjadi jwt.

Langkah 5 - Konfigurasi Model User


Sekarang, kita lanjutkan untuk melakukan beberapa konfigurasi di dalam Model User agar
bisa menggunakan JWT untuk generate token.

Silahkan buka file app/Models/User.php kemudian ubah kode-nya menjadi seperti


berikut ini :

<?php

namespace App\Models;

use Tymon\JWTAuth\Contracts\JWTSubject; // <-- import JWTSubject


use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable implements JWTSubject // <--


tambahkan ini
{
use HasFactory, Notifiable;

/**
* The attributes that are mass assignable.
*

114
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];

/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];

/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];

/**
* prooducts
*
* @return void
*/
public function products()
{
return $this->hasMany(Product::class);
}
/**
* Get the identifier that will be stored in the subject claim of
the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}

115
/**
* Return a key value array, containing any custom claims to be
added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}

Di atas, pertama kita import JWTSubject di dalam Model User.

use Tymon\JWTAuth\Contracts\JWTSubject; // <-- import JWTSubject

Setelah itu kita tambahkan implements JWTSubject di dalam class User.

class User extends Authenticatable implements JWTSubject // <--


tambahkan ini
{
....

Kemudian, kita juga menambahkan 2 method baru, yaitu :

function getJWTIdentifier
function getJWTCustomClaims

116
Membuat Restful API Login Admin

Setelah belajar installasi dan konfigurasi JWT (Json Web Token) di dalam project Laravel,
maka sekarang kita akan lanjutkan untuk membuat sebuah controller yang nantinya akan
digunakan untuk proses otentikasi. Disini kita akan membuat beberapa method juga,
diantaranya adalah :

Login - proses otentikasi ke sistem.


Get Data User - menampilkan data user yang sedang login.
Refresh Token - memperbarui token JWT.
Logout - melakukan proses logout dengan mengahpus token JWT dari memory
server.

Langkah 1 - Membuat Controller API login


Sekarang kita akan mulai dengan membuat controller-nya terlebih dahulu, silahkan jalankan
perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\Admin\\LoginController

Perintah di atas akan digunakan untuk membuat controller baru dengan nama
LoginController yang berada di dalam folder app/Http/Controllers/Api/Admin.
Sekarang buka file tersebut kemudian ubah kode-nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Admin;

use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;

class LoginController extends Controller


{
/**
* index
*
* @param mixed $request
* @return void

117
*/
public function index(Request $request)
{
//set validasi
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);
//response error validasi
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

//get "email" dan "password" dari input


$credentials = $request->only('email', 'password');

//check jika "email" dan "password" tidak sesuai


if(!$token = auth()->guard('api_admin')->attempt($credentials))
{

//response login "failed"


return response()->json([
'success' => false,
'message' => 'Email or Password is incorrect'
], 401);

}
//response login "success" dengan generate "Token"
return response()->json([
'success' => true,
'user' => auth()->guard('api_admin')->user(),
'token' => $token
], 200);
}
/**
* getUser
*
* @return void
*/
public function getUser()
{
//response data "user" yang sedang login
return response()->json([
'success' => true,
'user' => auth()->guard('api_admin')->user()
], 200);

118
}
/**
* refreshToken
*
* @param mixed $request
* @return void
*/
public function refreshToken(Request $request)
{
//refresh "token"
$refreshToken = JWTAuth::refresh(JWTAuth::getToken());

//set user dengan "token" baru


$user = JWTAuth::setToken($refreshToken)->toUser();

//set header "Authorization" dengan type Bearer + "token" baru


$request->headers->set('Authorization','Bearer '.$refreshToken);

//response data "user" dengan "token" baru


return response()->json([
'success' => true,
'user' => $user,
'token' => $refreshToken,
], 200);
}
/**
* logout
*
* @return void
*/
public function logout()
{
//remove "token" JWT
$removeToken = JWTAuth::invalidate(JWTAuth::getToken());

//response "success" logout


return response()->json([
'success' => true,
], 200);

}
}

Dari perubahan kode di atas, pertama kita import provider Http Request dari Laravel, ini
digunakan untuk menerimaa request yang dikirim melalui cookie, input form dan lain-lain.

119
use Illuminate\Http\Request;

Setelah itu, kita juga import Facades dari JWT, ini akan kita gunakan untuk proses yang
berhubungan dengan Token JWT nantinya.

use Tymon\JWTAuth\Facades\JWTAuth;

Dan kita juga import Facades Validator, yang mana akan kita gunakan untuk proses
validasi input yang akan kita buat di Rest API nanti.

use Illuminate\Support\Facades\Validator;

Kemudian di dalam class LoginController kita membuat 4 method, yaitu :

Di atas, pertama kita ubah nama guard yang semula api menjadi api_admin,
kemudian untuk driver-nya yang semula token kita ubah menjadi jwt.

getUser

refreshToken

logout

function index

Di dalam method ini kita akan melakukan proses otentikasi menggunakan JWT, dimana jika
proses otentikasi berhasil maka akan dilakukan generate Token. Sebelum itu disini kita
menambahkan sebuah validasi untuk pengecekan apakah data yang dikirim benar-benar
sesuai dengan yang diharapkan.

120
//set validasi
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);

Dari definisi validasi di atas, kurang lebih seperti berikut ini :

KEY VALIDASI KETERANGAN

email required field wajib diisi

email field harus menggunakan format email

password required field wajib diisi

Jika data yang dikirim tidak sesuai dengan validasi di atas, maka kita akan mengembalikan
sebuah response dengan format JSON yang berisi informasi error tentang validasi.

//response error validasi


if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

Setelah itu kita buat sebuah variable yang bernama $credentials yang berisi sebuah
data email dan password yang dikirim melalui Http request. Kemudian kita lakukan proses
pengecekan jika email dan password tersebut tidak terdaftar di dalam database.

//check jika "email" dan "password" tidak sesuai


if(!$token = auth()->guard('api_admin')->attempt($credentials)) {

//...

Maka akan mengembalikan sebuah response dengan format JSON yang berisi informasi
tentang proses login gagal atau failed.

121
//response login "failed"
return response()->json([
'success' => false,
'message' => 'Email or Password is incorrect'
], 401);

Tapi jika email dan password ada di dalam database, maka kita akan mengembalikan
sebuah response dalam format JSON yang berisi informasi data user dan informasi token
JWT.

//response login "success" dengan generate "Token"


return response()->json([
'success' => true,
'user' => auth()->guard('api_admin')->user(),
'token' => $token
], 200);

function getUser

Method ini digunakan untuk menampilkan informasi data user yang sedang login, kurang
lebih seperti berikut ini :

//response data "user" yang sedang login


return response()->json([
'success' => true,
'user' => auth()->guard('api_admin')->user()
], 200);

Di atas kita mendapatkan data user menggunakan helper auth dari Laravel, dimana helper
auth tersebut kita arahkan ke dalam object guard api_admin, yang memang proses
otentikasinya menggunakan guard API tersebut.

function refreshToken

Method ini kita gunakan untuk proses memperbarui token JWT, alurnya kita generate
sebuah token baru setelah itu token tersebut kita set ke data user yang sedang login.

122
//refresh "token"
$refreshToken = JWTAuth::refresh(JWTAuth::getToken());

Kode di atas digunakan untuk melakukan proses generate token baru. Setelah itu hasil dari
token baru tersebut kita set ke dalam data user yang sedang login.

//set user dengan "token" baru


$user = JWTAuth::setToken($refreshToken)->toUser();

Kemudian kita set juga untuk header Authorization dengan value Bearer + token baru.

//set header "Authorization" dengan type Bearer + "token" baru


$request->headers->set('Authorization','Bearer '.$refreshToken);

Setelah itu kita mengembalikan sebuah response dalam format JSON yang berisi informasi
data user beserta token baru.

//response data "user" dengan "token" baru


return response()->json([
'success' => true,
'user' => $user,
'token' => $refreshToken,
], 200);

function logout

Method ini digunakan untuk proses menghapus token yang terdaftar di server backend,
untuk mengahapus token kita bisa menggunakan kode seperti berikut ini :

//remove "token" JWT


$removeToken = JWTAuth::invalidate(JWTAuth::getToken());

Setelah itu kita mengembalikan sebuah response dalam format JSON dengan status success
true.

123
//response "success" logout
return response()->json([
'success' => true,
], 200);

Langkah 2 - Membuat Route


Setelah berhasil membuat controller dan menambahkan method-method di dalamnya, maka
sekarang kita lanjutkan untuk membuat route agar method-method tersebut dapat
digunakan.

Silahkan buka file routes/api.php kemudian ubah kode-nya menjadi seperti berikut ini :

124
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

//group route with prefix "admin"


Route::prefix('admin')->group(function () {

//route login
Route::post('/login',
[App\Http\Controllers\Api\Admin\LoginController::class, 'index', ['as'
=> 'admin']]);

//group route with middleware "auth:api_admin"


Route::group(['middleware' => 'auth:api_admin'], function() {

//data user
Route::get('/user',
[App\Http\Controllers\Api\Admin\LoginController::class, 'getUser', ['as'
=> 'admin']]);

//refresh token JWT


Route::get('/refresh',
[App\Http\Controllers\Api\Admin\LoginController::class, 'refreshToken',
['as' => 'admin']]);

//logout
Route::post('/logout',
[App\Http\Controllers\Api\Admin\LoginController::class, 'logout', ['as'
=> 'admin']]);
});

});

Di atas, kita buat sebuah route dengan prefix admin. Prefix ini digunakan untuk
menambahkan kata admin di awalan URL. Jadi jika kita atur sebuah route dengan url
/login, maka kita akan otomatis mendapatkan hasilnya menjadi /admin/login. Karena
awalan URL akan di tambahkan kata admin.

125
//group route with prefix "admin"
Route::prefix('admin')->group(function () {

//...

});

Di dalamnya kita membuat sebuah route, yang kita arahkan ke URL /login dan route ini
memanggil sebuah method yang bernama index di dalam controller LoginController.

//route login
Route::post('/login',
[App\Http\Controllers\Api\Admin\LoginController::class, 'index', ['as'
=> 'admin']]);

Selanjutnya kita membuat route dengan jenis group, yaitu kita akan mengelompokan route
berdasarkan kondisi tertentu, disini kita akan mengelompokan sebuah route ke dalam
middleware auth:api_admin. Yang artinya route yang ada di dalam group ini hanya bisa
diakses jika sudah melakukan proses otentikasi atau login.

//group route with middleware "auth:api_admin"


Route::group(['middleware' => 'auth:api_admin'], function() {

//...

});

Di dalamnya kita mendefinisikan 3 route baru, yaitu :

/user - digunakan untuk menampilkan data user yang sedang login, dimana route ini
kita arahkan ke dalam sebuah method yang bernama getUser yang berada di dalam
controller LoginController.
refresh - digunakan untuk melakukan generate token JWT baru atau biasa disebut
dengan refresh token, disini kita arahkan ke dalam method yang bernama
refreshToken yang berada di dalam controller LoginController.
logout - digunakan untuk proses logout dari aplikasi, disini kita arahkan ke dalam
sebuah method yang bernama logout yang berada di dalam controller
LoginController.

Dan setiap route kita berikan tambahan ['as' => 'admin'], yang artinya setiap route

126
akan diberikan name dengan awalan admin.

Langkah 3 - Uji Coba Rest API


Setelah berhasil membuat controller dan menambahkan beberapa method di dalamnya,
sekarang kita akan lanjutkan untuk proses uji coba atau tetsing dari Rest API yang sudah
kita buat.

Uji Coba Proses Login

Silahkan teman-teman buka aplikasi Postman terlebih dahulu, setelah aplikasi berhasil
terbuka, silahkan masukkan URL berikut ini http://localhost:8000/api/admin/login dan untuk
method-nya silahkan gunakan POST.

Jika sudah, silahkan klik Send, maka kita akan mendapatkan sebuah response validasi yang
berisi informasi bahwa kolom email dan password tidak boleh dikosongkan.

Dari gambar di atas bisa dilihat, kita mendapatkan sebuah response validasi dengan status
code 422 Unprosessable Entity.

Sekarang mari kita coba lagi dengan memasukkan email dan password yang sebelumnya
sudah kita buat di dalam user seeder. Silahkan kembali lagi ke aplikasi Postman dan klik
tab Body kemudian pilih form-data dan masukkan key dan value berikut ini :

KEY VALUE

127
KEY VALUE

email admin@gmail.com

password password
CATATAN ! : data di atas merupakan data user yang ada di dalam database dan data
tersebut dibuat saat kita melakukan pembuatan user seeder.

Sekarang, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response yang berisi informasi data user dan token JWT. Kurang lebih seperti berikut ini :

INFORMASI : selanjutnya untuk mengakses route yang membutuhkan otentikasi, maka kita
harus menyertakan token yang di dapatkan dari proses login seperti di atas.

Uji Coba Get Data User

Setelah berhasil mendapatkan token dari hasil proses otentikasi, sekarang kita lanjutkan
untuk proses uji coba get data user, dimana untuk mendapatkan data user yang sedang
login kita membutuhkan token dari proses otentikasi sebelumnya.

Silahkan buka tab baru di aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/user, dan untuk method-nya silahkan pilih GET. Setelah itu,
silahkan klik tab Headers kemudian masukkan key dan value berikut ini di dalamnya.

128
KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token


CATATAN ! : untuk value dari Authorization adalah Bearer kemudian spasi Token,
token tersebut silahkan diambil dari hasil response token di proses login.

Jika sudah, silahakn klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response yang berisi informasi tentang data user yang sedang login.

Uji Coba Refresh Token

Pada tahap kali ini kita semua akan belajar bagaimana cara melakuakn refresh token di
dalam JWT (Json Web Token), yaitu konsepnya melakukan regenerate atau memperbarui
token yang lama menjadi baru dan di set ke user yang sedang login.

Silahkan buka tab baru di dalam aplikasi Postman, kemudian masukkan URL berikut ini
http://localhost:8000/api/admin/refresh dan untuk method-nya silahkan pilih GET.
Selanjutnya klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

129
KEY VALUE

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dengan berisi informasi data user beserta token baru. Jika kita perhatikan token
dari hasil login akan di regenerate dengan token baru, kita bisa bandingkan 2 token
tersebut, maka nilainya akan berbeda.

CATATAN ! : selanjutnya untuk token yang dipakai adalah token dari hasil Refresh Token.
Tapi jika kita melakukan proses login ulang, maka token yang di pakai adalah token hasil
login tersebut.

Uji Coba Proses Logout

Terakhir kita akan lakukan uji coba untuk proses logout, silahkan buka tab baru di Postman
daan masukkan URL berikut ini http://localhost/api/admin/logout dan untuk method-nya
adalah POST.

Selanjutnya klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

130
KEY VALUE

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response berupa informasi bahwa proses logout berhasil.

131
Membuat Restful API Dashboard Admin

Di materi sebelumnya kita sudah belajar banyak hal tentang proses otentikasi menggunakan
JWT (Json Web Token). Maka disini kita akan lanjutkan untuk membuat sebuah Rest API
untuk dashboard, yang mana isinya adalah statistik pendapatan dari data order yang akan
kita tampilkan nantinya di halaman frontend menjadi grafik atau chart.

Langkah 1 - Membuat Controller API Dashboard


Pertama, kita akan membuat controller dashboard terlebih dahulu, silahkan jalankan
perintah berikut ini di dalam terminal/CMD dan pastikan menjalankan perintahnya berada di
dalam project Laravel.

php artisan make:controller Api\\Admin\\DashboardController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama DashboardController yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Admin;

use App\Models\Invoice;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;

class DashboardController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//count invoice
$pending = Invoice::where('status', 'pending')->count();
$success = Invoice::where('status', 'success')->count();

132
$expired = Invoice::where('status', 'expired')->count();
$failed = Invoice::where('status', 'failed')->count();

//year and month


$year = date('Y');

//chart
$transactions = DB::table('invoices')
->addSelect(DB::raw('SUM(grand_total) as grand_total'))
->addSelect(DB::raw('MONTH(created_at) as month'))
->addSelect(DB::raw('MONTHNAME(created_at) as month_name'))
->addSelect(DB::raw('YEAR(created_at) as year'))
->whereYear('created_at', '=', $year)
->where('status', 'success')
->groupBy('month')
->orderByRaw('month ASC')
->get();
if(count($transactions)) {
foreach ($transactions as $result) {
$month_name[] = $result->month_name;
$grand_total[] = (int)$result->grand_total;
}
}else {
$month_name[] = "";
$grand_total[] = "";
}

//response
return response()->json([
'success' => true,
'message' => 'Statistik Data',
'data' => [
'count' => [
'pending' => $pending,
'success' => $success,
'expired' => $expired,
'failed' => $failed
],
'chart' => [
'month_name' => $month_name,
'grand_total' => $grand_total
]
]
], 200);
}
}

133
Dari perubahan kode di atas, pertama kita melakukan import Model Invoice terlebih
dahulu, karena kita akan menggunakannya untuk mendapatkan data transaksi.

use App\Models\Invoice;

Setelah itu, kita juga melakukan import Facades DB dari Laravel, karena nanti kita akan
melakukan sedikit query advanced untuk mendapatkan data transaksi di bulan dan tahun
yang sedang aktif.

use Illuminate\Support\Facades\DB;

Dan di dalam class DashboardController kita menambahkan 1 method baru yang


bernama index, di dalamnya kita melakukan banyak penambahan kode, diantaranya yaitu :

//count invoice
$pending = Invoice::where('status', 'pending')->count();
$success = Invoice::where('status', 'success')->count();
$expired = Invoice::where('status', 'expired')->count();
$failed = Invoice::where('status', 'failed')->count();

Di atas, kita menggunakan Eloquent, untuk menghitung jumlah transaksi berdasarkan


status pending, success, expired dan failed di dalam Model Invoice.

Setelah itu, kita menambahkan kode lagi dengan melakukan query ke dalam table
invoices untuk mendapatkan nama bulan, jumlah transaksi dan data yang memiliki status
success di tahun yang aktif.

134
//yearth
$year = date('Y');

//chart
$transactions = DB::table('invoices')
// 1
->addSelect(DB::raw('SUM(grand_total) as grand_total'))
// 2
->addSelect(DB::raw('MONTH(created_at) as month'))
// 3
->addSelect(DB::raw('MONTHNAME(created_at) as month_name'))
// 4
->addSelect(DB::raw('YEAR(created_at) as year'))
// 5
->whereYear('created_at', '=', $year)
// 6
->where('status', 'success')
// 7
->groupBy('month')
// 8
->orderByRaw('month ASC')
// 9
->get();
if(count($transactions)) {
foreach ($transactions as $result) {
$month_name[] = $result->month_name;
$grand_total[] = (int)$result->grand_total;
}
else {
$month_name[] = "";
$grand_total[] = "";
}

Di atas, kita coba terjemahkan query-nya kurang lebih seperti berikut ini :

1. menggunakan table invoices.


2. kemudian melakukan penjumlahan ke attribute grand_total, menggunakan SUM.
3. mengambil angkan bulan dari attribute created_at, menggunakan MONTH.
4. mengambil nama bulan dari attribute created_at, menggunakan MONTHNAME.
5. mengambil tahun dari atrribute created_at, menggunakan YEAR.
6. mencocokan tahun dari attribute created_at dengan variable $year, menggunakan
WhereYear.
7. mencari data dengan status success, menggunakan where
8. di gabungkan berdasarkan angkan bulan, menggunakan groupBy.

135
9. di urutkan berdasarkan nama bulan secara Ascending, menggunakan orderByRaw.

Jika transaksi dari query di atas ditemukan, maka kita akan melakuak perulangan
menggunakan foreach dan nilainnya akan di assign ke dalam variable $month_name dan
$grand_total.

foreach ($transactions as $result) {


$month_name[] = $result->month_name;
$grand_total[] = (int)$result->grand_total;
}

Tapi jika hasil dari query di atas kosong, maka kita cukup atur kedua variable tersebut
dengan nilai kosong.

$month_name[] = "";
$grand_total[] = "";

Setelah itu, kita lakukan return ke dalam format JSON, kurang lebih seperti berikut ini :

/response
return response()->json([
'success' => true,
'message' => 'Statistik Data',
'data' => [
'count' => [
'pending' => $pending,
'success' => $success,
'expired' => $expired,
'failed' => $failed
],
'chart' => [
'month_name' => $month_name,
'grand_total' => $grand_total
]
]
], 200);

136
Langkah 2 - Menambahkan Route Dashboard
Setelah berhasil membuat controller dan menambahkan method di dalamnya, maka
sekarang kita lanjutkan untuk menambahkan route agar controller dashboard dapat diakses.

Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route /logout.

//dashboard
Route::get('/dashboard',
[App\Http\Controllers\Api\Admin\DashboardController::class, 'index',
['as' => 'admin']]);

Jika ditulis dengan lengkap kurang lebih seperti berikut ini :

137
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

//group route with prefix "admin"


Route::prefix('admin')->group(function () {

//route login
Route::post('/login',
[App\Http\Controllers\Api\Admin\LoginController::class, 'index', ['as'
=> 'admin']]);

//group route with middleware "auth:api_admin"


Route::group(['middleware' => 'auth:api_admin'], function() {

//data user
Route::get('/user',
[App\Http\Controllers\Api\Admin\LoginController::class, 'getUser', ['as'
=> 'admin']]);

//refresh token JWT


Route::get('/refresh',
[App\Http\Controllers\Api\Admin\LoginController::class, 'refreshToken',
['as' => 'admin']]);

//logout
Route::post('/logout',
[App\Http\Controllers\Api\Admin\LoginController::class, 'logout', ['as'
=> 'admin']]);
//dashboard
Route::get('/dashboard',
[App\Http\Controllers\Api\Admin\DashboardController::class, 'index',
['as' => 'admin']]);
});

});

Langkah 3 - Uji Coba Rest API Dashboard


Setelah berhasil membuat controller dan menambahkan method di dalamnya, sekarang kita
akan lanjutkan untuk proses uji coba atau tetsing dari Rest API yang sudah kita buat.

138
Silahkan teman-teman buka aplikasi Postman terlebih dahulu, setelah aplikasi berhasil
terbuka, silahkan masukkan URL berikut ini http://localhost:8000/api/admin/dashboard dan
untuk method-nya silahkan gunakan GET.

Setelah itu, silahkan klik tab Headers kemudian masukkan key dan value berikut ini di
dalamnya.

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send, jika menemukan error seperti gambar dibawah ini, maka kita
perlu melakukan sedikit konfigurasi tambahan di dalam config database yang ada di Laravel
untuk mengizinkan melakukan query GROUP BY.

Silahkan buka file config/database.php, kemudian cari di dalam array connections


untuk mysql dan ubah kode berikut ini :

'strict' => true,

Menjadi sperti berikut ini :

139
'strict' => false,

Jika sudah, silahkan simpahan perubahan di atas dan kita coba lagi klik Send di dalam
aplikasi Postman, jika berhasil maka kita akan mendapatkan hasil seperti berikut ini :

140
Membuat Restful API CRUD Categories

Di materi sebelumnya kita telah belajar bagaimana cara membuat proses otentikasi di
Laravel menggunakan JWT, sekarang kita lanjutkan untuk belajar bagaimana cara membuat
Rest API untuk proses CRUD atau Create, Read, Update dan Delete data Categories.

Dan kali ini kita akan menggunakan salah satu fitur dari Laravel yang berfungsi untuk
mentransformasi dari Model ke dalam sebuah format JSON dengan lebih mudah dan cepat.
Fitur tersebut bernama API Resources.

Apa itu API Resources ?


Saat membuat sebuah API, kita mungkin memerlukan sebuah layer atau lapisan yang
berfungsi sebagai transformasi antara Model dengan response JSON yang akan di tampilkan
ke sisi user/pengguna. Dengan menggunakan fitur ini kita dapat menampilkan sebuah
Relatiponships ke dalam format JSOn menjadi sangat mudah.

Sebenernya kita selalu bisa menggunakan fitur dari Eloquent yaitu toJson untuk membuat
sebuah response di dalam Model, tapi dengan menggunakan API Resouces kita bisa
memberikan kontrol lebih dan sangat terperinci.

Didalam buku ini kita semua akan belajar menggunakan API Resources untuk menangani
dalam proses pembuatan response JSON agar lebih sederhana dan cepat. Disini kita juga
akan belajar bagaimana cara melakukan customisasi sebuah Resource agar dapat
menampilkan format JSON sesuai dengan yang kita harapkan.

Langkah 1 - Membuat Resources Category


Pertama, kita akan membuat Resource untuk category terlebih dahulu dan nanti kita juga
akan belajar melakukan beberapa konfigurasi agar response JSON yang dihasilkan sesuai
dengan yang diharapkan.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan tentunya berada di dalam
project Laravel.

php artisan make:resource CategoryResource

Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file baru dengan nama
CategoryResource.php yang berada di dalam folder app/Http?Resources/.

141
Secara defualt file Resource tersebut sudah dapat digunakan untuk melakukan konversi dari
model menjadi format JSON yang cepat dan efisien, tetapi disini kita akan melakukan
sedikit perubahan agar format JSON yang dihasilkan bisa lebih baik lagi.

Silahkan buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :

142
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class CategoryResource extends JsonResource


{
//public properti
public $status;
public $message;
/**
* __construct
*
* @param mixed $status
* @param mixed $message
* @param mixed $resource
* @return void
*/
public function __construct($status, $message, $resource)
{
parent::__construct($resource);
$this->status = $status;
$this->message = $message;
}

/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}

Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.

143
public $status;
public $message;

Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :

$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.

Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :

return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];

Langkah 2 - Membuat Controller Category


Setelah berhasil membuat sebuah resource, maka sekarang kita akan lanjutkan untuk
membuat sebuah controller baru untuk category. Silahkan jalankan perintah berikut ini di
dalam terminal/CMD :

php artisan make:controller Api\\Admin\\CategoryController

Jika berhasil, maka kita akan mendapatkan 1 file controller baru dengan nama
CategoryController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah semua kode-
nya menjadi seperti berikut ini :

<?php

144
namespace App\Http\Controllers\Api\Admin;

use App\Models\Category;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
use App\Http\Resources\CategoryResource;
use Illuminate\Support\Facades\Validator;

class CategoryController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//get categories
$categories = Category::when(request()->q, function($categories)
{
$categories = $categories->where('name', 'like', '%'.
request()->q . '%');
})->latest()->paginate(5);
//return with Api Resource
return new CategoryResource(true, 'List Data Categories',
$categories);
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
'name' => 'required|unique:categories',
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

145
//upload image
$image = $request->file('image');
$image->storeAs('public/categories', $image->hashName());

//create category
$category = Category::create([
'image'=> $image->hashName(),
'name' => $request->name,
'slug' => Str::slug($request->name, '-'),
]);

if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Data Category Berhasil
Disimpan!', $category);
}

//return failed with Api Resource


return new CategoryResource(false, 'Data Category Gagal
Disimpan!', null);
}

/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$category = Category::whereId($id)->first();
if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Detail Data Category!',
$category);
}

//return failed with Api Resource


return new CategoryResource(false, 'Detail Data Category Tidak
DItemukan!', null);
}

/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request

146
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Category $category)
{
$validator = Validator::make($request->all(), [
'name' =>
'required|unique:categories,name,'.$category->id,
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

//check image update


if ($request->file('image')) {

//remove old image


Storage::disk('local')->delete('public/categories/'.basename($category->
image));
//upload new image
$image = $request->file('image');
$image->storeAs('public/categories', $image->hashName());

//update category with new image


$category->update([
'image'=> $image->hashName(),
'name' => $request->name,
'slug' => Str::slug($request->name, '-'),
]);

//update category without image


$category->update([
'name' => $request->name,
'slug' => Str::slug($request->name, '-'),
]);

if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Data Category Berhasil
Diupdate!', $category);
}

//return failed with Api Resource

147
return new CategoryResource(false, 'Data Category Gagal
Diupdate!', null);
}

/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Category $category)
{
//remove image
Storage::disk('local')->delete('public/categories/'.basename($category->
image));

if($category->delete()) {
//return success with Api Resource
return new CategoryResource(true, 'Data Category Berhasil
Dihapus!', null);
}

//return failed with Api Resource


return new CategoryResource(false, 'Data Category Gagal
Dihapus!', null);
}
}

Dari perubahan kode di atas, kita melakukan import Model Category, ini dilakukan karena
kita akan menggunakan Model ini untuk melakukan beberapa hal, seperti menampilkan
data, melakuakn insert, update, dan lain-lain.

use App\Models\Category;

Setelah itu, kita juga import Helper Str dari Laravel, yang mana akan kita gunakan nanti
untuk melakukan generate slug untuk data category.

use Illuminate\Support\Str;

Slug biasanya digunakan untuk membuat sebuah URL yang SEO friendly. Contoh genearte

148
slug kurang lebih seperti berikut ini :

///import support Str


use Illuminate\Support\Str;

//generate slug dengan "Str::slug"


$slug = Str::slug('Laravel 5 Framework', '-');

//hasil generate
echo $slug; // ouput --> laravel-5-framework

Jadi dari kata Laravel 5 Framework akan digenerate menjadi slug dengan diubah
menjadi huruf kecil dan dipisahkan dengan notasi -. Dan hasilnya kurang lebih seperti ini
laravel-5-framework.

Kemudian kita juga import Http Request dari Laravel, dimana akan digunakan untuk
menerima sebuah input yang dikirim melalui cookie, form, URL dan lain-lain.

use Illuminate\Http\Request;

Selanjutnya, tentu saja kita akan import CategoryResource yang sebelumnya sudah kita
buat, ini akan kita gunakan untuk mentransformasi Model menjadi sebuah format JSON
dengan lebih cepat dan efisien.

use App\Http\Resources\CategoryResource;

Karena kita akan membutuhkan sebuah validasi, maka kita perlu melakukan import juga,
disini kita import Facades Validator dari Laravel.

use Illuminate\Support\Facades\Validator;

Dan jika kita perhatikan dari penambahan kode yang ada di dalam controller
CategoryController, kita telah menambahkan 5 method baru, dimana method tersebut
yang akan kita gunakan untuk melakukan proses CRUD atau Create, Read, Update dan
Delete. Ke 5 method tersebut yaitu :

1. function index

149
2. function store
3. function show
4. function update
5. function destroy

function index

Method ini akan kita gunakan untuk menampilkan list data category, dan di dalam method
ini kita membuat kondisi di dalam Eloquent untuk proses pencarian data berdasarkan nama.

//get categories
$categories = Category::when(request()->q, function($categories) {
$categories = $categories->where('name', 'like', '%'. request()->q .
'%');
})->latest()->paginate(5);

Di atas kita membuat kondisi menggunakan method when, jadi jika ada sebuah request
dengan nama q, maka kita akan melakukan pencarian ke dalam table categories
berdasarkan attribute name yang sesuai dengan isi dari request q tersebut.

Kemudian kita urutkan datanya berdasarkan yang paling baru menggunakan method
latest() dan kita batasi data yang ditampilkan perhalaman 5 record menggunakan
method paginate().

Setelah itu, kita masukkan variable $categories di atas ke dalam CategoryResource


agar di ubah atau ditransformasi menjadi format JSON dengan cepat dan efisien.

//return with Api Resource


return new CategoryResource(true, 'List Data Categories', $categories);

Di atas bisa kita perhatikan, bahwa kita memberikan 3 parameter di dalamnya. Kurang lebih
seperti berikut ini :

true - merupakan status success dari response.


List Data Categories - merupakan pesan/message dari response.
$categories - merupakan isi data categories dari database.

Kenapa kita memberikan 3 parameter tersebut? karena di dalam CategoryResource pada


bagian __construct kita juga memberikan 3 parameter yang difungsikan untuk
menampung isi data yang dikirim melalui controller. Yaitu ada status, message dan
resource.

150
function store

Method store merupakan fungsi yang akan digunakan untuk melakukan proses insert atau
memasukkan data ke dalam database, di dalam method ini sebelum melakukan proses
insert kita akan menambahkan sebuah validasi terlebih dahulu, fungsinya untuk memeriksa
apakah data yang akan di masukkan ke dalam database sudah sesuai dengan yang
diharapkan.

$validator = Validator::make($request->all(), [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
'name' => 'required|unique:categories',
]);

Dari penambahan kode validasi di atas, kurang lebih penjelasannya seperti berikut ini :

KEY VALIDASI KETERANGAN

image required field wajib diisi.

image field harus berupa gambar.

extensi gambar yang boleh diupload adalah .jpeg, .jpg


mimes:jpeg,jpg,png
dan .png.

gambar yang boleh diupload maksimal memiliki ukuran


max:2000
2000 Kb / 2 Mb.

name required field wajib diisi.

field bersifat unik dan tidak boleh ada yang sama di


unique:categories
dalam table categories.

Jika validasi di atas tidak terpenuhi, maka kita akan mengembalikan sebuah response
dengan format JSON yang berisi informasi tentang error validasi.

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

Jika data yang akan di insert ke dalam database sudah sesuai dengan validasi di atas, maka
pertama kita akan melakukan proses upload gambar ke dalam server.

151
//upload image
$image = $request->file('image');
$image->storeAs('public/categories', $image->hashName());

Di atas, pertama kita membuat variable dengan nama $image yang isinya akan mengambil
sebuah request dengan jenis file dan bernama image, setelah itu akan dipindahkan ke
dalam folder /storage/app/public/categories menggunakan method storeAs dan
nama dari file tersebut akan di ubah menjadi random menggunakan method hashName().

Setelah gambar berhasil di upload di dalam server, selanjutnya kita melakukan proses insert
data ke dalam database menggunakan Eloquent, kurang lebih seperti berikut ini :

//create category
$category = Category::create([
'image'=> $image->hashName(),
'name' => $request->name,
'slug' => Str::slug($request->name, '-'),
]);

Dari kode proses insert data di atas, kuranng lebih penjelasannya seperti berikut ini :

ATTRIBUTE VALUE KETERANGAN

akan mengambil nama dari gambar yang


image $image->hashName()
diupload

akan mengambil data dari request yang


name $request->name
bernama name

akan diisi dengan request name dan di


masukkan ke dalam helper Str::slug
slug Str::slug($request->name, '-') untuk menghasilkan sebuah URL dari
nama category dan akan di tambahkan
notasi (-) untuk pemisah antar kata.

Untuk memastikan proses insert data ke dalam database di atas berhasil atau tidak, maka
kita membuat sebuah kondisi dengan mengecek nilai dari variable $category.

Jika variable $category bernilai true atau proses insert berhasil dilakukan, maka kita akan
melakukan return ke dalam CategoryResource dengan memberikan 3 paramater, kurang
lebih seperti berikut ini :

152
if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Data Category Berhasil
Disimpan!', $category);
}

Tapi, jika proses insert data ke dalam database gagal dilakukan, yang artinya variable
$category bernilai false maka kita akan melakukan return ke dalam
CategoryResource dengan 3 parameter yang memiliki status false.

//return failed with Api Resource


return new CategoryResource(false, 'Data Category Gagal Disimpan!',
null);

function show

Method ini akan kita gunakan untuk menampilkan detail data category berdasarkan ID, di
dalam method ini kita melakukan proses pencarian data menggunakaan Elqouent.

$category = Category::whereId($id)->first();

Di atas, kita mencari data category menggunakan method whereId yang artinya mencari
berdasarkan ID. Dan data ID tersebut yaitu variable $id, yang mana akan di ambil dari
parameter yang ada di dalam method show.

Jika variable $category di atas bernilai true, yang artinya data ditemukan di dalam
database, maka kita akan melakukan return ke dalam CategoryRescoure dengan 3
parameter yang memiliki status true.

if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Detail Data Category!',
$category);
}

Tapi, jika variable $category bernilai false, artinya data tidak ditemukan di dalam
database, maka kita akan melakuakn return ke dalam CategoryResource dengan

153
memberikan status false dan pesan Detail Data Category Tidak Ditemukan!.

function update

Method ini akan digunakan untuk melakukan proses update data ke dalam database, dan
sebelum data di update, kita harus melakukan proses validasi terlebih dahulu untuk
memastikan data yang akan diupdate sudah sesuai dengan yang diharapkan.

$validator = Validator::make($request->all(), [
'name' => 'required|unique:categories,name,'.$category->id,
]);

Dari kode validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :

KEY VALIDASI KETERANGAN

name required field wajib diisi.

field bersifat unik dan tidak boleh ada


yang sama di dalam table
categories. Karena bersifat unik,
unique:categories,name,'.$category->id maka untuk proses update kita
tambahkan name,'.$category->id,
yang artinya kusus ID category yang
diupdate akan dikecualikan.

Jika data yang akan diupdate belum sesuai dengan validasi yang di definisikan di atas, maka
akan mengembalikan sebuah error response validasi menggunakan kode berikut ini :

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

Tapi, jika data yang diupdate sudah sesuai dengan validasi di atas, maka kita akan
melakukan sebuah kondisi untuk mengecek apakah request dengan jenis file dan nama
image memiliki sebuah value. Jika iya, maka kita akan melakukan proses update data
beserta melakukan proses upload gambar baru.

154
//check image update
if ($request->file('image')) {

//...

Di mana di dalamnya pertama kita akan melakukan proses hapus gambar yang lama
terlebih dahulu.

//remove old image


Storage::disk('local')->delete('public/categories/'.basename($category->
image));

Setelah gambar lama berhasil di hapus, sekarang kita lanjutkan untuk melakukan proses
upload gambar yang baru.

//upload new image


$image = $request->file('image');
$image->storeAs('public/categories', $image->hashName());

Dan jika gambar baru sudah berhasil terupload, maka selanjutnya kita tinggal melakukan
proses update data category ke dalam database dengan data baru.

//update category with new image


$category->update([
'image'=> $image->hashName(),
'name' => $request->name,
'slug' => Str::slug($request->name, '-'),
]);

Tapi, jika request dengan jenis file dan bernama image tersebut tidak memiliki value,
maka kita hanya melakukan proses update data category tanpa mengupdate attribute
image-nya.

155
//update category without image
$category->update([
'name' => $request->name,
'slug' => Str::slug($request->name, '-'),
]);

Setelah itu, untuk memastikan proses update data berhasil atau tidak, kita akan membuat
sebuah kondisi untuk mengecek nilai dari variable $category. Jika variable tersebut
bernilai true, maka kita akan melakukan return ke dalam CategoryResource dengan
status true.

if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Data Category Berhasil Diupdate!',
$category);
}

Tapi, jika variable $category bernilai false, yang artinya data category gagal diupdate ke
dalam database, maka kita akan melakukan return ke dalam CategoryResource dengan
status false.

//return failed with Api Resource


return new CategoryResource(false, 'Data Category Gagal Diupdate!',
null);

function destroy

Method ini akan digunakan untuk menghapus data dari database dan gambar yang terkait
akan dihapus juga dari server. Di dalam method ini pertama kita akan melakukan hapus
gambar terlebih dahulu dari server.

//remove image
Storage::disk('local')->delete('public/categories/'.basename($category->
image));

Kode di atas, digunakan untuk menghapus sebuah gambar dari category yang terkait di
dalam folder storage/app/public/categories.

156
Setelah proses hapus gambar berhasil, selanjutnya kita melakukan proses hapus data
category dari database menggunakan kode seperti berikut ini :

$category->delete()

Dan jika proses hapus data berhasil, maka kita akan melakukan return ke dalam
CategoryResource dengan memberikan status true.

//return success with Api Resource


return new CategoryResource(true, 'Data Category Berhasil Dihapus!',
null);

Dan jika proses hapus data gagal dilakukan, maka akan melakukan return ke dalam
CategoryResource dengan status false.

//return failed with Api Resource


return new CategoryResource(false, 'Data Category Gagal Dihapus!',
null);

Langkah 3 - Membuat Route Category


Setelah berhasil membuat controller dan beberapa method di dalamnya, sekarang kita
lanjutkan untuk membuat route agar controller dan method tersebut dapat digunakan.
Silahkan buka file routes/api.php kemudian tambahkan route di bawah ini di dalam
prefix admin dan group middleware auth:api_admin dan tepatnya di bawah route
/dashboard.

//categories resource
Route::apiResource('/categories',
App\Http\Controllers\Api\Admin\CategoryController::class, ['except' =>
['create', 'edit'], 'as' => 'admin']);

Di atas, kita menambahkan except, yang artinya untuk mengecualikan route tertentu.
Karena jika menggunakan route dengan jenis Resource atau ApiRescource, maka semua
method akan dibuatkan sebuah route secara otomatis, karena kita tidak membutuhkan
route create dan edit, maka kita akan melakukan pengecualian di kedua route tersebut.

157
Untuk mengetahui apakah route yang kita tambahkan di atas berhasil atau tidak, kita bisa
menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

Jika belum muncul seperti gambar di atas, silahkan jalankan perintah berikut ini di dalam
terminal/CMD :

php artisan route:cache

Langkah 4 - Uji Coba Rest API Categories


Sekarang kita lanjutkan untuk melakukan uji coba dari setiap method yang sudah kita buat
di atas. Disini kita akan melakukan uji coba di 5 method yang ada di dalam controller
CategoryController.

Uji Coba Method Index

Pertama kita akan lakukan uji coba untuk menampilkan list data category, silahkan buka
aplikasi Postman dan masukkan URL berikut ini http://localhost:8000/api/admin/categories
dan untuk method-nya silahkan pilih GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

158
KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan response dalam
format JSON yang berisi informasi list data category.

Ujia Coba Method Store

Silahkan unduh file-file assets gambar category untuk bahan latihan.


DOWNLOAD :
https://drive.google.com/file/d/1BtdNKWeaB95ks25xAUNS5fTz3ySJ82bx/view?usp=sharing

Selanjutnya kita akan lakukan uji coba untuk melakukan proses insert data category ke
dalam database. Silahkan buka aplikasi Postman, kemudiaan masukkan URL berikut ini
http://localhost:8000/api/admin/categories dan untuk method-nya silahkan pilih POST.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

159
KEY VALUE

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, sekarang klik tab Body kemudian pilih form-data dan masukkan key dan value
berikut ini :

KEY TYPE VALUE

image file Pilih gambar dari komputer.

name text Silahkan diisi dengan nama Category yang diinginkan.

Setelah semua input sudah diisi, sekarang silahkan klik Send dan jika berhasil maka kita
akan mendapatkan sebuah response dalam format JSON yang berisi informasi data category
yang baru saja di insert.

Uji Coba Method Show

Setelah berhasil melakukan proses insert data category ke dalam database, maka sekarang
kita lanjutkan untuk menampilkan data tersebut. Silahkan buka aplikasi Postman kemudian
masukkan URL berikut ini http://localhost:8000/api/admin/categories/1 dan untuk method-
nya silahkan pilih GET.

CATATAN ! : di atas kita akan mencoba menampilkan data category yang memiliki ID 1.
Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

160
KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, sekarang silahkan klik Send dan jika berhasil maka kita akan mendapatkan
sebuah response dengan format JSON yang berisi informasi tentang detail data category
dengan ID 1.

Uji Coba Method Update

Selanjutnya kita akan lakukan uji coba untuk proses update, disini untuk attribute image
sifatnya opsional, yaitu boleh di isi dan boleh dikosongkan.

Silahkan buka aplikasi Postman kemudian masukkan URL berikut ini


http://lcaolhost:8000/adpi/admin/categories/1 dan untuk method-nya silahkan pilih POST.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

161
KEY VALUE

Authorization Bearer <spasi> Token

Jika sudah, sekarang klik tab Body kemudian pilih form-data dan masukkan key dan value
berikut ini :

KEY TYPE VALUE

image file Pilih gambar dari komputer (Tidak Wajib Diisi).

name text Silahkan diisi dengan nama Category yang diinginkan.

_method text PATCH

Setelah semua sudah diisi sekarang silahkan klik Send dan jika berhasil maka kita akan
mendapatkan sebuah response dengan format JSON yang berisi informasi data category
yang diupdate.

Uji Coba Method Destoy

Sekarang kita lanjutkan untuk proses uji coba hapus data dari database. Silahkan buka
aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/categories/1 dan untuk method-nya silahkan pilih DELETE.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

162
KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, siljhkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dengan format JSON yang berisi informasi data category yang sudah dihapus.

CATATAN ! : silahkan tambahkan data category yang banyak ke dalam database untuk
digunakan pada materi CRUD Product nanti.

163
Membuat Restful API CRUD Products

Setelah di materi sebelumnya kita belajar bagaimana cara membuat sebuah resource,
controller berserta method-nya dan cara uji coba Rest Api untuk data category di aplikasi
Postman, maka sekarang kita akan melakukan hal yang sama lagi tetapi dengan data yang
berbeda. Yups kita akan membuat sebuah CRUD Rest API untuk data product.

Langkah 1 - Membuat Resource Product


Sebelum membuat sebuah controller, maka kita perlu menyiapkan sebuah resource terlebih
dahulu, karena resource ini akan kita gunakan nanti di dalam controller untuk melakukan
transformasi data dari Model ke dalam format JSON secara mudah dan cepat.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan tentunya harus berada di
dalam folder Laravel-nya.

php artisan make:resource ProductResource

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru yang telah
di generate dengan nama ProductResource.php yang berada di dalam folder
app/Http/Resources/.

Silahkan buka file tersebut dan kita akan melakukan kustomisasi agar response JSON yang
di hasilkan bisa sesuai dengan yang diharapkan, setelah berhasil dibuka kemudian ubah
semua kode-nya menjadi seperti berikut ini :

164
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class ProductResource extends JsonResource


{
//public properti
public $status;
public $message;
/**
* __construct
*
* @param mixed $status
* @param mixed $message
* @param mixed $resource
* @return void
*/
public function __construct($status, $message, $resource)
{
parent::__construct($resource);
$this->status = $status;
$this->message = $message;
}

/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}

Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.

165
public $status;
public $message;

Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :

$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.

Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :

return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];

Langkah 2 - Membuat Controller Product


Setelah berhasil membuat resource dan sekaligus melakukan modifikasi di dalamnya,
sekarang kita lanjutkan untuk membuat sebuah controller baru untuk product.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan tentunya berada di dalam
project Laravel.

php artisan make:controller Api\\Admin\\ProductController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller yang
telah di generate dengan nama ProductController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah semua kode-
nya menjadi seperti berikut ini :

166
<?php

namespace App\Http\Controllers\Api\Admin;

use App\Models\Product;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\ProductResource;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;

class ProductController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//get products
$products = Product::with('category')->when(request()->q,
function($products) {
$products = $products->where('title', 'like', '%'.
request()->q . '%');
})->latest()->paginate(5);
//return with Api Resource
return new ProductResource(true, 'List Data Products',
$products);
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'image' =>
'required|image|mimes:jpeg,jpg,png|max:2000',
'title' => 'required|unique:products',
'category_id' => 'required',

167
'description' => 'required',
'weight' => 'required',
'price' => 'required',
'stock' => 'required',
'discount' => 'required'
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

//upload image
$image = $request->file('image');
$image->storeAs('public/products', $image->hashName());

//create product
$product = Product::create([
'image' => $image->hashName(),
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'user_id' => auth()->guard('api_admin')->user()->id,
'description' => $request->description,
'weight' => $request->weight,
'price' => $request->price,
'stock' => $request->stock,
'discount' => $request->discount
]);

if($product) {
//return success with Api Resource
return new ProductResource(true, 'Data Product Berhasil
Disimpan!', $product);
}

//return failed with Api Resource


return new ProductResource(false, 'Data Product Gagal
Disimpan!', null);
}

/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/

168
public function show($id)
{
$product = Product::whereId($id)->first();
if($product) {
//return success with Api Resource
return new ProductResource(true, 'Detail Data Product!',
$product);
}

//return failed with Api Resource


return new ProductResource(false, 'Detail Data Product Tidak
Ditemukan!', null);
}

/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Product $product)
{
$validator = Validator::make($request->all(), [
'title' =>
'required|unique:products,title,'.$product->id,
'category_id' => 'required',
'description' => 'required',
'weight' => 'required',
'price' => 'required',
'stock' => 'required',
'discount' => 'required'
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

//check image update


if ($request->file('image')) {

//remove old image


Storage::disk('local')->delete('public/products/'.basename($product->ima
ge));
//upload new image
$image = $request->file('image');

169
$image->storeAs('public/products', $image->hashName());

//update product with new image


$product->update([
'image' => $image->hashName(),
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'user_id' =>
auth()->guard('api_admin')->user()->id,
'description' => $request->description,
'weight' => $request->weight,
'price' => $request->price,
'stock' => $request->stock,
'discount' => $request->discount
]);

//update product without image


$product->update([
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'user_id' => auth()->guard('api_admin')->user()->id,
'description' => $request->description,
'weight' => $request->weight,
'price' => $request->price,
'stock' => $request->stock,
'discount' => $request->discount
]);

if($product) {
//return success with Api Resource
return new ProductResource(true, 'Data Product Berhasil
Diupdate!', $product);
}

//return failed with Api Resource


return new ProductResource(false, 'Data Product Gagal
Diupdate!', null);
}

/**
* Remove the specified resource from storage.
*

170
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Product $product)
{
//remove image
Storage::disk('local')->delete('public/products/'.basename($product->ima
ge));

if($product->delete()) {
//return success with Api Resource
return new ProductResource(true, 'Data Product Berhasil
Dihapus!', null);
}

//return failed with Api Resource


return new ProductResource(false, 'Data Product Gagal Dihapus!',
null);
}
}

Dari perubahan kode di atas, pertama kita melakukan import Model Product terlebih
dahulu, karena kita akan gunakan untuk proses yang berhubungan dengan database,
seperti menampilkan, insert, update dan delete data.

use App\Models\Product;

Setelah itu, kita juga import Helper Str dari Laravel, yang mana akan kita gunakan nanti
untuk melakukan generate slug untuk data product.

use Illuminate\Support\Str;

kemudian kita juga import Http Request dari Laravel, dimana akan digunakan untuk
menerima sebuah input yang dikirim melalui cookie, form, URL dan lain-lain.

use Illuminate\Http\Request;

Dan kita import ProductResource yang sebelumnya sudah kita buat di atas, Resource ini

171
akan digunakan untuk mentransformasi Model kita ke dalam format JSON dengan lebih baik
dan cepat.

use App\Http\Resources\PostResource;

Karena kita akan melakukan upload gambar, maka kita juga akan import Facades Storage
dari Laravel.

use Illuminate\Support\Facades\Storage;

kita juga akan membutuhkan sebuah validasi, maka kita perlu melakukan import juga, disini
kita import Facades Validator dari Laravel.

use Illuminate\Support\Facades\Validator;

Jika di perhatikan, di dalam class ProductController kita telah menambahkan 5 method


baru, diantaranya adalah :

1. function index
2. function store
3. function show
4. function update
5. function destroy

function index

Method ini akan kita gunakan untuk menampilkan list data product dari database. Dan di
dalam method ini kita melakukan beberapa kondisi, kurang lebih seperti berikut ini :

//get products
$products = Product::with('category')->when(request()->q,
function($products) {
$products = $products->where('title', 'like', '%'. request()->q
. '%');
})->latest()->paginate(5);

Kode di atas merupakan Eloquent di Laravel untuk mendapatkan list data menggunakan
Model. Kemudian kita juga menambahkan eager loaded menggunakan method with,

172
dimana method tersebut akan memanggil relasi yang bernama category di dalam Model
Product. Dan jika diterjemahkan seperti melakukan sebuah query join dari 2 table yang
berbeda.

Setelah itu, kita membuat kondisi lagi untuk proses pencarian, disini kita menggunakan
method when yang artinya jika ada sebuah request bernama q, maka akan melakukan
proses pencarian data berdasarkan title yang sesuai dengan isi dari request q tersebut.

Setelah itu, kita urutkan data yang akan ditampilkan berdasarkan yang paling terbaru, disini
kita menggunakan method latest(). Dan data yang akan ditampilkan akan dibatas
perhalaman 5 record menggunakan method paginate().

Kemudian, agar variable $products di atas dapat menjadi sebuah format JSON, maka kita
perlu menkonversinya ke dalam ProductResource, kurang lebih seperti berikut ini :

//return with Api Resource


return new ProductResource(true, 'List Data Products', $products);

function store

Method ini yang akan digunakan untuk melakukan proses insert data ke dalam database
dan juga melakukan proses upload gambar ke dalam server. Sebelum data disimpan,
pertama-tama kita akan lakukan validasi terlebih dahulu agar data yang akan disimpan
sudah benar-benar sesuai dengan yang diharapkan.

$validator = Validator::make($request->all(), [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
'title' => 'required|unique:products',
'category_id' => 'required',
'description' => 'required',
'weight' => 'required',
'price' => 'required',
'stock' => 'required',
'discount' => 'required'
]);

Dari definisi validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :

KEY VALIDASI KETERANGAN

image required field wajib diisi.

173
KEY VALIDASI KETERANGAN

image field harus memiliki extensi gambar.

extensi gambar yang boleh diupload adalah .jpeg,


mimes:png,jpg,jpeg
.jpg dan .png.

gambar yang boleh diupload maksimal memiliki


max:2000
ukuran 2000 Kb / 2 Mb.

title required field wajib diisi.

field bersifat unik dan tidak boleh ada yang sama


unique:product
di dalam table products.

category_id required field wajib diisi.

description required field wajib diisi.

weight required field wajib diisi.

price required field wajib diisi.

stock required field wajib diisi.

discount required field wajib diisi.

Jika data yang akan disimpan tidak sesuai dengan validasi di atas, maka akan
mengembalikan sebuah response error validasi menggunakan kode berikut ini :

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

Tapi, jika data yang akan di insert sudah sesuai dengan yang diharapkan, maka pertama
akan melakukan proses upload gambar ke dalam server menggunakan kode berikut ini :

//upload image
$image = $request->file('image');
$image->storeAs('public/products', $image->hashName());

Di atas, pertama kita membuat variable dengan nama $image yang isinya akan mengambil
sebuah request dengan jenis file dan bernama image, setelah itu akan dipindahkan ke
dalam folder /storage/app/public/categories menggunakan method storeAs dan
nama dari file tersebut akan di ubah menjadi random menggunakan method hashName().

174
Setelah proses upload gambar selesai, sekarang akan dilanjutkan untuk proses insert data
ke dalam database menggunakan Eloquent, kurang lebih seperti berikut ini :

//create product
$product = Product::create([
'image' => $image->hashName(),
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'user_id' => auth()->guard('api_user')->user()->id,
'description' => $request->description,
'weight' => $request->weight,
'price' => $request->price,
'stock' => $request->stock,
'discount' => $request->discount
]);

Dari kode proses insert di atas, kurang lebih penjelasannya seperti berikut ini :

ATTRIBUTE VALUE KETERANGAN

akan mengambil nama dari


image $image->hashName()
gambar yang diupload

akan mengambil data dari


title $request->title
request yang bernama title

akan diisi dengan request


title dan di masukkan ke
dalam helper Str::slug
slug Str::slug($request->title, '-') untuk menghasilkan sebuah
URL dari title post dan akan di
tambahkan notasi (-) untuk
pemisah antar kata.

akan mengambil data dari


category_id $request->category_id request yang bernama
category_id

akan mengambil ID dari user


user_id auth()->guard('api_admin')->user()->id
yang sedang login

akan mengambil data dari


description $request->description request yang bernama
description

175
ATTRIBUTE VALUE KETERANGAN

akan mengambil data dari


weight $request->weight request yang bernama
weight

akan mengambil data dari


price $request->price
request yang bernama price

akan mengambil data dari


stock $request->stock
request yang bernama stock

akan mengambil data dari


discount $request->discount request yang bernama
discount

Setelah itu, kita membuat kondisi untuk memastikan apakah proses insert data di atas
berhasil atau tidak. Disini kita mengecek jika variable $product bernilai true, maka akan
melakukan return ke dalam ProductResource dengan status true.

if($product) {
//return success with Api Resource
return new ProductResource(true, 'Data Product Berhasil Disimpan!',
$product);
}

Tapi jika variable $product bernilai false, artinya proses insert data gagal dilakukaan,
maka akan melakukan return ke dalam ProductResource dengan status false.

//return failed with Api Resource


return new ProductResource(false, 'Data Product Gagal Disimpan!', null);

function show

Method ini akan digunakan untuk menampilkan informasi detail data product berdasarkan
ID, dimana di dalam method ini kita melakukan pencarian data menggunakan ELoquent
dengan parameter ID yang di dapatkan dari URL.

$product = Product::whereId($id)->first();

Jika variable $product bernilai true, artinya data ditemukan di dalam database dan kita

176
akan melakukan return ke dalam ProductResource dengan status true.

if($product) {
//return success with Api Resource
return new ProductResource(true, 'Detail Data Product!', $product);
}

Tapi, jika variable tersebut bernilai false, yang artinya data tidak ditemukan di dalam
database, maka kita akan lakukan return ke dalam ProductResource dengan status
false.

//return failed with Api Resource


return new ProductResource(false, 'Detail Data Product Tidak
Ditemukan!', null);

function update

Method ini akan digunakan untuk melakukaan proses update data ke dalam database dan
untuk attribute image ini sifatnya opsional, yang artinya bisa diubah dan juga tidak.

Tentu saja kita akan menambahkan sebuah validasi terlebih dahulu untuk memeriksa data
yang akan di update ke dalam database, apakah sudah sesuai dengan apa yang diharapkan.

$validator = Validator::make($request->all(), [
'title' => 'required|unique:products,title,'.$product->id,
'category_id' => 'required',
'description' => 'required',
'weight' => 'required',
'price' => 'required',
'stock' => 'required',
'discount' => 'required'
]);

Dari penambahan kode validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini
:

KEY VALIDASI KETERANGAN

title required field wajib diisi.

177
KEY VALIDASI KETERANGAN

field bersifat unik dan tidak boleh ada


yang sama di dalam table prducts.
Karena bersifat unik, maka untuk
unique:products,title,'.$post->id proses update kita tambahkan
title,'.$product->id, yang
artinya kusus ID product ini akan
dikecualikan.

category_id required field wajib diisi.

description required field wajib diisi.

weight required field wajib diisi.

price required field wajib diisi.

stock required field wajib diisi.

discount required field wajib diisi.

Jika data yang akan diupdate belum sesuai dengan ketentuan validasi di atas, maka kita
akan melakukan return atau mengembalikan ke sebuah format JSON dengan informasi
validasi.

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

Tapi jika validasi sudah sesuai dengan yang di harapkan, maka selanjutnya kita akan
membuat kondisi lagi untuk pengecekan request file gambar. Jika request file dengam nama
image memiliki value, maka kita akan melakukan update data post dengan gambar baru.

//check image update


if ($request->file('image')) {

//...

Di dalamnya kita melakukan remove atau mengahpus data gambar lama di dalam server.

178
//remove old image
Storage::disk('local')->delete('public/products/'.basename($product->ima
ge));

Setelah gambar lama berhasil dihapus, selanjutnya kita melakukan proses upload gambar
yang baru.

//upload new image


$image = $request->file('image');
$image->storeAs('public/products', $image->hashName());

Dan jika gambar sudah berhasil terupload di dalam server, maka selanjutnya kita akan
melakukan proses update data product ke dalam database.

//update product with new image


$product->update([
'image' => $image->hashName(),
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'user_id' => auth()->guard('api_admin')->user()->id,
'description' => $request->description,
'weight' => $request->weight,
'price' => $request->price,
'stock' => $request->stock,
'discount' => $request->discount
]);

Kemudian jika request file dengan nama image tidak memiliki value, maka kita cukup
melakukan update data product tanpa melakukan update attribute image.

179
//update product without image
$product->update([
'title' => $request->title,
'slug' => Str::slug($request->title, '-'),
'category_id' => $request->category_id,
'user_id' => auth()->guard('api_admin')->user()->id,
'description' => $request->description,
'weight' => $request->weight,
'price' => $request->price,
'stock' => $request->stock,
'discount' => $request->discount
]);

Setelah itu, untuk memastikan proses update data berhasil atau tidak, kita akan membuat
sebuah kondisi untuk mengecek nilai dari variable $product. Jika variable tersebut bernilai
true, maka kita akan melakukan return ke dalam ProductResource dengan status true.

if($product) {
//return success with Api Resource
return new ProductResource(true, 'Data Product Berhasil Diupdate!',
$product);
}

Tapi, jika variable $product bernilai false, yang artinya data product gagal diupdate ke
dalam database, maka kita akan melakukan return ke dalam ProductResource dengan
status false.

//return failed with Api Resource


return new ProductResource(false, 'Data Product Gagal Diupdate!', null);

function destroy

Method ini akan digunakan untuk melakukan proses hapus data di dalam database sesuai
dengan ID yang dikirimkan dan disini kita juga akan melakukan proses hapus gambar yang
terkait di dalam server.

180
//remove image
Storage::disk('local')->delete('public/products/'.basename($product->ima
ge));

Kode di atas, digunakan untuk menghapus gambar yang terkait dengan data product, dan
untuk lokasi gambar tersebut berada di dalam folder
/storage/app/public/categories/.

Setelah proses hapus gambar di dalam server berhasil, selanjutnya kit melakukan proses
hapus data product di dalam database, kurang lebih seperti ini :

$product->delete()

Jika proses hapus data berhasil, maka kita akan lakukan return atau mengembalikan ke
dalam ProductResource dengan status true.

//return success with Api Resource


return new ProductResource(true, 'Data Product Berhasil Dihapus!',
null);

Tapi, jika proses hapus data di dalam database gagal dilakukan, maka kita juga akan
melakukan return atau mengembalikan ke dalam ProductResource dengan status false.

//return failed with Api Resource


return new ProductResource(false, 'Data Product Gagal Dihapus!', null);

Langkah 3 - Menambahkan Route Product


Setelah berhasil membuat controller dan menambahkan method di dalamnya, maka
sekarang kita lanjutkan untuk menambahkan route agar controller product dapat diakses.

Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route
/categories.

181
//products resource
Route::apiResource('/products',
App\Http\Controllers\Api\Admin\ProductController::class, ['except' =>
['create', 'edit'], 'as' => 'admin']);

Sama seperti sebelumnya, kita menambahkan except untuk mengecualikan beberapa


method dari resource, yaitu create dan edit, karena kita tidak membutuhkan route
tersebut dalam kasus ini.

Untuk memastikan apakah route yang sudah kita tambahkan berhasil, kita bisa melakukan
verifikasi dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

182
Langkah 4 - Uji Coba Rest API Product
Sebelum memulai uji coba Rest API products, jangan lupa menambahkan data baru untuk
categories, berikut ini contoh data categories yang saya buat, silahkan disesuaikan dengan
keinginan masing-masing.

Untuk menambahkan data tersebut, silahkan bisa melalui aplikasi Postman, seperti di
langkah-langkah sebelumnya. Untuk datanya bebas, jadi silahkan disesuaikan dengan
keinginan masing-masing.

Uji Coba Method Index

Pertama kita akan lakukan uji coba untuk mendapatkan list data products dari database.
Silahkan buka aplikasi Postman kemudian masukkan URL berikut ini
http://localhost:8000/api/admin/products dan untuk method-nya silahkan pilih GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data products.

183
Uji Coba Method Store

Silahkan unduh file-file assets gambar product untuk bahan latihan.


DOWNLOAD :
https://drive.google.com/file/d/15mk8YLo_IYnQ08TNt_2AkFFObGHwF-Q_/view?usp=sharing

Sekarang kita lanjutkan untuk melakukan uji coba proses insert data products ke dalam
database. Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/products dan untuk method-nya silahkan pilih POST.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Setelah itu, silahkan klik tab Body dan pilih form-data kemudian masukkan key dan value
seperti berikut ini :

KEY TYPE VALUE

image file Pilih gambar dari komputer.

184
KEY TYPE VALUE

title text Silahkan diisi dengan judul Product yang diinginkan.

category_id text Silahkan disesuaikan dengan ID category yang diinginkan.

description text Silahkan diisi dengan deskripsi yang diinginkan.

Silahkan disesuaikan dengan berat yang diinginkan (dalam


weight text
satuan gram).

price text Silahkan disesuaikan dengan harga yang diinginkan.

stock text Silahkan disesuaikan dengan jumlah yang diinginkan.

Silahkan disesuaikan dengan discount yang diinginkan (satuan


discount text
persen).

Dari key dan value di atas, saya memberikan contoh seperti berikut ini :

CATATAN ! : untuk category_id silahkan disesuikan dengan ID dari masing-masing data


di dalam database.

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data product yang baru saja di insert.

185
Uji Coba Method Show

Sekarang kita lanjutkan untuk proses uji coba menampilkan data product yang ada di dalam
database, dan di dalam contoh kali ini kita akan menampilkan data product yang memiliki ID
1.

Silahkan buka aplikasi Postman dan masukkan URL berikut ini


http://localhost:8000/api/admin/products/1 dan untuk method-nya silahkan pilih GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan response yang
berisi informasi detail data product dengan ID 1.

186
Uji Coba Update

Sekarang kita lanjutkan untuk proses uji coba update data product ke dalam database. Dan
disini kita akan mencoba melakukan update data product yang memiliki ID 1. Silahkan buka
aplikasi Postman dan masukkan URL berikut ini http://localhost:8000/api/admin/products/1
dan untuk method-nya silahkan pilih POST.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Setalah itu, silahkan masuk ke tab Body dan pilih form-data dan masukkan key dan value
seperti berikut ini :

KEY TYPE VALUE

image file Pilih gambar dari komputer (tidak wajib diisi).

title text Silahkan diisi dengan judul Product yang diinginkan.

category_id text Silahkan disesuaikan dengan ID category yang diinginkan.

187
KEY TYPE VALUE

description text Silahkan diisi dengan deskripsi yang diinginkan.

Silahkan disesuaikan dengan berat yang diinginkan (dalam


weight text
satuan gram).

price text Silahkan disesuaikan dengan harga yang diinginkan.

stock text Silahkan disesuaikan dengan jumlah yang diinginkan.

Silahkan disesuaikan dengan discount yang diinginkan (satuan


discount text
persen).

_method text PATCH

Dari key dan value di atas, saya memberikan contoh seperti berikut ini :

Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan response dalam
format JSON yang berisi informasi data product yang baru saja diupdate.

188
Uji Coba Destroy

Sekarang kita lanjutkan untuk proses uji coba menghapus data product dari database. Dan
disini kita juga akan menggunakan data product yang memiliki ID 1.

Silahkan buka aplikasi Postman dan masukkan URL berikut ini


http://localhost:8000/api/admin/products/1 dan untuk method-nya silahkan pilih DELETE.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data product yang telah dihapus.

189
190
Membuat Restful API Invoices Admin

Setalah berhasil membuat Rest API untuk CRUD data products, sekarang kita akan lanjutkan
untuk membuat Rest API untuk menampilkan data invoices, disini kita tidak akan membuat
proses CRUD seperti sebelumnya, karena data invoice ini akan digenerate oleh customer
nantinya dan di halaman admin tinggal menampilkan data tersebut.

Langkah 1 - Membuat Resource Invoice


Seperti sebelum-sebelumnya, ketika kita ingin membuat sebuah Rest API dengan lebih
mudah dan cepat, maka kita akan menggunakan fitur Resource di Laravel, yaitu untuk
melakukan konversi dari Model menjadi format JSON.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan tentu saja harus berada di
dalam project Laravel.

php artisan make:resource InvoiceResource

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan
nama InvoiceResource.php yang berada di dalam folder app/Http/Resources.
Silahkan buka file tersebut, karena kita akan melakukan modifikasi agar response JSON
yang dihasilkan bisa sesuai dengan yang diharapkan. Setelah berhasil dibuka, silahkan ubah
semua kode-nya menjadi seperti berikut ini :

191
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class InvoiceResource extends JsonResource


{
//public properti
public $status;
public $message;
/**
* __construct
*
* @param mixed $status
* @param mixed $message
* @param mixed $resource
* @return void
*/
public function __construct($status, $message, $resource)
{
parent::__construct($resource);
$this->status = $status;
$this->message = $message;
}

/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}

Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.

192
public $status;
public $message;

Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :

$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.

Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :

return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];

Langkah 2 - Membuat Controller Invoice


Setelah berhasil membuat sebuah resource, maka sekarang kita lanjutkan untuk membuat
controller-nya, dan nantinya di dalam controller ini kita hanya akan menambahkan 2 method
saja, yaitu index untuk menamplikan list data dan show untuk menampilkan detail data.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\Admin\\InvoiceController

Jika perintah berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan nama
InvoiceController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah semua kode-
nya menjadi seperti berikut ini :

193
<?php

namespace App\Http\Controllers\Api\Admin;

use App\Models\Invoice;
use App\Http\Controllers\Controller;
use App\Http\Resources\InvoiceResource;

class InvoiceController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$invoices = Invoice::with('customer')->when(request()->q,
function($invoices) {
$invoices = $invoices->where('invoice', 'like', '%'.
request()->q . '%');
})->latest()->paginate(5);

//return with Api Resource


return new InvoiceResource(true, 'List Data Invoices',
$invoices);
}

/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$invoice = Invoice::with('orders.product', 'customer', 'city',
'province')->whereId($id)->first();
if($invoice) {
//return success with Api Resource
return new InvoiceResource(true, 'Detail Data Invoice!',
$invoice);
}

//return failed with Api Resource

194
return new InvoiceResource(false, 'Detail Data Invoice Tidak
Ditemukan!', null);
}
}

Dari perubahan kode di atas, pertama kita melakukan import Model Invoice terlebih
dahulu, karena kita akan menggunakan model tersebut untuk mendapatkan data dari
database.

use App\Models\Invoice;

Setelah itu, kita juga melakukan import untuk InvoiceResource, file ini yang nantinya
akan digunakan untuk mentransformasi model ke dalam format JSON menjadi sangat
mudah dan baik.

use App\Http\Resources\InvoiceResource;

Dan jika kita perhatikan, di dalam class InvoiceController, kita menambahkan 2


method baru, yaitu :

1. function index
2. function show

funtion index

Method ini akan digunakan untuk menampilkan list data invoice dari dalam database, disini
kita menggunakan Eloquent untuk proses getting datanya.

$invoices = Invoice::with('customer')->when(request()->q,
function($invoices) {
$invoices = $invoices->where('invoice', 'like', '%'. request()->q .
'%');
})->latest()->paginate(5);

Kode di atas merupakan Eloquent di Laravel untuk mendapatkan list data menggunakan
Model. Setelah itu, kita membuat kondisi lagi untuk proses pencarian, disini kita
menggunakan method when yang artinya jika ada sebuah request bernama q, maka akan
melakukan proses pencarian data berdasarkan attribute invoice yang sesuai dengan isi

195
dari request q tersebut.

Setelah itu, kita urutkan data yang akan ditampilkan berdasarkan yang paling terbaru, disini
kita menggunakan method latest(). Dan data yang akan ditampilkan akan dibatas
perhalaman 5 record menggunakan method paginate().

Kemudian, agar variable $invoices di atas dapat menjadi sebuah format JSON, maka kita
perlu menkonversinya ke dalam InvoiceResource, kurang lebih seperti berikut ini :

//return with Api Resource


return new InvoiceResource(true, 'List Data Invoices', $invoices);

function show

Method ini akan digunakan untuk mendapatkaan detail data invoice dari dalam database
berdasarkan ID yang di dapatkaan dari parameter.

$invoice = Invoice::with('orders.product', 'customer', 'city',


'province')->whereId($id)->first();

Kode diatas digunakan untuk mencari data invoice berdasarkan ID, dan kita juga memanggil
beberapa relasi menggunakan eager loaded yaitu method with, yang mana di dalamnya
ada beberapa relasi, diantaranya adalah :

1. invoice -> orders -> product


2. invoice -> customer.
3. invoice -> city.
4. invoice -> province.

Relasi tersebut merupakan pendefinisian sebuah method yang sebelumnya sudah kita buat
di dalam materi Eloquent Relationships.

Setelah itu, kita membuat kondisi untuk memastikan apakah variable $invoice tersebut
berilai true atau false, jika bernilai true, artinya data ditemukan di dalam database, dan
kita akan melakukan return ke dalam InvoiceResource dengan status true.

196
if($invoice) {
//return success with Api Resource
return new InvoiceResource(true, 'Detail Data Invoice!', $invoice);
}

Tapi, jika variable tersebut bernilai false, artinya data invoice tidak ditemukan di dalam
database, maka kita akan melakukan return ke dalam InvoiceResource dengan status
false.

//return failed with Api Resource


return new InvoiceResource(false, 'Detail Data Invoice Tidak
Ditemukan!', null);

Langkah 3 - Menambahkan Route Invoice


Setelah berhasil membuat controller dan menambahkan method di dalamnya, maka
sekarang kita lanjutkan untuk menambahkan route agar controller invoice dapat diakses.

Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route
/products.

//invoices resource
Route::apiResource('/invoices',
App\Http\Controllers\Api\Admin\InvoiceController::class, ['except' =>
['create', 'store', 'edit', 'update', 'destroy'], 'as' => 'admin']);

Sama seperti sebelumnya, kita menambahkan except untuk mengecualikan beberapa


method dari resource, yaitu create, store, edit, update dan destroy, karena kita
hanya membutuhkan route index dan juga show saja.

Untuk memastikan apakah route yang sudah kita tambahkan berhasil, kita bisa melakukan
verifikasi dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

197
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 4 - Uji Coba Rest API Invoice


Sekarang kita lanjutkan untuk melakukan uji coba dari setiap method yang sudah kita buat
di atas. Disini kita akan melakukan uji coba di method index, karena untuk melakukan uji
coba method show datanya belum tersedia.

Uji Coba Method Index

Pertama kita akan lakukan uji coba untuk mendapatkan list data invoices dari database.
Silahkan buka aplikasi Postman kemudian masukkan URL berikut ini
http://localhost:8000/api/admin/invoices dan untuk method-nya silahkan pilih GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

198
KEY VALUE

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data invoices.

199
Membuat Restful API Customers

Di materi kali ini kita semua akan belajar bagaimana cara membuat Rest API untuk
menampilkan list data customer yang sudah register di dalam website toko online kita.
Pertama-tama tentu saja kita akan membuat Resource terlebih dahulu, karena dengan
menggunakan fitur ini proses membuat JSON sangat mudah dan cepat.

Langkah 1 - Membuat Resource Customer


Pertama, kita akan membuat Resource untuk customer terlebih dahulu, silahkan jalankan
perintah berikut ini di dalam terminal/CMD :

php artisan make:resource CustomerResource

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan
nama CustomerResource.php yang berada di dalam folder app/Http/Resources.
Silahkan buka file tersebut dan ubah semua kode-nya menjadi seperti berikut ini :

200
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class CustomerResource extends JsonResource


{
//public properti
public $status;
public $message;
/**
* __construct
*
* @param mixed $status
* @param mixed $message
* @param mixed $resource
* @return void
*/
public function __construct($status, $message, $resource)
{
parent::__construct($resource);
$this->status = $status;
$this->message = $message;
}

/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}

Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.

201
public $status;
public $message;

Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :

$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.

Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :

return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];

Langkah 2 - Membuat Controller Customer


Sekarang kita lanjutkan untuk membuat controller customer, di dalam controller ini nanti
kita akan buat 1 method aja, yaitu index, yang fungsinya untuk menampilkan list data
customer.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Laravel-nya.

php artisan make:controller Api\\Admin\\CustomerController

Jika berhasil, maka kita akan mendapatkan 1 file baru dengan nama
CustomerController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikuit ini :

202
<?php

namespace App\Http\Controllers\Api\Admin;

use App\Models\Customer;
use App\Http\Controllers\Controller;
use App\Http\Resources\CustomerResource;

class CustomerController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$customers = Customer::when(request()->q, function($customers) {
$customers = $customers->where('name', 'like',
'%'. request()->q . '%');
})->latest()->paginate(5);

//return with Api Resource


return new CustomerResource(true, 'List Data Customer',
$customers);
}
}

Dari perubahan kode di atas, pertama-tama kita melakukan import Model Customer
terlebih dahulu, karena kita akan menggunakan model untuk mendapatkan data dari
database.

use App\Models\Customer;

Kemudian, kita juga melakukan import ResourceCustomer, ini akan digunakan untuk
melakukan transformasi atau konversi data dari model menjadi format JSON dengan sangat
mudah dan cepat.

use App\Http\Resources\CustomerResource;

Dan di dalam class CustomerController, kita menambahkan 1 method baru dengan

203
nama index, di dalam method tersebut kita melakukan getting data dari database
menggunakan Model, kurang lebih seperti berikut ini :

$customers = Customer::when(request()->q, function($customers) {


$customers = $customers->where('name', 'like', '%'. request()->q .
'%');
})->latest()->paginate(5);

Di atas, kita melakukan get data ke dalam database mengunakan Model dan di dalamnya
kita membuat kondisi untuk proses pencarian menggunakan method when. Jika ada sebuah
request dengan nama q, maka akan melakuakn pencarian berdasarkan attribute name dan
di cocokan dengan isi dari request q tersebut.

Setelah itu, kita urutkan data yang akan ditampilkan berdasarkan yang paling terbaru, disini
kita menggunakan method latest(). Dan data yang akan ditampilkan akan dibatas
perhalaman 5 record menggunakan method paginate().

Kemudian, agar variable $customers di atas dapat menjadi sebuah format JSON, maka
kita perlu menkonversinya ke dalam CustomerResource, kurang lebih seperti berikut ini :

//return with Api Resource


return new CustomerResource(true, 'List Data Customer', $customers);

Langkah 3 - Menambahkan Route Customer


Setelah berhasil membuat controller dan menambahkan method di dalamnya, maka
sekarang kita lanjutkan untuk menambahkan route agar controller customer dapat diakses.

Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route
/invoices.

//customer
Route::get('/customers',
[App\Http\Controllers\Api\Admin\CustomerController::class, 'index',
['as' => 'admin']]);

Untuk memastikan apakah route yang sudah kita tambahkan berhasil, kita bisa melakukan
verifikasi dengan menjalankan perintah berikut ini di dalam terminal/CMD :

204
php artisan route:list

Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 4 - Uji Coba Rest API Customer


Sekarang kita lanjutkan untuk melakukan uji coba dari method yang sudah kita buat di atas.
Disini kita akan melakukan uji coba di method index.

Uji Coba Method Index

Pertama kita akan lakukan uji coba untuk mendapatkan list data customers dari database.
Silahkan buka aplikasi Postman kemudian masukkan URL berikut ini
http://localhost:8000/api/admin/customers dan untuk method-nya silahkan pilih GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

205
KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data customers.

206
Membuat Restful API CRUD Sliders

Kita lanjutkan untuk membuat Rest API untuk proses CRUD data sliders, karena data slider
maka kita otomatis akan melakukan proses upload gambar ke dalam server. Dan di dalam
controller nanti kita akan buat beberapa method saja, yaitu index, store dan destroy,
karena akan disesuaikan dengan UI di halaman website nanti.

Langkah 1 - Membuat Resource Slider


Pertama kita akan membuat sebuah Resource terlebih dahulu, dimana Resource ini akan
kita gunakan untuk melakukan transformasi dari Model ke dalam format JSON. Silahkan
jalankan perintah berikut ini di dalam terminal/CMD untuk melakukan generate Resource
baru untuk slider.

php artisan make:resource SliderResource

Jika perintah di atas berhasil di jalankan, maka kita akan mendapatkan 1 file baru dengan
nama SliderResource.php yang berada di dalam folder app/Http/Resources.
Silahkan buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :

207
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class SliderResource extends JsonResource


{
//public properti
public $status;
public $message;
/**
* __construct
*
* @param mixed $status
* @param mixed $message
* @param mixed $resource
* @return void
*/
public function __construct($status, $message, $resource)
{
parent::__construct($resource);
$this->status = $status;
$this->message = $message;
}

/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}

Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.

208
public $status;
public $message;

Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :

$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.

Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :

return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];

Langkah 2 - Membuat Controller Slider


Kita lanjutkan untuk membuat controller slider dan sekaligus menambahkan beberapa
method di dalamnya, silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\Admin\\SliderController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama SliderController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Admin;

209
use App\Models\Slider;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\SliderResource;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;

class SliderController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//get sliders
$sliders = Slider::latest()->paginate(5);
//return with Api Resource
return new SliderResource(true, 'List Data Sliders', $sliders);
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

//upload image
$image = $request->file('image');
$image->storeAs('public/sliders', $image->hashName());

//create slider
$slider = Slider::create([
'image'=> $image->hashName(),
'link' => $request->link,

210
]);

if($slider) {
//return success with Api Resource
return new SliderResource(true, 'Data Slider Berhasil
Disimpan!', $slider);
}

//return failed with Api Resource


return new SliderResource(false, 'Data Slider Gagal Disimpan!',
null);
}

/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Slider $slider)
{
//remove image
Storage::disk('local')->delete('public/sliders/'.basename($slider->image
));

if($slider->delete()) {
//return success with Api Resource
return new SliderResource(true, 'Data Slider Berhasil
Dihapus!', null);
}

//return failed with Api Resource


return new SliderResource(false, 'Data Slider Gagal Dihapus!',
null);
}
}

Dari perubahan kode di atas, pertama kita melakukan import Model Slider terlebih dahulu.

use App\Models\Slider;

kemudian kita juga import Http Request dari Laravel, dimana akan digunakan untuk

211
menerima sebuah input yang dikirim melalui cookie, form, URL dan lain-lain.

use Illuminate\Http\Request;

Dan kita juga import SliderResource yang sebelumnya sudah kita buat di atas, dan
fungsinya yang akan mentransformasi dari Model menjadi format JSON.

use App\Http\Resources\SliderResource;

Karena kita akan melakukan upload gambar, maka kita juga akan import Facades Storage
dari Laravel.

use Illuminate\Support\Facades\Storage;

kita juga akan membutuhkan sebuah validasi, maka kita perlu melakukan import juga, disini
kita import Facades Validator dari Laravel.

use Illuminate\Support\Facades\Validator;

Dan di dalam class SliderController kita menambahkan 3 method baru, yaitu :

1. function index
2. function store
3. function destroy

function index

Method ini akan kita gunakan untuk mendapatkan data slider dari database. Dan disni kita
tampilkan data-nya dari yang paling terbaru menggunakan method latest() dan akan
dibatasi data yang di tampilkan perhalaman menggunakan method paginate.

$sliders = Slider::latest()->paginate(5);

Setelah itu, agar varibale $sliders di atas dapat berubah menjadi format JSON, maka kita
perlu melakukan parsing variable tersebut ke dalam SliderResource.

212
//return with Api Resource
return new SliderResource(true, 'List Data Sliders', $sliders);

function store

Method ini akan digunakan untuk proses insert data ke dalam database, dan disini kita juga
akan melakukan proses upload gambar ke dalam server. Sebelum semuanya dijalankan, kita
akan membuat sebuah validasi terlebih dahulu untuk memastikan data yang akan disimpan
sudah sesuai dengan yang diharapkan.

$validator = Validator::make($request->all(), [
'image' => 'required|image|mimes:jpeg,jpg,png|max:2000',
]);

Dari kode validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :

KEY VALIDASI KETERANGAN

image required field wajib diisi.

image field harus memiliki extensi gambar.

extensi gambar yang boleh diupload adalah .jpeg, .jpg


mimes:png,jpg,jpeg
dan .png.

gambar yang boleh diupload maksimal memiliki ukuran


max:2000
2000 Kb / 2 Mb.

Jika data yang di insert tidak sesuai dengan validasi di atas, maka akan mengembalikan
sebuah response dengan format JSON yang berisi informasi error validasi, berikut ini kode
yang digunakan.

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

Ketika data yang di masukkan sudah sesuai, maka pertama yang akan dijalankan adalah
proses upload gambar ke dalam server, kurang lebih seperti berikut ini :

213
//upload image
$image = $request->file('image');
$image->storeAs('public/sliders', $image->hashName());

Di atas, pertama kita membuat variable dengan nama $image yang isinya akan mengambil
sebuah request dengan jenis file dan bernama image, setelah itu akan dipindahkan ke
dalam folder /storage/app/public/sliders menggunakan method storeAs dan nama
dari file tersebut akan di ubah menjadi random menggunakan method hashName().

Setelah gambar berhasil di upload di dalam server, langkah selanjutnya adalah melakukan
insert data ke dalam database, kurang lebih seperti berikut ini :

//create slider
$slider = Slider::create([
'image'=> $image->hashName(),
'link' => $request->link,
]);

Dari kode proses insert data di atas, kuranng lebih penjelasannya seperti berikut ini :

ATTRIBUTE VALUE KETERANGAN

akan mengambil nama dari gambar yang


image $image->hashName()
diupload

akan mengambil data dari request yang bernama


link $request->link
link

Untuk memastikan proses insert dan upload berjalan dengan benar atau berhasil, kita akan
membuat sebuah kondisi untuk mengecek nilai dari variable $slider.

Jika variable $slider bernilai true, yang artinya data berhasil disimpan, maka akan
melakukan return ke dalam SliderResource dengan status true.

if($slider) {
//return success with Api Resource
return new SliderResource(true, 'Data Slider Berhasil Disimpan!',
$slider);
}

214
Tapi, jika variable tersebut bernilai false, yang artinya data gagal disimpan, maka akan
melakukan return ke dalam SliderResource dengan status false.

//return failed with Api Resource


return new SliderResource(false, 'Data Slider Gagal Disimpan!', null);

function destroy

Method ini akan digunakan untuk menghapus data dari database dan gambar yang terkait
akan dihapus juga dari server. Di dalam method ini pertama kita akan melakukan hapus
gambar terlebih dahulu dari server.

//remove image
Storage::disk('local')->delete('public/sliders/'.basename($slider->image
));

Kode di atas, digunakan untuk menghapus sebuah gambar dari slider yang terkait di dalam
folder storage/app/public/sliders.

Setelah proses hapus gambar berhasil, selanjutnya kita melakukan proses hapus data slider
dari database menggunakan kode seperti berikut ini :

$slider->delete()

Dan jika proses hapus data berhasil, maka kita akan melakukan return ke dalam
SliderResource dengan memberikan status true.

//return success with Api Resource


return new SliderResource(true, 'Data Slider Berhasil Dihapus!', null);

Dan jika proses hapus data gagal dilakukan, maka akan melakukan return ke dalam
SliderResource dengan status false.

215
//return failed with Api Resource
return new SliderResource(false, 'Data Slider Gagal Dihapus!', null);

Langkah 3 - Menambahkan Route Slider


Setelah berhasil membuat controller dan menambahkan method di dalamnya, maka
sekarang kita lanjutkan untuk menambahkan route agar controller slider dapat digunakan.

Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route
/customers.

//sliders resource
Route::apiResource('/sliders',
App\Http\Controllers\Api\Admin\SliderController::class, ['except' =>
['create', 'show', 'edit', 'update'], 'as' => 'admin']);

Untuk memastikan apakah route yang sudah kita tambahkan berhasil, kita bisa melakukan
verifikasi dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

216
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 4 - Uji Coba Rest API Slider


Sekarang kita lanjutkan untuk melakukan uji coba dari setiap method yang sudah kita buat
di atas. Disini kita akan melakukan uji coba di 3 method yang ada di dalam controller
SliderController.

Uji Coba Method Index

Pertama kita akan melakukan proses uji coba untuk method index, silahkan buka aplikasi
Postman kemudian masukkan URL berikut ini http://localhost:8000/api/admin/sliders dan
untuk method-nya silahkan pilih GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data sliders.

217
Uji Coba Store

Silahkan unduh file-file assets gambar slider untuk bahan latihan.


DOWNLOAD :
https://drive.google.com/file/d/1KwWc-UC5Q-_U5NJcvWqGdcSBzsKB2j8W/view?usp=sharing

Sekarang kita lanjutkan untuk melakukan proses uji coba insert data beserta upload
gambar. Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/sliders dan untuk method-nya silahkan pilih POST.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Setelah itu silahkan klik tab Body dan pilih form-data kemudian masukkan key dan value
berikut ini :

KEY TYPE VALUE

image file Pilih gambar dari komputer.

218
KEY TYPE VALUE

link text Isi dengan URL bebas (tidak wajib diisi).

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data slider yang baru saja di insert.

Uji Coba Destroy

Sekarang kita lanjuitkan untuk proses uji coba hapus data dari database dan sekalian
menghapus file gambar dari server. Silahkan buka aplikasi Postman dan masukkan URL
berikut ini http://localhost:8000/api/admin/sliders/1 dan untuk method-nya silahkan pilih
DELETE.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data slider yang berhasil dihapus.

219
220
Membuat Restful API CRUD Users

Pada tahap kali ini kita akan belajar bagaimana cara membuat Rest API untuk data users.
Users disini merupakan admin atau pengguna yang dapat mengelola konten website, seperti
menambah category, product, dan lain-lain.

Langkah 1 - Membuat Resource User


Pertama tentu saja kita akan membuat sebuah Resource terlebih dahulu, yang mana akan
difungsikan untuk melakukan transformasi dari Model menjadi sebuah response dengan
format JSON. Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:resource UserResource

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan
nama UserResource.php yang berada di dalam folder app/Http/Resources. Silahkan
buka file tersebut dan ubah semua kode-nya menjadi seperti berikut ini :

221
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource


{
//public properti
public $status;
public $message;
/**
* __construct
*
* @param mixed $status
* @param mixed $message
* @param mixed $resource
* @return void
*/
public function __construct($status, $message, $resource)
{
parent::__construct($resource);
$this->status = $status;
$this->message = $message;
}

/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}

Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.

222
public $status;
public $message;

Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :

$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.

Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :

return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];

Langkah 2 - Membuat Controller User


Kita lanjutkan membuat sebuah controller baru yang nantinya akan kita gunakan untuk
melakukan proses CRUD data user. Silahkan jalankan perintah berikut ini di dalam
terminal/CMD :

php artisan make:controller Api\\Admin\\UserController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama UserController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah semua kode-
nya menjadi seperti berikut ini :

<?php

223
namespace App\Http\Controllers\Api\Admin;

use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;
use Illuminate\Support\Facades\Validator;

class UserController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//get users
$users = User::when(request()->q, function($users) {
$users = $users->where('name', 'like', '%'. request()->q .
'%');
})->latest()->paginate(5);
//return with Api Resource
return new UserResource(true, 'List Data Users', $users);
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|unique:users',
'password' => 'required|confirmed'
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

//create user
$user = User::create([

224
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password)
]);

if($user) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil
Disimpan!', $user);
}

//return failed with Api Resource


return new UserResource(false, 'Data User Gagal Disimpan!',
null);
}

/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$user = User::whereId($id)->first();
if($user) {
//return success with Api Resource
return new UserResource(true, 'Detail Data User!', $user);
}

//return failed with Api Resource


return new UserResource(false, 'Detail Data User Tidak
DItemukan!', null);
}

/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, User $user)
{
$validator = Validator::make($request->all(), [
'name' => 'required',

225
'email' => 'required|unique:users,email,'.$user->id,
'password' => 'confirmed'
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

if($request->password == "") {

//update user without password


$user->update([
'name' => $request->name,
'email' => $request->email,
]);
}

//update user with new password


$user->update([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password)
]);

if($user) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil
Diupdate!', $user);
}

//return failed with Api Resource


return new UserResource(false, 'Data User Gagal Diupdate!',
null);
}

/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(User $user)
{
if($user->delete()) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil Dihapus!',

226
null);
}

//return failed with Api Resource


return new UserResource(false, 'Data User Gagal Dihapus!',
null);
}
}

Dari perubahan file controller di atas, pertama kita melakukan import untuk Model User.

use App\Models\User;

kemudian kita juga import Http Request dari Laravel, dimana akan digunakan untuk
menerima sebuah input yang dikirim melalui cookie, form, URL dan lain-lain.

use Illuminate\Http\Request;

Dan kita juga import UserResource yang sebelumnya sudah kita buat di atas, dan
fungsinya yang akan mentransformasi dari Model menjadi format JSON.

use App\Http\Resources\UserResource;

kita juga akan membutuhkan sebuah validasi, maka kita perlu melakukan import juga, disini
kita import Facades Validator dari Laravel.

use Illuminate\Support\Facades\Validator;

Dan di dalam class UserController kita menambahkan 5 method baru, yaitu :

1. function index
2. function store
3. function show
4. function update
5. function destroy

227
function index

Method ini akan digunakan untuk menampilkan list data user dari database. Didalam
method ini kita juga membuat sebuah kondisi untuk proses pencarian data.

//get users
$users = User::when(request()->q, function($users) {
$users = $users->where('name', 'like', '%'. request()->q . '%');
})->latest()->paginate(5);

Di atas, kita membuat sebuah kondisi pencarian menggunakan method when, jadi ketika
ada sebuah request yang bernama q, maka kita akan melakukan pencarian ke dalam table
users berdasarkan attribute name yang sesuai dengan isi dari request q tersebut.

Setelah itu, kita urutkan data yang ditampilkan berdasarkan data yang paling terbaru,
menggunakan method latest() dan kita batasi perhalaman untuk menampilkan datanya
menggunakan method paginate().

Setelah itu, agar variable $users bisa diubah ke format JSON, maka kita perlu
menggunakan UserResource, kurang lebih seperti berikut ini :

//return with Api Resource


return new UserResource(true, 'List Data Users', $users);

function store

Method ini akan digunakan untuk menangani proses insert data ke dalam database,
sebelum data yang dikirim disimpan ke dalam database, maka akan kita validasi terlebih
dahulu, tujuannya agar data yang di insert bisa sesuai dengan yang diharapkan.

$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|unique:users',
'password' => 'required|confirmed'
]);

Dari kode validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :

228
KEY VALIDASI KETERANGAN

name required field wajib diisi

email required field wajib diisi

unique:users data tidak boleh ada yang sama di dalam table users

password required field wajib diisi

confirmed field wajib sama dengan isi password

Jika data yang di kirim belum sesuai dengan ketentuan validasi di atas, maka akan
mengembalikan sebuah response JSON yang berisi informasi validasi, kurang lebih seperti
berikut ini :

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

Akan tetapi, jika data yang akan disimpan sudah sesuai dengan yang diharapkan, maka
akan melakukan insert ke dalam database menggunakan Eloquent, kurang lebih seperti
berikut ini :

//create user
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password)
]);

Dari kode proses insert di atas, kurang lebih penjelasan detailnya seperti berikut ini :

ATTRIBUTE VALUE KETERANGAN

akan mengambil data dari request yang


name $request->name
bernama name

akan mengambil data dari request yang


email $request->email
bernama email

229
ATTRIBUTE VALUE KETERANGAN

akan mengambil data dari request yang


bernama password, kemudian akan di
password bcrypt($request->password) encrypt menggunakan helper bcrypt,
agar password yang dihasilkan menjadi
random.

Untuk memastikan proses insert berhasil atau tidak, maka kita akan membuat sebuah
kondisi untuk memeriksa hasil dari variable $user, jika bernilai true maka proses insert
berhasil dilakukan, tapi jika hasilnya false maka proses insert gagal dilakukan.

Jika veriable $user bernilai true, maka kita akan mengembalikan ke dalam
UserResource dengan memberikan status true, kurang lebih seperti ini :

if($user) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil Disimpan!', $user);
}

Tapi, jika variable tersebut bernilai false, maka akan mengembalikan ke dalam
UserResource dengan status false.

//return failed with Api Resource


return new UserResource(false, 'Data User Gagal Disimpan!', null);

function show

Method ini akan digunakan untuk menampilkan detail data user berdasarkan ID yang di
dapatkan melalui paremeter URL.

$user = User::whereId($id)->first();

Di atas kita melakukan get data user menggunakan Model User dimana data tersebut di
cari berdasarkan parameter $id. Jika data user tersebut di dapatkan, maka kita akan
melakukan return menggunakan UserResource dengan status true.

230
if($user) {
//return success with Api Resource
return new UserResource(true, 'Detail Data User!', $user);
}

Tapi, jika data user tidak ditemulan, maka akan mengembalikan ke dalam UserResource
dengan status false.

//return failed with Api Resource


return new UserResource(false, 'Detail Data User Tidak DItemukan!',
null);

function update

Method ini akan kita gunakan untuk melakukan proses update data ke dalam database.
Sama seperti method store, disini kita juga menggunakan sebuah validasi untuk
memastikan data yang akan diupdate itu sudah benar-benar sesuai dengan yang di
harapkan.

$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|unique:users,email,'.$user->id,
'password' => 'confirmed'
]);

Dari deklrasi validasi di atas, kurang lebih seperti berikut ini :

KEY VALIDASI KETERANGAN

name required field wajib diisi

email required field wajib diisi

field bersifat unik dan tidak boleh ada yang


sama di dalam table users. Karena
bersifat unik, maka untuk proses update
unique:users,email,'.$user->id
kita tambahkan email,'.$user->id,
yang artinya kusus ID user ini akan
dikecualikan.

231
KEY VALIDASI KETERANGAN

password confirmed field wajib sama dengan isi password

Jika data yang akan diupdate belum sesuai dengan validasi di atas, maka akan melakuakn
return ke dalam JSON dengan informasi error validasi.

if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

Setelah itu kita membuat sebuah kondisi lagi untuk memeriksa apakah request dengan
nama password memiliki value atau tidak. Jika IYA, maka kita akan melakukan update data
user dengan password baru, tapi jika TIDAK, maka kita akan update data user tanpa
melakukan update untuk attribute password-nya.

if($request->password == "") {

//update user without password


$user->update([
'name' => $request->name,
'email' => $request->email,
]);
}

Kode di atas digunakan untuk melakukan update data user tanpa harus mengupdate
attribute password, karena nilai dari request yang bernama password memiliki value
null atau kosong.

Tapi jika request dengan nama password tersebut memiliki value, maka artinya kita akan
melakukan update data user dengan password baru.

//update user with new password


$user->update([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password)
]);

232
Untuk memastikan proses update berhasil, kita akan membuat kondisi untuk variable
$user, jika bernilai true, maka akan kita return ke dalam UserResource dengan status
true.

if($user) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil Diupdate!', $user);
}

Tapi, jika variable tersebut bernilai false, maka akan kita return ke dalam UserResource
dengan status false, kurang lebih seperti berikut ini :

//return failed with Api Resource


return new UserResource(false, 'Data User Gagal Diupdate!', null);

function destroy

Method ini kita gunakan untuk proses hapus data user dari database, disini kita akan
menghapus data user tersebut berdasarkan ID yang di dapat melalui parameter URL.

Jika proses delete data berhasil maka kita akan memanggil UserResource dengan status
true, kurang lebih seperti berikut ini :

if($user->delete()) {
//return success with Api Resource
return new UserResource(true, 'Data User Berhasil Dihapus!', null);
}

Tapi, jika proses delete data gagal, maka akan melakukan return ke UserResource dengan
status false, kurang lebih seperti berikut ini :

//return failed with Api Resource


return new UserResource(false, 'Data User Gagal Dihapus!', null);

233
Langkah 3 - Menambahkan Route User
Setelah berhasil membuat controller dan menambahkan method di dalamnya, maka
sekarang kita lanjutkan untuk menambahkan route agar controller user dapat digunakan.

Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
admin dan group middleware auth:api_admin, lebih tepatnya di bawah route /sliders.

//users resource
Route::apiResource('/users',
App\Http\Controllers\Api\Admin\UserController::class, ['except' =>
['create', 'edit'], 'as' => 'admin']);

Untuk memastikan apakah route yang sudah kita tambahkan di atas berhasil, maka kita bisa
menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

234
php artisan route:cache

Langkah 4 - Uji Coba Rest API User


Sekarang kita lanjutkan untuk melakukan uji coba dari setiap method yang sudah kita buat
di atas. Disini kita akan melakukan uji coba di 5 method yang ada di dalam controller
UserController.

Uji Coba Method Index

Pertama kita akan melakukan proses uji coba untuk method index, silahkan buka aplikasi
Postman kemudian masukkan URL berikut ini http://localhost:8000/api/admin/users dan
untuk method-nya silahkan pilih GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data users.

235
Uji Coba Method Store

Sekarang kita lanjutkan untuk melakukan proses uji coba insert data ke dalam database.
Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/users dan untuk method-nya silahkan pilih POST.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Setelah itu silahkan klik tab Body dan pilih form-data kemudian masukkan key dan value
berikut ini :

KEY TYPE VALUE

name text Silahkan isi dengan nama User yang diinginan.

email text Silahkan isi dengan email User yang diinginan.

password text Silahkan isi dengan password User yang diinginan.

password_confirmation text Silahkan isi sama seperti field password.

236
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data user yang baru saja di insert.

Uji Coba Method Show

Setelah berhasil melakukan proses insert data user ke dalam database, sekarang kita
lanjutkan untuk menampilkaan detail dari user tersebut. Silahkan buka aplikasi Postman
dan masukkan URL berikut ini http://localhost:8000/api/admin/users/2 dan untuk method-
nya silahkan pilih GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi detail data user.

237
Uji Coba Method Update

Sekarang kita lanjutkan untuk uji coba proses update data user ke dalam database. Silahkan
buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/admin/users/2 dan untuk method-nya silahkan pilih PUT.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Setelah itu klik tab Body dan pilih form-data kemudian masukkan key dan value seperti
berikut ini :

KEY TYPE VALUE

name text Silahkan isi dengan nama User yang diinginan.

email text Silahkan diisi dengan email User yang diinginkan.

Silahkan diisi dengan password User yang


password text
diinginkan. (ini tidak wajib diisi)

password_confirmation text Silahkan diisi jika field password di isi.

238
KEY TYPE VALUE

_method text PATCH

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi tentang data user yang diupdate.

Uji Coba Method Destroy

Sekarang kita lanjutkan untuk melakukan proses hapus data, silahkan buka aplikasi
Postman kemudian masukkan URL berikut ini http://localhost:8000/api/admin/users/2 dan
untuk method-nya silahkan pilih DELETE.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data user telah berhasil dihapus.

239
240
Membuat Restful API Register Customer

Pada materi kali ini, kita semua akan belajar bagaimana cara membuat Rest API untuk
proses register data customer, disini kita tidak akan membuat sebuah Resource lagi, karena
kita bisa menggunakan Resource yang sudah kita buat sebelumnya.

Langkah 1 - Membuat Controller Register Customer


Pertama, kita akan membuat controller terlebih dahulu, silahkan jalankan perintah berikut
ini di dalam terminal/CMD dan pastikan sudah berada di dalam project Laravel.

php artisan make:controller Api\\Customer\\RegisterController

Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file controller baru dengan
nama RegisterController.php yang berada di dalam folder
app/Http/Controllers/Api/Customer. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Customer;

use App\Models\Customer;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use App\Http\Resources\CustomerResource;
use Illuminate\Support\Facades\Validator;

class RegisterController extends Controller


{
/**
* store
*
* @param mixed $request
* @return void
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [

241
'name' => 'required|string|max:255',
'email' => 'required|email|unique:customers',
'password' => 'required|confirmed',
]);

if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

//create customer
$customer = Customer::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);

if($customer) {
//return with Api Resource
return new CustomerResource(true, 'Register Customer
Berhasil', $customer);
}

//return failed with Api Resource


return new CustomerResource(false, 'Register Customer Gagal!',
null);

}
}

Dari penambahan kode di atas, pertama kita melakukan import Model Customer, karena
kita akan gunakan untuk proses insert data nanti.

use App\Models\Customer;

Setelah itu, kita juga import Facades Hash, ini akan digunakan untuk melakukan hashing
atau random password saat proses register.

use Illuminate\Support\Facades\Hash;

Dan karena akan mengembalikan response JSON, maka kita juga akan import

242
CustomerResource.

use App\Http\Resources\CustomerResource

Karena kita akan membutuhkan sebuah validasi, maka kita perlu melakukan import juga,
disini kita import Facades Validator dari Laravel.

use Illuminate\Support\Facades\Validator;

Dan di dalam class RegisterController kita menambahkan method baru dengan nama
store, method tersebut yang akan digunakan untuk memproses register customer.

Pertama-tama, kita akan membuat sebuah validasi terlebih dahulu, dimana validasi ini
bertujuan untuk memverifikasi data yang dimasukkan apakah sudah sesuai dengan yang
diharapkan.

$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:customers',
'password' => 'required|confirmed',
]);

Dari kode validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :

KEY VALIDASI KETERANGAN

name required field wajib diisi

email required field wajib diisi

data tidak boleh ada yang sama di dalam table


unique:customers
customers

password required field wajib diisi

confirmed field wajib sama dengan isi password

Jika data yang di masukkan belum sesuai dengan validasi di atas, maka akan
mengembalikan sebuah response dengan format JSON dengan informasi error validasi.

243
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

Tapi jika data yang di masukkan sudah sesuai, maka akan melakukan proses insert
menggunakan Model dan eloquent, kurang lebih seperti berikut ini :

//create customer
$customer = Customer::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);

Untuk memastikan apakah proses register di atas berhasil, maka kita akan membuat kondisi
untuk mengecek hasil dari variable $customer.

Jika variable $customer bernilai true, maka akan melakukan return ke dalam
CustomerResource dengan status true.

if($customer) {
//return with Api Resource
return new CustomerResource(true, 'Register Customer Berhasil',
$customer);
}

Tapi jika variable tersebut bernilai false, maka akan melakukan return dengan
CustomerResource dengan status false.

//return failed with Api Resource


return new CustomerResource(false, 'Register Customer Gagal!', null);

Langkah 2 - Menambahkan Route Register Customer


Sekarang kita akan lanjutkan untuk menambahkan route agar proses register dapat
digunakan. Silahkan tambahkan route berikut ini di dalam file routes/api.php dan

244
pastikan berada di luar prefix admin, karena kita akan membuat sebuah prefix baru untuk
customer.

//group route with prefix "customer"


Route::prefix('customer')->group(function () {

//route register
Route::post('/register',
[App\Http\Controllers\Api\Customer\RegisterController::class, 'store'],
['as' => 'customer']);

});

Untuk memastikan route di atas berhasil ditambahkan, kita bisa menjalankan perintah
berikut ini di dalam terminal/CMD :

php artisan route:list

Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

245
php artisan route:cache

Langkah 3 - Uji Coba Rest API Register Customer


Sekarang kita akan lakukan uji coba untuk proses register, silahkan buka aplikasi Postman
dan masukkan URL berikut ini http://localhost:8000/api/customer/register dan untuk
method-nya silahkan gunakan POST.

Jika sudah, silahkan klik Send, maka kita akan mendapatkan error validasi, karena kita
belum memasukkan data apapun di dalam Postman.

Sekarang silahkan klik tab Body dan pilih form-data, kemudian masukkan key dan value
berikut ini :

KEY VALUE

name Silahkan diisi sesuai keinginan.

email Silahkan diisi sesuai keinginan (format email).

password Silahkan diisi sesuai keinginan

password_confirmation Silahkan diisi sesuai dengan field password.

Jika sudah, silahkan klik Send lagi dan jika berhasil maka kita akan mendapatkan sebuah

246
response JSON dengan informasi proses registrasi berhasil dilakukan.

Di materi selanjutnya kita akan mempelajari bagaimana cara melakukan konfigurasi JWT
untuk otentikasi di customer dan menggunakan data yang berhasil register di atas untuk
proses otentikasi.

247
Konfigurasi JWT untuk Customer

Sekarang kita akan lanjutkan belajar untuk konfigurasi JWT untuk customer, disini kita tidak
perlu melakukan installasi JWT lagi, karena itu sudah pernah kita lakukan sebelumnya. Jadi
disini tugas kita hanya membuat sebuah guard, provider dan menambahkan beberapa kode
di Model Customer.

Langkah 1 - Konfigurasi Guard dan Provider JWT Customer


Pertama, silahkan buka file config/auth.php kemudian cari kode berikut ini :

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

'api_admin' => [ // <-- atur menjadi "api_admin"


'driver' => 'jwt', // <-- atur menjadi "jwt"
'provider' => 'users',
'hash' => false,
],
],

Setelah itu, silahkan ubah menjadi seperti berikut ini :

248
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

'api_admin' => [ // <-- atur menjadi "api_admin"


'driver' => 'jwt', // <-- atur menjadi "jwt"
'provider' => 'users',
'hash' => false,
],
'api_customer' => [ // <-- atur menjadi "api_customer"
'driver' => 'jwt', // <-- atur menjadi "jwt"
'provider' => 'customers',
'hash' => false,
],
],

Bisa kita perhatikan, di atas kita menambahkan 1 konfigurasi lagi untuk api_customer dan
untuk providernya kita arahkan ke dalam customers.

Sekarang, kita akan lanjutkan untuk membuat provider customers tersebut, masih di
dalam file yang sama yaitu config/auth.php dan kemudian silahkan cari kode berikut ini
:

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],

// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],

Dan ubahlah menjadi seperti berikut ini :

249
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],

'customers' => [
'driver' => 'eloquent',
'model' => App\Models\Customer::class,
],
],

Di atas, kita menambahkan 1 provider baru dengan nama customers dan untuk driver-nya
kita atur menggunakan Eloquent dan Model-nya kita arahkan ke dalam Customer.

Langkah 2 - Konfigurasi Model Customer


Setelah berhasil membuat guard dan provider untuk customer, sekarang kita lanjutkan
untuk melakukan konfigurasi di dalam Model Customer.

Silahkan buka file app/Models/Customer.php kemudian ubah semua kode-nya menjadi


seperti berikut ini :

<?php

namespace App\Models;

use Illuminate\Support\Carbon;
use Tymon\JWTAuth\Contracts\JWTSubject; // <-- import
JWTSubject
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; // <-- import
Auth Laravel

class Customer extends Authenticatable implements JWTSubject // <--


tambahkan "Authenticatable" dan "JWTSubject

{
use HasFactory;

/**
* fillable

250
*
* @var array
*/
protected $fillable = [
'name', 'email', 'email', 'email_verified_at', 'password',
'remember_token'
];

/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];

/**
* invoice
*
* @return void
*/
public function invoices()
{
return $this->hasMany(Invoice::class);
}

/**
* reviews
*
* @return void
*/
public function reviews()
{
return $this->hasMany(Review::class);
}

/**
* getCreatedAtAttribute
*
* @param mixed $date
* @return void
*/
public function getCreatedAtAttribute($date)
{

251
$value = Carbon::parse($date);
$parse = $value->locale('id');
return $parse->translatedFormat('l, d F Y');
}

/**
* Get the identifier that will be stored in the subject claim of
the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}

/**
* Return a key value array, containing any custom claims to be
added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}

Di atas, pertama kita melakukan import JWTSubject.

use Tymon\JWTAuth\Contracts\JWTSubject; // <-- import


JWTSubject

Setelah itu, kita juga import Auth dari Laravel, karena Model Customer nanti akan
digunakan untuk proses otentikasi.

use Illuminate\Foundation\Auth\User as Authenticatable; // <-- import


Auth Laravel

Dan pada class Customer untuk extends kita atur menjadi Authenticatable dan

252
implements ke JWTSubject.

class Customer extends Authenticatable implements JWTSubject


{
//...

Dan terakhir, kita menambahkan 2 method, yaitu :

1. function getJWTIdentifier.
2. function getJWTCustomClaims.

Sekarang, kita sudah bisa menggunakan customer untuk melakukan proses otentikasi dan
generate token JWT.

253
Membuat Restful API Login Customer

Setelah berhasil melakukan konfigurasi JWT untuk customer, maka sekarang kita akan
lanjutkan untuk membuat proses login atau otentikasi untuk customer. Dan disini kita tidak
hanya membuat proses login, tapi ada beberapa fitur lainnya, seperti :

Login - proses login / otentikasi


Get User - mendapatkan data customer yang sedang login
Refresh Token - melakukan refresh token JWT
Logout - membuat proses logout

Langkah 1 - Membuat Controller Login Customer


Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Larvel-nya.

php artisan make:controller Api\\Customer\\LoginController

Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file controller baru dengan
nama LoginController.php yang berada di dalam folder
app/Http/Controllers/Api/Customer. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Customer;

use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;

class LoginController extends Controller


{
/**
* index
*
* @param mixed $request
* @return void
*/

254
public function index(Request $request)
{
//set validasi
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);
//response error validasi
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

//get "email" dan "password" dari input


$credentials = $request->only('email', 'password');

//check jika "email" dan "password" tidak sesuai


if(!$token =
auth()->guard('api_customer')->attempt($credentials)) {

//response login "failed"


return response()->json([
'success' => false,
'message' => 'Email or Password is incorrect'
], 401);

}
//response login "success" dengan generate "Token"
return response()->json([
'success' => true,
'user' => auth()->guard('api_customer')->user(),
'token' => $token
], 200);
}

/**
* getUser
*
* @return void
*/
public function getUser()
{
//response data "user" yang sedang login
return response()->json([
'success' => true,
'user' => auth()->guard('api_customer')->user()
], 200);

255
}
/**
* refreshToken
*
* @param mixed $request
* @return void
*/
public function refreshToken(Request $request)
{
//refresh "token"
$refreshToken = JWTAuth::refresh(JWTAuth::getToken());

//set user dengan "token" baru


$user = JWTAuth::setToken($refreshToken)->toUser();

//set header "Authorization" dengan type Bearer + "token" baru


$request->headers->set('Authorization','Bearer '.$refreshToken);

//response data "user" dengan "token" baru


return response()->json([
'success' => true,
'user' => $user,
'token' => $refreshToken,
], 200);
}
/**
* logout
*
* @return void
*/
public function logout()
{
//remove "token" JWT
$removeToken = JWTAuth::invalidate(JWTAuth::getToken());

//response "success" logout


return response()->json([
'success' => true,
], 200);

}
}

Dari perubahan kode di atas, pertama kita import provider Http Request dari Laravel, ini
digunakan untuk menerimaa request yang dikirim melalui cookie, input form dan lain-lain.

256
use Illuminate\Http\Request;

Setelah itu, kita juga import Facades dari JWT, ini akan kita gunakan untuk proses yang
berhubungan dengan Token JWT nantinya.

use Tymon\JWTAuth\Facades\JWTAuth;

Dan kita juga import Facades Validator, yang mana akan kita gunakan untuk proses
validasi input yang akan kita buat di Rest API nanti.

use Illuminate\Support\Facades\Validator;

Kemudian di dalam class LoginController kita membuat 4 method, yaitu :

1. index
2. getUser
3. refreshToken
4. logout

function index

Method inilah yang akan digunakan untuk proses otentikasi untuk customer, pertama-tama
kita akan membuat sebuah validasi terlebih dahulu, fungsinya agar memastikan data yang
dikirim sudah sesuai dengan yang diharapkan.

//set validasi
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);

Dari pendefinisian validasi di atas, kurang lebih penjelasan detailnya seperti berikut ini :

KEY VALIDASI KETERANGAN

email required field wajib diisi

257
KEY VALIDASI KETERANGAN

email field harus menggunakan format email

password required field wajib diisi

Jika data yang dikirim tidak sesuai dengan validasi di atas, maka kita akan mengembalikan
sebuah response dengan format JSON yang berisi informasi error tentang validasi.

//response error validasi


if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}

Setelah itu kita buat sebuah variable yang bernama $credentials yang berisi sebuah
data email dan password yang dikirim melalui Http request. Kemudian kita lakukan proses
pengecekan jika email dan password tersebut tidak terdaftar di dalam database.

//check jika "email" dan "password" tidak sesuai


if(!$token = auth()->guard('api_customer')->attempt($credentials)) {

//...

Maka akan mengembalikan sebuah response dengan format JSON yang berisi informasi
tentang proses login gagal atau failed.

//response login "failed"


return response()->json([
'success' => false,
'message' => 'Email or Password is incorrect'
], 401);

Tapi jika email dan password ada di dalam database, maka kita akan mengembalikan
sebuah response dalam format JSON yang berisi informasi data customer dan informasi
token JWT.

258
//response login "success" dengan generate "Token"
return response()->json([
'success' => true,
'user' => auth()->guard('api_customer')->user(),
'token' => $token
], 200);

function getUser

Method ini digunakan untuk menampilkan informasi data customer yang sedang login,
kurang lebih seperti berikut ini :

//response data "user" yang sedang login


return response()->json([
'success' => true,
'user' => auth()->guard('api_customer')->user()
], 200);

Di atas kita mendapatkan data customer menggunakan helper auth dari Laravel, dimana
helper auth tersebut kita arahkan ke dalam object guard api_customer, yang memang
proses otentikasinya menggunakan guard API tersebut.

function refreshToken

Method ini kita gunakan untuk proses memperbarui token JWT, alurnya kita generate
sebuah token baru setelah itu token tersebut kita set ke data customer yang sedang login.

//refresh "token"
$refreshToken = JWTAuth::refresh(JWTAuth::getToken());

Kode di atas digunakan untuk melakukan proses generate token baru. Setelah itu hasil dari
token baru tersebut kita set ke dalam data customer yang sedang login.

//set user dengan "token" baru


$user = JWTAuth::setToken($refreshToken)->toUser();

Kemudian kita set juga untuk header Authorization dengan value Bearer + token baru.

259
//set header "Authorization" dengan type Bearer + "token" baru
$request->headers->set('Authorization','Bearer '.$refreshToken);

Setelah itu kita mengembalikan sebuah response dalam format JSON yang berisi informasi
data customer beserta token baru.

//response data "user" dengan "token" baru


return response()->json([
'success' => true,
'user' => $user,
'token' => $refreshToken,
], 200);

function logout

Method ini digunakan untuk proses menghapus token yang terdaftar di server backend,
untuk mengahapus token kita bisa menggunakan kode seperti berikut ini :

//remove "token" JWT


$removeToken = JWTAuth::invalidate(JWTAuth::getToken());

Setelah itu kita mengembalikan sebuah response dalam format JSON dengan status success
true.

//response "success" logout


return response()->json([
'success' => true,
], 200);

Langkah 2 - Menambahkan Route


Setelah berhasil membuat controller dan menambahkan method-method di dalamnya, maka
sekarang kita lanjutkan untuk membuat route agar method-method tersebut dapat
digunakan.

Silahkan buka file routes/api.php kemudian masukkan route berikut ini di dalam prefix

260
customer dan tepatnya di bawah route /register.

//route login
Route::post('/login',
[App\Http\Controllers\Api\Customer\LoginController::class, 'index'],
['as' => 'customer']);

//group route with middleware "auth:api_customer"


Route::group(['middleware' => 'auth:api_customer'], function() {

//data user
Route::get('/user',
[App\Http\Controllers\Api\Customer\LoginController::class, 'getUser'],
['as' => 'customer']);

//refresh token JWT


Route::get('/refresh',
[App\Http\Controllers\Api\Customer\LoginController::class,
'refreshToken'], ['as' => 'customer']);

//logout
Route::post('/logout',
[App\Http\Controllers\Api\Customer\LoginController::class, 'logout'],
['as' => 'customer']);
});

Di atas kita menambahkan beberapa route. Dan untuk route /user, /refresh, /logout
kita masukkan di dalam middleware auth:api_customer, karena ketiga route tersebut
hanya bisa diakses jika customer sudah melakukan proses otentikasi terlebih dahulu.

Jika route dengan prefix customer ditulis secara lengkap, kurang lebih seperti berikut ini :

261
//group route with prefix "customer"
Route::prefix('customer')->group(function () {

//route register
Route::post('/register',
[App\Http\Controllers\Api\Customer\RegisterController::class, 'store'],
['as' => 'customer']);

//route login
Route::post('/login',
[App\Http\Controllers\Api\Customer\LoginController::class, 'index'],
['as' => 'customer']);

//group route with middleware "auth:api_customer"


Route::group(['middleware' => 'auth:api_customer'], function() {

//data user
Route::get('/user',
[App\Http\Controllers\Api\Customer\LoginController::class, 'getUser'],
['as' => 'customer']);

//refresh token JWT


Route::get('/refresh',
[App\Http\Controllers\Api\Customer\LoginController::class,
'refreshToken'], ['as' => 'customer']);

//logout
Route::post('/logout',
[App\Http\Controllers\Api\Customer\LoginController::class, 'logout'],
['as' => 'customer']);
});

});

Untuk memastikan penambahan route di atas berhasil, kita bisa memverifikasinya dengan
menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

262
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 3 - Uji Coba Rest API


Setelah berhasil membuat controller dan menambahkan beberapa method di dalamnya,
sekarang kita akan lanjutkan untuk proses uji coba atau tetsing dari Rest API yang sudah
kita buat.

Uji Coba Proses Login

Silahkan teman-teman buka aplikasi Postman terlebih dahulu, setelah aplikasi berhasil
terbuka, silahkan masukkan URL berikut ini http://localhost:8000/api/customer/login dan
untuk method-nya silahkan gunakan POST.

Kemudian memasukkan email dan password yang pernah kita masukkan saat register.
Silahkan kembali lagi ke aplikasi Postman dan klik tab Body kemudian pilih form-data
dan masukkan key dan value berikut ini :

KEY VALUE

email kurnia@gmail.com

password password

263
CATATAN ! : data di atas merupakan data customer yang ada di dalam database dan data
tersebut dibuat saat kita melakukan proses register.

Sekarang, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response yang berisi informasi data customer dan token JWT. Kurang lebih seperti berikut
ini :

INFORMASI : selanjutnya untuk mengakses route yang membutuhkan otentikasi, maka kita
harus menyertakan token yang di dapatkan dari proses login seperti di atas.

Uji Coba Get Data Customer

Setelah berhasil mendapatkan token dari hasil proses otentikasi, sekarang kita lanjutkan
untuk proses uji coba get data customer, dimana untuk mendapatkan data customer yang
sedang login kita membutuhkan token dari proses otentikasi sebelumnya.

Silahkan buka tab baru di aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/customer/user, dan untuk method-nya silahkan pilih GET. Setelah
itu, silahkan klik tab Headers kemudian masukkan key dan value berikut ini di dalamnya.

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

264
CATATAN ! : untuk value dari Authorization adalah Bearer kemudian spasi Token,
token tersebut silahkan diambil dari hasil response token di proses login.

Jika sudah, silahakn klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response yang berisi informasi tentang data customer yang sedang login.

Uji Coba Refresh Token

Pada tahap kali ini kita semua akan belajar bagaimana cara melakuakn refresh token di
dalam JWT (Json Web Token), yaitu konsepnya melakukan regenerate atau memperbarui
token yang lama menjadi baru dan di set ke customer yang sedang login.

Silahkan buka tab baru di dalam aplikasi Postman, kemudian masukkan URL berikut ini
http://localhost:8000/api/customer/refresh dan untuk method-nya silahkan pilih GET.
Selanjutnya klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dengan berisi informasi data customer beserta token baru. Jika kita perhatikan
token dari hasil login akan di regenerate dengan token baru, kita bisa bandingkan 2 token
tersebut, maka nilainya akan berbeda.

265
CATATAN ! : selanjutnya untuk token yang dipakai adalah token dari hasil Refresh Token.
Tapi jika kita melakukan proses login ulang, maka token yang di pakai adalah token hasil
login tersebut.

Uji Coba Proses Logout

Terakhir kita akan lakukan uji coba untuk proses logout, silahkan buka tab baru di Postman
daan masukkan URL berikut ini http://localhost/api/customer/logout dan untuk method-nya
adalah POST.

Selanjutnya klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response berupa informasi bahwa proses logout berhasil.

266
267
Membuat Restful API Dashboard Customer

Setelah berhasil membuat proses otentikasi untuk customer, maka sekarang kita lanjutkan
untuk membuat Rest API untuk menampilkan statistik data dari customer tersebut. Dan
disini kita akan letakkan di dalam controller dashboard.

Langkah 1 - Membuat Controller Dashboard


Pertama-tama kita akan membuat sebuah controller dulu dan nantinya akan kita tambahkan
sebuah method di dalamnya. Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\Customer\\DashboardController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
yang berada di dalam folder app/Http/Controllers/Api/Customer. Silahkan buka file
tersebut dan ubah semua kode-nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Customer;

use App\Models\Invoice;
use App\Http\Controllers\Controller;

class DashboardController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//count invoice
$pending = Invoice::where('status',
'pending')->where('customer_id',
auth()->guard('api_customer')->user()->id)->count();
$success = Invoice::where('status',
'success')->where('customer_id',
auth()->guard('api_customer')->user()->id)->count();

268
$expired = Invoice::where('status',
'expired')->where('customer_id',
auth()->guard('api_customer')->user()->id)->count();
$failed = Invoice::where('status',
'failed')->where('customer_id',
auth()->guard('api_customer')->user()->id)->count();

//response
return response()->json([
'success' => true,
'message' => 'Statistik Data',
'data' => [
'count' => [
'pending' => $pending,
'success' => $success,
'expired' => $expired,
'failed' => $failed
]
]
], 200);
}
}

Dari penambahan kode di atas, pertama kita melakukan import untuk Model Invoice,
karena kita akan menggunakan ini untuk mendapatkan data invoice berdasarkan customer
yang sedang login.

use App\Models\Invoice;

Dan di dalam class DashboardController kita menambahkan 1 method baru yang


bernama index, dimana method tersebut kita melakukan beberapa count berdasarkan
kondisi tertentu, kurang lebih seperti berikut ini :

269
//count invoice
$pending = Invoice::where('status', 'pending')->where('customer_id',
auth()->guard('api_customer')->user()->id)->count();

$success = Invoice::where('status', 'success')->where('customer_id',


auth()->guard('api_customer')->user()->id)->count();

$expired = Invoice::where('status', 'expired')->where('customer_id',


auth()->guard('api_customer')->user()->id)->count();

$failed = Invoice::where('status', 'failed')->where('customer_id',


auth()->guard('api_customer')->user()->id)->count();

Di atas, kita melakukan count ke dalam table invoices sesuai dengan customer yang
sedang login dan kita pisahkan berdasarkan kondisi dari invoice tersebut, yaitu pending,
success, expired da failed.

Setelah itu, kita akan lakukan return ke dalam format JSON, kurang lebih seperti berikut ini :

//response
return response()->json([
'success' => true,
'message' => 'Statistik Data',
'data' => [
'count' => [
'pending' => $pending,
'success' => $success,
'expired' => $expired,
'failed' => $failed
]
]
], 200);

Langkah 2 - Menambahkan Route


Setelah berhasil membuat controller daan menambahkan method di dalamnya, maka
sekarang kita akan lanjutkan untuk membuatkan sebuah route.

Silahkan buka file routes/api.php kemudian masukkan route berikut ini di dalam prefix
customer dan di dalam midlleware auth:api_customer dan tepatnya di bawah route
/logout.

270
//dashboard
Route::get('/dashboard',
[App\Http\Controllers\Api\Customer\DashboardController::class, 'index'],
['as' => 'customer']);

Untuk memastikan route yang kita buat di atas berhasil, kita bisa menjalankan perintah
berikut ini di dalam terminal/CMD :

php artisan route:list

Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 3 - Uji Coba Rest API Dashboard


Setelah berhasil membuat controller dan menambahkan method di dalamnya, sekarang kita
akan lanjutkan untuk proses uji coba atau tetsing dari Rest API yang sudah kita buat.

Silahkan teman-teman buka aplikasi Postman terlebih dahulu, setelah aplikasi berhasil
terbuka, silahkan masukkan URL berikut ini http://localhost:8000/api/customer/dashboard

271
dan untuk method-nya silahkan gunakan GET.

Setelah itu, silahkan klik tab Headers kemudian masukkan key dan value berikut ini di
dalamnya.

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token


CATATAN ! : untuk value dari Authorization adalah Bearer kemudian spasi Token,
token tersebut silahkan diambil dari hasil response token di proses login.

Jika sudah, silahkan klik Send dan jika berhasil maka kurang lebih hasilnya seperti berikut ini
:

272
Membuat Restful API Invoices Customer

Pada materi kali ini, kita semua akan belajar bagaimana cara membuat Rest API untuk
menampilkan data invoice berdasarkan customer yang sedang login. Dan tentu saja kita
tidak perlu membuat sebuah Resource lagi, kita cukup menggunakan yang sudah ada.

Langkah 1 - Membuat Controller Invoice


Sekarang kita akan membuat controller baru untuk data invoice, silahkan jalankan perintah
berikut ini di dalam terminal/CMD dan pastikan sudah berada di dalam project Laravel-nya.

php artisan make:controller Api\\Customer\\InvoiceController

Jika berhasil, maka kita akan mendapatkan 1 file controller baru dengan nama
InvoiceController.php yang berada di dalam folder
app/Http/Controllers/Api/Admin. Silahkan buka file tersebut dan ubah semua kode-
nya menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Customer;

use App\Models\Invoice;
use App\Http\Controllers\Controller;
use App\Http\Resources\InvoiceResource;

class InvoiceController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$invoices = Invoice::latest()->when(request()->q,
function($invoices) {
$invoices = $invoices->where('invoice', 'like', '%'.
request()->q . '%');
})->where('customer_id',

273
auth()->guard('api_customer')->user()->id)->paginate(5);

//return with Api Resource


return new InvoiceResource(true, 'List Data Invoices :
'.auth()->guard('api_customer')->user()->name.'', $invoices);
}

/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($snap_token)
{
$invoice = Invoice::with('orders.product', 'customer', 'city',
'province')->where('customer_id',
auth()->guard('api_customer')->user()->id)->where('snap_token',
$snap_token)->first();
if($invoice) {
//return success with Api Resource
return new InvoiceResource(true, 'Detail Data Invoice :
'.$invoice->snap_token.'', $invoice);
}

//return failed with Api Resource


return new InvoiceResource(false, 'Detail Data Invoice Tidak
DItemukan!', null);
}
}

Dari penambahan kode di atas, pertama kita lakukan import Model Invoice.

use App\Models\Invoice;

Setelah itu, kita import juga InvoiceResource karena akan digunakan untuk melakukan
konversi dari Model menjadi format JSON dengan cepat dan baik.

use App\Http\Resources\InvoiceResource;

274
Dan di dalam class InvoiceController kita menambahkan 2 method baru, yaitu :

1. function index - digunakan untuk menampilkan list data invoice berdasarkan


customer yang sedang login.
2. function show - digunakan untuk menampilkan detail data invoice dan order
berdasarkan snap_token.

function index

Method ini akan digunakan untuk menampilkan list data invoice dari dalam database, disini
kita menggunakan Eloquent untuk proses getting datanya.

$invoices = Invoice::latest()->when(request()->q, function($invoices) {


$invoices = $invoices->where('invoice', 'like', '%'. request()->q .
'%');
})->where('customer_id',
auth()->guard('api_customer')->user()->id)->paginate(5);

Kode di atas merupakan Eloquent di Laravel untuk mendapatkan list data menggunakan
Model. Setelah itu, kita membuat kondisi lagi untuk proses pencarian, disini kita
menggunakan method when yang artinya jika ada sebuah request bernama q, maka akan
melakukan proses pencarian data berdasarkan attribute invoice yang sesuai dengan isi
dari request q tersebut.

Dan kita berikan kondisi lagi menggunakan where yaitu mencari data berdasarkan
customer_id dan kita cocokan dengan ID customer yang sedang login.

where('customer_id', auth()->guard('api_customer')->user()->id)

Setelah itu, kita urutkan data yang akan ditampilkan berdasarkan yang paling terbaru, disini
kita menggunakan method latest(). Dan data yang akan ditampilkan akan dibatas
perhalaman 5 record menggunakan method paginate().

kemudian kita return varibale $invoices dengan InvoiceResource akan


mengembalikan sebuah response dengan format JSON, kurang lebih seperti berikut ini :

//return with Api Resource


return new InvoiceResource(true, 'List Data Invoices :
'.auth()->guard('api_customer')->user()->name.'', $invoices);

275
function show

Method ini akan digunakan untuk menampilkan detail data invoice berdasarkan
snap_token dan ID customer, kurang lebih sepeerti berikut ini :

$invoice = Invoice::with('orders.product', 'customer', 'city',


'province')->where('customer_id',
auth()->guard('api_customer')->user()->id)->where('snap_token',
$snap_token)->first();

Kode diatas digunakan untuk mencari data invoice berdasarkan snap_token dan
customer_id. Dan kita juga memanggil beberapa relasi menggunakan eager loaded yaitu
method with, yang mana di dalamnya ada beberapa relasi, diantaranya adalah :

1. invoice -> orders -> product


2. invoice -> customer.
3. invoice -> city.
4. invoice -> province.

Relasi tersebut merupakan pendefinisian sebuah method yang sebelumnya sudah kita buat
di dalam materi Eloquent Relationships.

Setelah itu, kita membuat kondisi untuk memastikan apakah variable $invoice tersebut
berilai true atau false, jika bernilai true, artinya data ditemukan di dalam database, dan
kita akan melakukan return ke dalam InvoiceResource dengan status true.

if($invoice) {
//return success with Api Resource
return new InvoiceResource(true, 'Detail Data Invoice :
'.$invoice->snap_token.'', $invoice);
}

Tapi, jika variable tersebut bernilai false, artinya data invoice tidak ditemukan di dalam
database, maka kita akan melakukan return ke dalam InvoiceResource dengan status
false.

//return failed with Api Resource


return new InvoiceResource(false, 'Detail Data Invoice Tidak
Ditemukan!', null);

276
Langkah 2 - Menambahkan Route Invoice
Setelah berhasil membuat controller dan menambahkan method di dalamnya, maka
sekarang kita lanjutkan untuk menambahkan route agar controller invoice dapat diakses.

Silahkan tambahkan route di bawah ini di dalam file routes/api.php, di dalam prefix
customer dan group middleware auth:api_customer, lebih tepatnya di bawah route
/dashboard.

//invoices resource
Route::apiResource('/invoices',
App\Http\Controllers\Api\Customer\InvoiceController::class, ['except' =>
['create', 'store', 'edit', 'update', 'destroy'], 'as' => 'customer']);

Di atas, route-nya kita tambahkan except untuk mengecualikan beberapa method dari
resource, yaitu create, store, edit, update dan destroy, karena kita hanya
membutuhkan route index dan juga show saja.

Untuk memastikan apakah route yang sudah kita tambahkan berhasil, kita bisa melakukan
verifikasi dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut

277
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 3 - Uji Coba Rest API Invoice


Sekarang kita lanjutkan untuk melakukan uji coba dari method yang sudah kita buat di atas.
Disini kita akan melakukan uji coba di method index, karena untuk melakukan uji coba
method show datanya belum tersedia.

Uji Coba Method Index

Pertama kita akan lakukan uji coba untuk mendapatkan list data invoices dari database.
Silahkan buka aplikasi Postman kemudian masukkan URL berikut ini
http://localhost:8000/api/customer/invoices dan untuk method-nya silahkan pilih GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data invoices.

278
279
Membuat Restful API Review Product

Sekarang kita akan belajar membuat controller yang fungsinya nanti akan digunakan untuk
melakukan proses insert data review yang diberikan oleh customer sesuai dengan produk
yang dibeli. Dan karena sebelumnya kita belum memiliki Resource untuk review, maka disini
kita akan membuatnya terlebih dahulu.

Langkah 1 - Membuat Resource Review


Pertama, kita akan membuat sebuah resource dan melakukan sedikit perubahan di
dalamnya, agar response JSONyang dihasilkan nanti bisa sesuai dengan yang di harapkan.

Silahkan jalankan perintah di bawah ini di dalam terminal/CMD untuk membuat resource
baru.

php artisan make:resource ReviewResource

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan
nama ReviewResource.php yang berada di dalam folder app/Http/Resources.
Silahkan buka file tersebut dan ubah semua kode-nya menjadi seperti berikut ini :

280
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class ReviewResource extends JsonResource


{
//public properti
public $status;
public $message;
/**
* __construct
*
* @param mixed $status
* @param mixed $message
* @param mixed $resource
* @return void
*/
public function __construct($status, $message, $resource)
{
parent::__construct($resource);
$this->status = $status;
$this->message = $message;
}

/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}

Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.

281
public $status;
public $message;

Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :

$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.

Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :

return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];

Langkah 2 - Membuat Controller Review


Selanjutnya kita akan belajar bagaimana cara membuat controller baru untuk review, disini
kita akan menambahkan 1 method aja untuk proses insert data.

Silahkan jalankan perintah di bawah ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Laravel-nya.

php artisan make:controller Api\\Customer\\ReviewController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama ReviewController.php yang berada di dalam folder
app/Http/Controllers/Api/Customer. Silahkan buka file tersebut dan ubah semua
kode-nya menjadi seperti berikut ini :

282
<?php

namespace App\Http\Controllers\Api\Customer;

use App\Models\Review;
use App\Http\Resources\ReviewResource;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ReviewController extends Controller


{
/**
* store
*
* @param mixed $request
* @return void
*/
public function store(Request $request)
{
//check review already
$check_review = Review::where('order_id',
$request->order_id)->where('product_id', $request->product_id)->first();

if($check_review) {
return response()->json($check_review, 409);
}

$review = Review::create([
'rating' => $request->rating,
'review' => $request->review,
'product_id' => $request->product_id,
'order_id' => $request->order_id,
'customer_id' => auth()->guard('api_customer')->user()->id
]);

if($review) {
//return success with Api Resource
return new ReviewResource(true, 'Data Review Berhasil
Disimpan!', $review);
}

//return failed with Api Resource


return new ReviewResource(false, 'Data Review Gagal Disimpan!',
null);
}

283
}

Dari penambahan kode di atas, pertama kita import Model Review terlebih dahulu.

use App\Models\Review;

Setelah itu, kita juga import ReviewResource, karena akan digunakan untuk
mentransformasi dari Model menjadi format JSON dengan cepat dan baik.

use App\Http\Resources\ReviewResource;

kita Juga import provider Http Request dari Laravel, ini digunakan untuk menerimaa
request yang dikirim melalui cookie, input form dan lain-lain.

use Illuminate\Http\Request;

Di dalam class ReviewController kita menambahkan 1 method baru dengan nama


store, method tersebut digunakan untuk melakukan proses insert data review ke dalam
database.

//check review already


check_review = Review::where('order_id',
$request->order_id)->where('product_id', $request->product_id)->first();

if($check_review) {
return response()->json($check_review, 409);
}

Kode di atas digunakan untuk mengecek apakah ada data yang sama antara order_id dan
product_id di dalam database dengan data yang dikirim oleh request, jika benar sudah
ada maka akan mengembalikan sebuah response JSON dengan memberikan status code
409, artinya data sudah ada di dalam database.

Jika data belum ada di dalam database, maka akan melakukan insert ke dalam database
menggunakan Model, kurang lebih seperti berikut ini :

284
$review = Review::create([
'rating' => $request->rating,
'review' => $request->review,
'product_id' => $request->product_id,
'order_id' => $request->order_id,
'customer_id' => auth()->guard('api_customer')->user()->id
]);

Dari proses insert data di atas, kurang lebih penjelasannya seperti berikut ini :

ATTRIBUTE VALUE KETERANGAN

akan mengambil data dari


rating $request->rating request yang bernama
rating

akan mengambil data dari


review $request->review request yang bernama
review

akan mengambil data dari


product_id $request->product_id request yang bernama
product_id

akan mengambil data dari


order_id $request->order_id request yang bernama
order_id

akan mengambil ID dari


customer_id auth()->guard('api_customer')->user()->id customer yang sedang
login.

Setelah itu, kita membuat kondisi untuk memastikan apakah proses insert data di atas
berhasil atau tidak. Jika variable $review bernilai true, maka akan kita return dengan
ReviewResource dengan status true.

if($review) {
//return success with Api Resource
return new ReviewResource(true, 'Data Review Berhasil Disimpan!',
$review);
}

Tapi, jika variable tersebut bernilai false, maka kita akan return dengan ReviewResource
dengan status false.

285
//return failed with Api Resource
return new ReviewResource(false, 'Data Review Gagal Disimpan!', null);

Langkah 3 - Menambahkan Route Review


Setelah berhasil membuat controller daan menambahkan method di dalamnya, maka
sekarang kita akan lanjutkan untuk membuatkan sebuah route.

Silahkan buka file routes/api.php kemudian masukkan route berikut ini di dalam prefix
customer dan di dalam midlleware auth:api_customer dan tepatnya di bawah route
/invoices.

//review
Route::post('/reviews',
[App\Http\Controllers\Api\Customer\ReviewController::class, 'store'],
['as' => 'customer']);

Di atas, kita membuat route dengan method POST, karena akan kita gunakan untuk
mengirim data ke dalam server. Untuk memverifikasi apakah route tersebut berhasil, kita
bisa menjalankan perintah berikut ini di dalam termina/CMD :

php artisan route:list

286
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 4 - Uji Coba Rest API Review


CATATAN : untuk sekarang kita belum bisa melakukan uji coba, karena master datanya
belum tersedia.

287
Membuat Restful API Categories Web

Sekarang kita akan lanjutkan belajar membuat Rest API untuk halaman depan website, tentu
saja karena akan ditampilkan di halaman website, maka Rest API yang akan kita buat ini
bersifat public, artinya dapat diakses oleh siapa saja tanpa perlu melakukan proses
otentikasi dan generate token.

Langkah 1 - Membuat Controller Category


Pertama-tama kita akan membuat sebuah controller baru dan menambahkan method di
dalamnya, silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\Web\\CategoryController

Jika perintah di atas berhasil dijalankan, maka akan mendapatkan 1 file controller baru
dengan nama CategoryController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah semua kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Web;

use App\Models\Category;
use App\Http\Controllers\Controller;
use App\Http\Resources\CategoryResource;

class CategoryController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//get categories
$categories = Category::latest()->get();
//return with Api Resource
return new CategoryResource(true, 'List Data Categories',

288
$categories);
}

/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($slug)
{
$category = Category::with('products.category')
//get count review and average review
->with('products', function ($query) {
$query->withCount('reviews');
$query->withAvg('reviews', 'rating');
})
->where('slug', $slug)->first();
if($category) {
//return success with Api Resource
return new CategoryResource(true, 'Data Product By Category
: '.$category->name.'', $category);
}

//return failed with Api Resource


return new CategoryResource(false, 'Detail Data Category Tidak
DItemukan!', null);
}
}

Dari penambahan kode di atas, pertama kita melakukan import Model Category, karena
kita akan menggunakan model tersebut untuk mendapaaatkan data dari database.

use App\Models\Category;

Setelah itu, kita juga melakukan import CategoryResource, ini nantinya yang akan
digunakan untuk melakukan transformasi data dari model menjadi format JSON dengan
cepat dan baik.

289
use App\Http\Resources\CategoryResource;

Dan di dalam class CategoryController, kita menambahkan 2 method baru, yaitu :

1. function index - method ini akan digunakan untuk menampilkan list data
category.
2. function show - method ini akan digunakan untuk menampilkan data product
berdasarkan category.

function index

Di dalam method ini kita memanggil model Category untuk melakukan get data dari
database, kurang lebih seperti berikut ini :

//get categories
$categories = Category::latest()->get();

Kode di atas digunakan untuk mendapatkan dari table categories dan data yang akan
ditampilkan akan diurutkan berdasarkan yang paling terbaru menggunakan methof
latest().

Agar data tersebut bisa menjadi format JSON, maka kita akan return menggunakan
CategoryResource, kurang lebih seperti berikut ini :

//return with Api Resource


return new CategoryResource(true, 'List Data Categories', $categories);

function show

Method ini akan kita gunakan untuk menampilkan list data product berdasarkan data
category yang di buka, disini kita melakukan banyak sekali kondisi.

290
$category = Category::with('products.category')

//get count review and average review


->with('products', function ($query) {
$query->withCount('reviews'); // <-- count
"reviews"
$query->withAvg('reviews', 'rating'); // <-- average
"rating"
})
->where('slug', $slug)->first();

Kode di atas merupakan Eloquent untuk menampilkan sebuah data dari database, dan
disini kita menambahkan beberapa eager loaded untuk memanggil beberapa relasi.

Category::with('products.category')

Kode di atas artinya Model Category akan memanggil relasi products dan dari relasi
products akan memanggil relasi category. kurang lebih alurnya seperti ini : Model Category
-> products -> category.

Dan hasil yang di dapatkan kurang lebih akan menjadi seperti ini :

- detail category
- product 1
- nama category
- product 2
- nama category
- product 3
- nama category

Kemudian untuk menghitung jumlah review dan average, kurang lebih seperti berikut ini :

291
->with('products', function ($query) {
$query->withCount('reviews'); // <-- count
"reviews"
$query->withAvg('reviews', 'rating'); // <-- average "rating"
})

Kode di atas artinya Model Category akan memanggil relasi products dan di dalam relasi
product kita extract lagi, fungsinya agar kita dapat memanggil beberapa relasi turunan yang
ada di dalam relasi products dari model Category, seperti memanggil relasi reviews.

alur menghitung jumlah review : Model Category -> products ->


count(reviews).

alur menghitung average rating : Model Category -> products ->


avg('reviews', 'rating').

Dan hasil yang di dapatkan kurang lebih akan menjadi seperti ini :

- detail category
- product 1
- jumlah review
- average rating
- product 2
- jumlah review
- average rating
- product 3
- jumlah review
- average rating

Dan kita cari detail data category tersebut berdasarkan parameter slug yang di dapatkan
dari URL browser. Kurang lebih seperti berikut ini :

->where('slug', $slug)->first();

Jika variable $category bernilai true, artinya data category ditemukan di dalam database,
maka kita akan melakuakn return ke dalam CategoryResource dengan status true.

292
//return success with Api Resource
return new CategoryResource(true, 'Data Product By Category :
'.$category->name.'', $category);

Tapi, jika variable tersebut bernilai false, maka kita akan return juga dan kita berikan
status false di dalam CategoryResource.

//return failed with Api Resource


return new CategoryResource(false, 'Detail Data Category Tidak
DItemukan!', null);

Langkah 2 - Menambahkan Route Category Web


Sekarang kita lanjutkan untuk membuat route agar controller dan method yang sudah kita
buat di atas dapat diakses di URL browser.

Silahkan tambahkan route di bawah ini di dalam file routes/api.php dan jangan di
masukkan di dalam prefix admin maupun customer, karena kita akan membuat prefix lagi
untuk web.

//group route with prefix "web"


Route::prefix('web')->group(function () {

//categories resource
Route::apiResource('/categories',
App\Http\Controllers\Api\Web\CategoryController::class, ['except' =>
['create', 'store', 'edit', 'update', 'destroy'], 'as' => 'web']);

});

Jadi di dalam file routes/api.php kita memiliki 3 prefix, kurang lebih seperti ini :

293
/routes/api.php
- prefix "admin"
- route
- route
...
- prefix "customer"
- route
- route
...
- prefix "web"
- route
- route
...

Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

294
php artisan route:cache

Langakah 3 - Uji Coba Rest API Category Web


CATATAN !: silahkan menambahkan data category dan product di Rest API admin, untuk
proses uji coba disini.

Uji Coba Method Index

Silahkan buka aplikasi Postman dan masukkan URL berikut ini


http://localhost:8000/api/web/categories dan untuk method-nya silahkan pilih GET. Jika
sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan response kurang
lebih seperti berikut ini :

Uji Coba Method Show

Sekarang kita lanjutkan untuk melakukan uji coba detail category, pastikan sudah
menambahkan data product terlebih dahulu ya dan sekarang kita akan lakukan uji coba
category yang di dalamnya memiliki data product.

Dalam contoh data yang saya gunakan adalah category Aksesoris, maka untuk uji
cobanya kurang lebih seperti berikut ini, silahkan buka aplikasi Postman dan masukkan
URL berikut ini http://localhost:8000/api/web/categories/aksesoris. Dan untuk method-nya
silahkan pilih GET.

295
CATATAN ! : silahkan ganti aksesoris dengan slug category yang kalian miliki. Dan
pastikan ada product yang menggunakan category tersebut.

Jika berhasil, maka kurang lebih hasilnya seperti berikut ini :

296
Membuat Restful API Products Web

Setelah berhasil membuat Rest API untuk category, maka sekarang kita akan lanjutkan
untuk membuat Rest API untuk data product, dan tentu saja karena akan ditampilkan di
halaman depan website, maka Rest API tersebut akan bersifat public. Disini kita juga akan
membuat 2 method, yang pertama untuk menampilkan list data product, dan yang kedua
adalah detail dari data product tersebut.

Langkah 1 - Membuat Controller Product


Sekarang kita akan membuat controller product terlebih dahulu, untuk membuatnya
silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\Web\\ProductController

Jika berhasil, maka kita akan mendapatkan 1 file controller baru dengan nama
ProductController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Web;

use App\Models\Product;
use App\Http\Controllers\Controller;
use App\Http\Resources\ProductResource;

class ProductController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//get products
$products = Product::with('category')
//count and average

297
->withAvg('reviews', 'rating')
->withCount('reviews')
//search
->when(request()->q, function($products) {
$products = $products->where('title', 'like', '%'.
request()->q . '%');
})->latest()->paginate(8);
//return with Api Resource
return new ProductResource(true, 'List Data Products',
$products);
}

/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($slug)
{
$product = Product::with('category', 'reviews.customer')
//count and average
->withAvg('reviews', 'rating')
->withCount('reviews')
->where('slug', $slug)->first();
if($product) {
//return success with Api Resource
return new ProductResource(true, 'Detail Data Product!',
$product);
}

//return failed with Api Resource


return new ProductResource(false, 'Detail Data Product Tidak
Ditemukan!', null);
}
}

Dari penambahan kode di atas, pertama kita lakukan import untuk Model Product.

use App\Models\Product;

Dan juga kita import ProductResource, karena nantinya akan digunakan untuk melakukan

298
transformasi data dari Model menjadi format JSON.

use App\Http\Resources\ProductResource;

Dan di dalam class ProductController, kita menambahkan 2 method baru, yaitu :

1. function index - digunakan untuk menampilkan list data products.


2. function show - digunakan untuk menampilkan detail data product.

function index

Method ini akan digunakan untuk menampilkan list data products dari database, dan disini
kita juga akan memanggil beberapa relasi yang terkait dengan data product, seperti
review dan category.

Product::with('category')

Kode di atas, artinya kita akan menampilkan data product beserta relasi category, method
with digunakan untuk memanggil sebuah relasi di dalam Eloquent atau biasa disebut
dengan eager loaded.

Setelah itu, kita juga akan melakukan count dan average relasi reviews, kurang lebih
seperti berikut ini :

->withAvg('reviews', 'rating') // <-- average "rating" di table


"reviews"
->withCount('reviews') // <-- count "reviews"

Kemudian kita menambahkan kondisi lagi menggunakan method when, jadi jika ada sebuah
request dengan nama q, maka kita akan mencari data product berdasarkan attribute title
dengan dicocokan isi dari request q.

->when(request()->q, function($products) {
$products = $products->where('title', 'like', '%'. request()->q .
'%');
})

299
Kemudian kita urutkan data yang akan di tampilkan berdasarkan yang paling terbaru
menggunakan method latest(). Dan datanya akan dibatasi perhalaman 8 record
menggunakan method paginate().

Agar data products dapat menghasilkan format JSON, maka kita akan return ke dalam
ProductResource, kurang lebih seperti berikut ini :

//return with Api Resource


return new ProductResource(true, 'List Data Products', $products);

function show

Method ini akan kita gunakan untuk menampilkan detail data product berdasarkan slug
yang ada di URL. Disini kita juga akan memanggil banyak relasi.

Product::with('category', 'reviews.customer')

Kode di atas artinya Model Product memanggil relasi category dan memanggil relasi
reviews dan dari relasi reviews memanggil ke relasi customer. Jadi kurang lebih seperti
berikut ini :

Model Product -> category.


Model Product -> reviews -> customer.

Setelah itu, kita juga akan melakukan count dan average di dalam relasi reviews, kurang
lebih seperti berikut ini :

//count and average


->withAvg('reviews', 'rating') // <-- average "rating"
->withCount('reviews') // <-- count "review"

Jika data product ditemukan, maka kita akan return ke ProductResource dengan status
true.

//return success with Api Resource


return new ProductResource(true, 'Detail Data Product!', $product);

Tapi, jika data tidak ditemukan di dalam database, maka kita akan mereturn ke dalam

300
ProductResource dengan status false.

//return failed with Api Resource


return new ProductResource(false, 'Detail Data Product Tidak
Ditemukan!', null);

Langkah 2 - Menambahkan Route Product


Kita lanjutkan untuk menambahkan route baru untuk data product, silahkan buka file
routes/api.php kemudian tambahkan route berikut ini di dalam prefix web dan tepatnya
di bawah route /categories.

//products resource
Route::apiResource('/products',
App\Http\Controllers\Api\Web\ProductController::class, ['except' =>
['create', 'store', 'edit', 'update', 'destroy'], 'as' => 'web']);

Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

301
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 3 - Uji Coba Rest API Product


Sekarang kita akan melakukan uji coba dari method yang sudah kita buat di atas, disini kita
akan melakukan ke dalam 2 method, yaitu index dan show.

Uji Coba Method Index

Silahkan buka aplikasi Postman dan masukkan URL berikut ini


http://localhost:8000/api/web/products dan untuk method-nya silahkan pilih GET.

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan list data
product beserta relasi yang terkait, seperti category, count review dan average rating.
Kurang lebih seperti berikut ini :

Uji Coba Method Show

Kita lanjutkan untuk melakukan uji coba method show, dan disini untuk nilai slug silahkan
disesuaikan dengan data yang teman-teman miliki. Dalam contoh ini saya menggunakan
slug mi-37w-dual-port-car-charger.

302
Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/web/products/mi-37w-dual-port-car-charger dan untuk method-nya
silahkan pilih GET.

Jika sudah, silahkan klik Send dan jika berhasil maka akan mendpatkan response JSON
kurang lebih seperti berikut ini :

303
Membuat Restful API Sliders Web

Pada materi kali ini kita semua akan belajar bagaiamana cara membuat Rest API untuk data
sliders, dan jenis yang akan buat adalah public, artinya siapa saja bisa mengakses Rest API
tersebut.

Langkah 1 - Membuat Controller Slider


Pertama kita akan membuat controller terlebih dahulu, kemudian kita akan menambahkan
method di dalamnya. Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\Web\\SliderController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
yang bernama SliderController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :

304
<?php

namespace App\Http\Controllers\Api\Web;

use App\Models\Slider;
use App\Http\Controllers\Controller;
use App\Http\Resources\SliderResource;

class SliderController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//get sliders
$sliders = Slider::latest()->get();
//return with Api Resource
return new SliderResource(true, 'List Data Sliders', $sliders);
}
}

Di atas kita melakukan import Model Slider, karena kita akan gunakan untuk
mendapatkan data slider dari database.

use App\Models\Slider;

Setelah itu, kita juga import SliderResource, karena akan digunakan untuk mengubah
model menjadi format JSON dengan cepat dan mudah.

use App\Http\Resources\SliderResource;

Di dalam class SliderController kita menambahkan 1 method baru yaitu index,


method tersebut akan kita gunakan untuk menampilkan list data slider dari database.

305
//get sliders
$sliders = Slider::latest()->get();

Di atas kita melakukan get data dari database menggunakan Model dan data yang akan
ditampilkan akan diurutkan berdasarkan yang terbaru menggunakan method latest().

Setelah itu, agar menjadi format JSON, maka kita perlu melakukan return menggunakan
SliderResource, kurang lebih seperti berikut ini :

//return with Api Resource


return new SliderResource(true, 'List Data Sliders', $sliders);

Langkah 2 - Menambahkan Route Slider


Kita lanjutkan untuk menambahkan route baru untuk data slider, silahkan buka file
routes/api.php kemudian tambahkan route berikut ini di dalam prefix web dan tepatnya
di bawah route /products.

//sliders route
Route::get('/sliders',
[App\Http\Controllers\Api\Web\SliderController::class, 'index'], ['as'
=> 'web']);

Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

306
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 3 - Uji Coba Rest API Slider


Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/web/sliders dan untuk method-nya silahkan pilih GET. Jika sudah,
silahkan klik Send dan jika berhasil maka kita akan mendapatkan list data sliders. Kurang
lebih seperti berikut ini :

307
Dari gambar di atas kita tidak menemukan data slider apapun, karena kita belum
menambahkannya, untuk itu jika ingin melihat hasilnya, silahkan menambahkan data slider
melalui Rest API di admin.

308
Installasi dan Konfigurasi Raja Ongkir

Sekarang kita akan belajar bagaimana cara konfigurasi RajaOngkir di dalam project
Laravel, nanti konsepnya kita akan mengambil data provinsi dan kota/kabupaten untuk kita
insert ke dalam database menggunakan seeder, tujuannya agar setiap request tidak
menembak API RajaOngkir secara langsung, yang akbibatnya proses menjadi lambat.

Jadi untuk request ke data provinsi dan kota/kabupaten akan diambil dari database kita
sendiri dan untuk proses biaya perhitungan ongkos kirim baru kita request ke API
RajaOngkir.

Langkah 1 - Daftar Akun di Raja Ongkir


Pertama, kita akan daftar akun terlebih dahulu di dalam raja ongkir, karena kita nanti akan
membutuhkan API KEY untuk bisa menggunakan layanan tersebut. Silahkan daftar akun
melalui link berikut ini : https://rajaongkir.com/akun/daftar.

Setelah berhasil mendaftar, silahkan login dan klik menu API Key, maka kita akan
mendapatkan API KEY tersebut. Kurang lebih seperti berikut ini :

Langkah 2 - Konfigurasi Config Raja Ongkir


Silahkan buka file .env kemudian tambahkan kode berikut ini :

309
RAJAONGKIR_API_KEY=api_key_kamu_di_paste_disini

Setelah itu, silahkan masuk di dalam file config/services.php, kemudian tambahkan


array berikut ini di dalamnya :

'rajaongkir' => [
'key' => env('RAJAONGKIR_API_KEY')
]

Jika file config/services.php di tulis secara lengkap, maka kurang lebih seperti berikut
ini :

310
<?php

return [

/*
|-------------------------------------------------------------------
-------
| Third Party Services
|-------------------------------------------------------------------
-------
|
| This file is for storing the credentials for third party services
such
| as Mailgun, Postmark, AWS and more. This file provides the de
facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/

'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
],

'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],

'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'rajaongkir' => [
'key' => env('RAJAONGKIR_API_KEY')
]

];

311
Langkah 3 - Membuat Seeder Data Provinsi
Setelah berhasil mengatur konfigurasi Raja Ongkir di dalam project Laravel, sekarang kita
lanjutkan untuk mengambil data provinsi yang ada di Raja Ongkir dan akan kita pindahkan
di database milik kita sendiri.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD untuk membuat seeder
provinsi :

php artisan make:seeder ProvinceSeeder

Jika perintah di atas berhasil di jalankan, maka kita akan mendapatkan 1 file seeder baru
dengan nama ProvinceSeeder.php yang berada di dalam folder database/seeders.
Silahkan buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :

312
<?php

namespace Database\Seeders;

use App\Models\Province;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Http;

class ProvinceSeeder extends Seeder


{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
//Fetch Rest API
$response = Http::withHeaders([
//api key rajaongkir
'key' => config('services.rajaongkir.key'),
])->get('https://api.rajaongkir.com/starter/province');
//loop data from Rest API
foreach($response['rajaongkir']['results'] as $province) {

//insert ke table "provinces"


Province::create([
'province_id' => $province['province_id'],
'name' => $province['province']
]);

}
}
}

Di atas, pertama kita melakukan import Model Province, karena kita akan menggunakan
model ini untuk melakukan insert data.

use App\Models\Province;

Setelah itu, kita juga import Facades Http Client dari Laravel, atau biasa disebut dengan
Guzzle, ini akan digunakan untuk melakukan fetching ke dalam sebuah Rest API.

313
use Illuminate\Support\Facades\Http;

Dan di dalam method run kita menambahkan beberapa kode, kurang lebih seperti berikut
ini :

//Fetch Rest API


$response = Http::withHeaders([
//api key rajaongkir
'key' => config('services.rajaongkir.key'),
])->get('https://api.rajaongkir.com/starter/province');

Di atas kita melakukan Http Request ke dalam sebuah endpoint


https://api.rajaongkir.com/starter/province dan method yang digunakan adalah GET dan kita
juga menyertakan API KEY di dalam headers.

Setelah data di dapatkan, kita lakukan perulangan menggunakan foreach dan di dalamnya
kita melakukan insert data menggunakan Model.

//loop data from Rest API


foreach($response['rajaongkir']['results'] as $province) {

//insert ke table "provinces"


Province::create([
'province_id' => $province['province_id'],
'name' => $province['province']
]);

Sekarang kita akan belajar menjalankan seeder yang sudah kita buat di atas, silahkan
jalankan perintah berikut ini di dalam terminal/CMD :

composer dump-autoload

314
php artisan db:seed --class=ProvinceSeeder

Jika berhasil, silahkan cek di dalam table provinces, maka kita sudah mendapatkan
hasilnya seperti berikut ini :

Langkah 4 - Membuat Seeder Data Kota/Kabupaten


Setelah berhasil membuat seeder untuk data provinsi, maka sekarang kita akan lanjutkan
untuk data kota/kabupaten. Silahkan jalankan perintah dibawah ini di dalam terminal/CMD :

php artisan make:seeder CitySeeder

Jika berhaasil, maka kita akan mendapatkan 1 file seeder baru dengan nama
CitySeeder.php yang berada di dalam folder database/seeders. Silahkan buka file
tersebut dan ubah semua kode-nya menjadi seperti berikut ini :

315
<?php

namespace Database\Seeders;

use App\Models\City;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Http;

class CitySeeder extends Seeder


{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
//Fetch Rest API
$response = Http::withHeaders([
//api key rajaongkir
'key' => config('services.rajaongkir.key'),
])->get('https://api.rajaongkir.com/starter/city');
//loop data from Rest API
foreach($response['rajaongkir']['results'] as $city) {

//insert ke table "cities"


City::create([
'province_id' => $city['province_id'],
'city_id' => $city['city_id'],
'name' => $city['city_name'] . ' - ' . '('.
$city['type'] .')',
]);

}
}
}

Di atas, kita melakukan import Model City, karena akan kita gunakan untuk melakukan
insert data ke dalam database.

use App\Models\City;

316
Setelah itu, kita juga import Facades Http Client dari Laravel, atau biasa disebut dengan
Guzzle, ini akan digunakan untuk melakukan fetching ke dalam sebuah Rest API.

use Illuminate\Support\Facades\Http;

Dan di dalam method run kita menambahkan beberapa kode, kurang lebih seperti berikut
ini :

//Fetch Rest API


$response = Http::withHeaders([
//api key rajaongkir
'key' => config('services.rajaongkir.key'),
])->get('https://api.rajaongkir.com/starter/city');

Di atas kita melakukan fetching ke dalam endpoint Rest API


https://api.rajaongkir.com/starter/city dan untuk method-nya adalah GET, dan di dalam
headers kita tambahkan API KEY dari Raja Ongkir.

Setelah data di dapatkan, kita lakukan perulangan menggunakan foreach dan di dalamnya
kita melakukan insert data menggunakan Model.

//loop data from Rest API


foreach($response['rajaongkir']['results'] as $city) {

//insert ke table "cities"


City::create([
'province_id' => $city['province_id'],
'city_id' => $city['city_id'],
'name' => $city['city_name'] . ' - ' . '('. $city['type']
.')',
]);

Sekarang kita akan belajar menjalankan seeder yang sudah kita buat di atas, silahkan
jalankan perintah berikut ini di dalam terminaal/CMD :

317
composer dump-autoload

php artisan db:seed --class=CitySeeder

Jika berhasil, silahkan cek di dalam table cities, maka kita sudah mendapatkan hasilnya
seperti berikut ini :

318
Membuat Restful API Raja Ongkir

Setelah berhasil mendapatkan data provinsi dan kota/kabupaten untuk Raja Oangkir, maka
sekarang kita akan lanjutkan membuat Rest API untuk menampilkan data provinsi,
kota/kabupaten dan menghitung biaya ongkos kirim.

Langkah 1 - Membuat Resource Raja Ongkir


Pertama-tama kita akan membuat Resource terlebih dahulu, agar nanti data yang di
dapatkan dari Model dapat diubah menjadi format JSON dengan lebih mudah dan cepat.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Laravel.

php artisan make:resource RajaOngkirResource

Jika perintah di atas berhasil, maka kita akan mendapatkan 1 file baru dengan nama
RajaOngkirResource.php di dalam folder app/Http/Resources. Silahkan buka file
tersebut dan ubah semua kode-nya menjadi seperti berikut ini :

319
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class RajaOngkirResource extends JsonResource


{
//public properti
public $status;
public $message;
/**
* __construct
*
* @param mixed $status
* @param mixed $message
* @param mixed $resource
* @return void
*/
public function __construct($status, $message, $resource)
{
parent::__construct($resource);
$this->status = $status;
$this->message = $message;
}

/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}

Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.

320
public $status;
public $message;

Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :

$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.

Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :

return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];

Langkah 2 - Membuat Controller Raja Ongkir


Kita lanjutkan untuk membuat controller baru, nantinya akan kita gunakan untuk
menampilkan list data provinsi, list data kota berdasarkan provinsi dan menghitung biaya
ongkos kirim.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD untuk membuat controller baru
:

php artisan make:controller Api\\Web\\RajaOngkirController

Jike perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama RajaOngkirController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubaah kode-nya
menjadi seperti berikut ini :

321
<?php

namespace App\Http\Controllers\Api\Web;

use App\Models\City;
use App\Models\Province;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Http;
use App\Http\Resources\RajaOngkirResource;

class RajaOngkirController extends Controller


{
/**
* getProvinces
*
* @return void
*/
public function getProvinces()
{
//get all provinces
$provinces = Province::all();

//return with Api Resource


return new RajaOngkirResource(true, 'List Data Provinces',
$provinces);
}
/**
* getCities
*
* @param mixed $request
* @return void
*/
public function getCities(Request $request)
{
//get province name
$province = Province::where('province_id',
$request->province_id)->first();

//get cities by province


$cities = City::where('province_id',
$request->province_id)->get();

//return with Api Resource


return new RajaOngkirResource(true, 'List Data City By Province

322
: '.$province->name.'', $cities);
}
/**
* checkOngkir
*
* @param mixed $request
* @return void
*/
public function checkOngkir(Request $request)
{
//Fetch Rest API
$response = Http::withHeaders([
//api key rajaongkir
'key' => config('services.rajaongkir.key')
])->post('https://api.rajaongkir.com/starter/cost', [

//send data
'origin' => 113, // ID kota Demak
'destination' => $request->destination,
'weight' => $request->weight,
'courier' => $request->courier
]);

//return with Api Resource


return new RajaOngkirResource(true, 'List Data Biaya Ongkos
Kirim : '.$request->courier.'',
$response['rajaongkir']['results'][0]['costs']);
}
}

Dari penambahan kode di atas, pertama kita import Model City dan Porivince, karena
kita akan menggunakan model tersebut untuk mendapatkan data dari database.

use App\Models\City;
use App\Models\Province;

Selanjutnya, agar data yang kita dapatkan dari Model dapat diubah menjadi format JSON,
maka kita juga akan melakukan import RajaOngkirResource.

323
use App\Http\Resources\RajaOngkirResource;

Dan karena kita akan melakukan fetching ke Rest API Raja Ongkir untuk menghitung biaya
ongkos kirim, maka kita juga akan import Facades Http Client atau Guzzle.

use Illuminate\Support\Facades\Http;

Dan di dalam class RajaOngkirController1 kita menambahkan 3 method baru, yaitu :

1. function getProvinces - digunakan untuk mendapatkan list data provinsi di


Indonesia.
2. function getCities - digunakan untuk mendapatkan list kota/kabupaten
berdasarkan provinsi tertentu.
3. function checkOngkir - digunakan untuk mendapatkan biaya ongkos kirim.

function getProvinces

Method ini akan kita gunakan untuk mendapatkan list data provinsi di seluruh Indonesia
melalui Model.

//get all provinces


$provinces = Province::all();

Setelah itu, agar dapat bisa berubah menjadi format JSON, maka kita melakukan
transformasi menggunakan RajaOngkirResource.

//return with Api Resource


return new RajaOngkirResource(true, 'List Data Provinces', $provinces);

function getCities

Method ini akan kita gunakan untuk mendapatkan list data kode/kabupaten berdasarkan ID
provinsi tertentu.

324
//get province name
$province = Province::where('province_id',
$request->province_id)->first();

Di atas akan kita gunakan untuk mendapatkan detail data provinsi berdasarkan
province_id yang dikirimkan oleh request.

Setelah itu, kita akan mencari data kota/kabupaten berdasarkan province_id juga, kurang
lebih seperti berikut ini :

//get cities by province


$cities = City::where('province_id', $request->province_id)->get();

Setelah itu, kita akan return menggunakan RajaOngkirResource agar data yang ada di
Model berubah menjadi format JSON.

//return with Api Resource


return new RajaOngkirResource(true, 'List Data City By Province :
'.$province->name.'', $cities);

function checkOngkir

Di dalam method ini akan digunakan untuk menghitung biaya ongkos kirim, disini kita
melakukan request ke dalam endpoint https://api.rajaongkir.com/starter/cost dengan
method POST. Dan kita juga mengirim 4 data di dalamnya, yaitu :

1. origin - merupakan ID kota/kabupaten asal.


2. destination - merupakan ID kota/kabupaten tujuan.
3. weight - merupakan berat paket/barang (dalam satuan gram).
4. courier - merupakan kurir yang di gunakan, untuk versi starter hanya tersedia jne,
pos dan tiki.

Dan di dalam headers kita juga menambahkan API KEY dari Raja Ongkir, yang sebelumnya
sudah kita buat di dalam file .env di di konfig melalui config/services.php

//api key rajaongkir


'key' => config('services.rajaongkir.key')

Setelah itu, agar kita dapat hasil dari perhitungan biaya ongkos kirim dengan format JSON,

325
maka kita perlu melakukan return menggunakan RajaOngkirResource.

//return with Api Resource


return new RajaOngkirResource(true, 'List Data Biaya Ongkos Kirim :
'.$request->courier.'', $response['rajaongkir']['results'][0]['costs']);

Langkah 3 - Menambahkan Route Raja Ongkir


Kita lanjutkan untuk menambahkan route baru untuk Raja Ongkir, silahkan buka file
routes/api.php kemudian tambahkan route berikut ini di dalam prefix web dan tepatnya
di bawah route /sliders.

//rajaongkir
Route::get('/rajaongkir/provinces',
[App\Http\Controllers\Api\Web\RajaOngkirController::class,
'getProvinces'], ['as' => 'web']);

Route::post('/rajaongkir/cities',
[App\Http\Controllers\Api\Web\RajaOngkirController::class, 'getCities'],
['as' => 'web']);

Route::post('/rajaongkir/checkOngkir',
[App\Http\Controllers\Api\Web\RajaOngkirController::class,
'checkOngkir'], ['as' => 'web']);

Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

326
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 4 - Uji Coba Rest API Raja Ongkir


Sekarang kita lanjutkan untuk melakukan uji coba dari setiap method yang sudah kita
tambahkan di dalam controller, yaitu untuk mendapatkan list data provinsi, kota dan
menghitung biaya ongkos kirim.

Uji Coba Method getProvinces

Silahkan buka aplikasi Postman, kemudian masukkan URL berikut ini


http://localhost:8000/api/web/rajaongkir/provinces dan untuk method-nya silahkan pilih GET.
Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan list data
provinsi kurang lebih seperti berikut ini :

327
Uji Coba Method getCities

Setelah itu, kita akan lakukan uji coba untuk mendapatkan list data kota/kabupaten
berdasarkan ID provinsi. Dan disini saya akan mencoba untuk menampilkan list dari provinsi
Bali dengan ID 1.

Silahkan buka aplikasi Postman dan masukkan URL berikut ini


http://localhost:8000/api/web/rajaongkir/cities dan untuk method-nya silahkan pilih POST.
Setelah itu, silahkan klik tab Body dan pilih form-data, kemudian masukkan key dan value
berikut ini :

KEY VALUE KETERANGAN

province_id 1 1 merupakan ID dari provinsi Bali.

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan list data
kota/kabupaten dari provinsi Bali, kurang lebih seperti berikut ini :

328
Uji Coba Method CheckOngkir

Kita lanjutkan untuk melakukan uji coba proses hitung biaya ongkos kirim, silahkan buka
aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/web/rajaongkir/checkOngkir dan untuk method-nya silahkan pilih
POST.

Setelah itu silahkan klik tab Body dan pilih form-data dan masukkan key dan value berikut
ini :

KEY VALUE

destination 114 merupakan ID dari kota/kabupaten Denpasar.

weight 1000 berat dalam satuan gram

courier jne jenis kurir, tersedia untuk jne, pos dan tiki

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan list biaya
ongkos kirim dari kota Demak ke kota Denpasar.

329
330
Membuat Restful API Carts

Sekarang akan kita lanjutkan belajar bagaimana cara membuat Restful API untuk data cart,
disini kita akan membuat beberapa method yang akan digunakan di dalam fitur cart, antara
lain :

menampilkan data cart.


menambahkan product ke cart (Add To Cart).
menampilkan total cart.
menampilkan total berat product di dalam cart.

Dan untuk Rest API yang akan kita buat ini akan bersifat private, yang artinya hanya bisa
diakses dan digunakan jika customer sudah melakukan proses otentikasi atau login terlebih
dahulu.

Langkah 1 - Membuat Resource Cart


Pertama-tama kita akan membuat Resource untuk data cart dulu, tujuannya agar proses
transformasi data dari model menjadi format JSON lebih mudah dan cepat.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Laravel-nya.

php artisan make:resource CartResource

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file baru dengan
nama CartResource.php yang berada di dalam folder app/Http/Resources. Silahkan
buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :

331
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class CartResource extends JsonResource


{
//public properti
public $status;
public $message;
/**
* __construct
*
* @param mixed $status
* @param mixed $message
* @param mixed $resource
* @return void
*/
public function __construct($status, $message, $resource)
{
parent::__construct($resource);
$this->status = $status;
$this->message = $message;
}

/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}

Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.

332
public $status;
public $message;

Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :

$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.

Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :

return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];

Langkah 2 - Membuat Controller Cart


Setelah berhasil menambahkan Resource, maka sekarang kita akan membuat controller
baru untuk data cart. Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\Web\\CartController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama CartController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah semua kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Web;

333
use App\Models\Cart;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\CartResource;

class CartController extends Controller


{
/**
* __construct
*
* @return void
*/
public function __construct()
{
$this->middleware('auth:api_customer');
}

/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$carts = Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->latest()
->get();
//return with Api Resource
return new CartResource(true, 'List Data Carts :
'.auth()->guard('api_customer')->user()->name.'', $carts);
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$item = Cart::where('product_id',
$request->product_id)->where('customer_id',
auth()->guard('api_customer')->user()->id);

334
//check if product already in cart and increment qty
if ($item->count()) {

//increment / update quantity


$item->increment('qty');

$item = $item->first();

//sum price * quantity


$price = $request->price * $item->qty;

//sum weight
$weight = $request->weight * $item->qty;

$item->update([
'price' => $price,
'weight' => $weight
]);

} else {

//insert new item cart


$item = Cart::create([
'product_id' => $request->product_id,
'customer_id' =>
auth()->guard('api_customer')->user()->id,
'qty' => $request->qty,
'price' => $request->price,
'weight' => $request->weight
]);

}
//return with Api Resource
return new CartResource(true, 'Success Add To Cart', $item);
}
/**
* getCartPrice
*
* @return void
*/
public function getCartPrice()
{
$totalPrice = Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->sum('price');

335
//return with Api Resource
return new CartResource(true, 'Total Cart Price', $totalPrice);
}
/**
* getCartWeight
*
* @return void
*/
public function getCartWeight()
{
$totalWeight = Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->sum('weight');

//return with Api Resource


return new CartResource(true, 'Total Cart Weight',
$totalWeight);
}
/**
* removeCart
*
* @param mixed $request
* @return void
*/
public function removeCart(Request $request)
{
$cart = Cart::with('product')
->whereId($request->cart_id)
->first();
$cart->delete();

//return with Api Resource


return new CartResource(true, 'Success Remove Item Cart', null);
}
}

Dari penambahan kode di atas, pertama kita melakukan import Model Cart, karena kita
akan gunakan untuk mengambil data, hapus data dan lain-lain.

use App\Models\Cart;

336
Kemudian kita juga import CartResource, fungsinya akan digunakan untuk melakukan
transformasi dari data model menjadi format JSON.

use App\Http\Resources\CartResource;

Dan di dalam class CartController, kita menambahkan beberapa method, diantaranya


yaitu :

1. function __construct - digunakan untuk check middleware


auth:api_customer.
2. function index - digunakan untuk menampilkan data cart berdasarkan customer.
3. function store - digunakan untuk melakukan insert data cart.
4. function getCartPrice - digunakan untuk mendapatkan total cart.
5. function getCartWeight - digunakan untuk mendapatkan total berat product
yang ada di dalam cart.
6. function removeCart - digunakan untuk menghapus data cart dari database.

function __construct

Method ini akan pertama kali dijalankan ketika sebuah class di panggil dan disini kita akan
manfaatkan untuk mengatur middleware auth:api_customer, artinya class dan method
yang ada di dalamnya hanya bisa di akses jika sudah melakukan proses otentikasi / login.

$this->middleware('auth:api_customer');

function index

Method ini akan digunakan untuk menampilkan list data cart dari customer yang sedang
login. Kurang lebih seperti berikut ini :

$carts = Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->latest()
->get();

Di atas kita juga memanggil relasi product, dan menampilkan data berdasarkan
customer_id.

Setelah itu, agar datanya dapat berubah menjadi format JSON, maka kita perlu melakukan

337
return dengan CartResource, kurang lebih seperti berikut ini :

//return with Api Resource


return new CartResource(true, 'List Data Carts :
'.auth()->guard('api_customer')->user()->name.'', $carts);

function store

Method ini akan digunakan untuk melakukan proses insert data cart ke dalam database,
disini kita membuat sebuah kondisi, apabila data product yang di masukkan sama, maka
kita cukup melakukan update quantity-nya saja.

$item = Cart::where('product_id',
$request->product_id)->where('customer_id',
auth()->guard('api_customer')->user()->id);

Kode di atas digunakan untuk melakukan check data di dalam table carts, apakah
product_id dan customer_id yang dikirm sudah ada di dalam table atau tidak.

Jika data tersebut ada, maka kita cukup melakukan update quantity-nya saja, kurang lebih
seperti berikut ini :

//increment / update quantity


$item->increment('qty');

$item = $item->first();

//sum price * quantity


$price = $request->price * $item->qty;

//sum weight
$weight = $request->weight * $item->qty;

$item->update([
'price' => $price,
'weight' => $weight
]);

Di atas kita melakukan update attribute qty, price dan weight, karena data yang dikirim
sebelumnya sudah ada. Tapi jika data product_id dan customer_id belum ada di dalam

338
table carts, maka kita akan melakukan proses insert , kurang lebih seperti berikut ini :

//insert new item cart


$item = Cart::create([
'product_id' => $request->product_id,
'customer_id' => auth()->guard('api_customer')->user()->id,
'qty' => $request->qty,
'price' => $request->price,
'weight' => $request->weight
]);

Setelah itu, agar datanya dapat menampilkan format JSON, maka kita perlu melakukan
return menggunakan CartResource. Kurang lebih seperti berikut ini :

//return with Api Resource


return new CartResource(true, 'Success Add To Cart', $item);

function getCartPrice

Method ini akan kita gunakan untuk melakukan hitung total harga yang ada di dalam table
carts berdasarkan customer yang sedang login, kurang lebih seperti berikut ini :

$totalPrice = Cart::with('product')
->where('customer_id', auth()->guard('api_customer')->user()->id)
->sum('price');

Di atas, kita melakukan sum ke dalam attribute price, dan kita hanya menghitung data
berdasarkan customer yang sedang login menggunakan where. Setelah itu kita return
menggunakan CartResource agar data yang di dapatkan berubah menjadi format JSON.

//return with Api Resource


return new CartResource(true, 'Total Cart Price', $totalPrice);

function getCartWeight

Method ini sama dengan getCartPrice bedanya disini kita melakukan sum ke dalam
attributte weight dan tujuannya untuk mendapatkan jumlah berat dari product yang ada di

339
dalam cart.

$totalWeight = Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->sum('weight');

Kemudian kita akan return menggunakan CartResource, kurang lebih seperti berikut ini :

//return with Api Resource


return new CartResource(true, 'Total Cart Weight', $totalWeight);

function removeCart

Method ini akan digunakan untuk melakukan hapus data dari table carts, kurang lebih
seperti berikut ini :

$cart = Cart::with('product')
->whereId($request->cart_id)
->first();

Kode di atas digunakan untuk mencari data cart berdasarkan ID yang dikirim melalui
request, jika data tersebut berhasil ditemukan, maka kita akan menghapusnya menggunakn
kode seperti berikut ini :

$cart->delete();

Dan setelah itu kita akan return menggunakan CartResource, kurang lebih seperti berikut
ini :

//return with Api Resource


return new CartResource(true, 'Success Remove Item Cart', null);

340
Langkah 3 - Menambahkan Route Cart
Kita lanjutkan untuk menambahkan route baru untuk Cart, silahkan buka file
routes/api.php kemudian tambahkan route berikut ini di dalam prefix web dan tepatnya
di bawah route /rajaongkir/checkOngkir.

//get cart
Route::get('/carts',
[App\Http\Controllers\Api\Web\CartController::class, 'index'], ['as' =>
'web']);

//store cart
Route::post('/carts',
[App\Http\Controllers\Api\Web\CartController::class, 'store'], ['as' =>
'web']);
//get cart price
Route::get('/carts/total_price',
[App\Http\Controllers\Api\Web\CartController::class, 'getCartPrice'],
['as' => 'web']);

//get cart weight


Route::get('/carts/total_weight',
[App\Http\Controllers\Api\Web\CartController::class, 'getCartWeight'],
['as' => 'web']);

//remove cart
Route::post('/carts/remove',
[App\Http\Controllers\Api\Web\CartController::class, 'removeCart'],
['as' => 'web']);

Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

341
Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

Langkah 4 - Uji Coba Rest API Cart


Sekarang kita lanjutkan untuk melakukan uji coba method yang sudah kita buat di dalam
controller cart. Pertama-tama silahkan login dulu via Postman untuk customer agar
mendapatkan token. Karena Rest API ini mebutuhkan akses token JWT.

Uji Coba Method Index

Silahkan buka aplikasi Postman, kemudian masukkan URL berikut ini


http://localhost:8000/api/web/carts dan untuk method-nya pilih GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah

342
response dalam format JSON yang berisi informasi data carts.

Uji Coba Method Store

Sekarang kita lanjutkan untuk membuat fungsi add to cart atau memasukkan data cart ke
dalam database. Silahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/web/carts dan untuk method-nya silahkan pilih POST.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Setelah itu, silahkan buka tab Body dan pilih form-data, kemudian masukkan key dan
value berikut ini :

KEY VALUE KETERANGAN

Silahkan disesuiakan dengan ID product yang dimiliki di dalam


product_id 2
database.

qty jumlah quantity

343
KEY VALUE KETERANGAN

price harga

weight berat product (dalam satuan gram)

Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan response dalam
format JSON yang berisi informasi data cart yang telah di insert.

Uji Coba Method getCartPrice

Sekarang kita lanjutkan untuk melakukan uji coba mendapatkan total price atau harga yang
ada di dalam table carts sesuai dengan customer yang login.

Silahkan buka aplikasi Postman dan masukkan URL berikut ini


http://localhost:8000/api/web/carts/total_price kemudian untuk method-nya silahkan pilih
GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

344
Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data total price carts.

Uji Coba Method getCartWeight

Sama seperti sebelumnya, disini kita akan coba untuk mendapatkan total weight / berat
yang ada di dalam table carts sesuai dengan customer yang login.

Silahkan buka aplikasi Postman dan masukkan URL berikut ini


http://localhost:8000/api/web/carts/total_weight kemudian untuk method-nya silahkan pilih
GET.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Jika sudah, silahkan klik Send dan jika berhasil maka kita akan mendapatkan sebuah
response dalam format JSON yang berisi informasi data total weight carts.

345
Uji Coba Method removeCart

Terakhir kita akan lakukan uji coba untuk melakukan hapus data cart dari database,
silaahkan buka aplikasi Postman dan masukkan URL berikut ini
http://localhost:8000/api/web/carts/remove dan untuk method-nya silahkan pilih POST.

Selanjutnya silahkan klik tab Headers kemudian masukkan key dan value berikut ini :

KEY VALUE

Accept application/json

Content-Type application/json

Authorization Bearer <spasi> Token

Setelah itu, silahkan buka tab Body dan pilih form-data, kemudian masukkan key dan
value berikut ini :

KEY VALUE

cart_id 1

Jika sudah silahkan klik Send dan jika berhasil maka kita akan mendapatkan response dalam
format JSON yang berisi informasi data cart yang telah di hapus.

346
347
Installasi dan Konfigurasi Midtrans

Midtrans merupakan salah satu layanan payment gateway di Indonesia yang sangat
populer dan banyak digunakan karena kemudahan dalam integrasinya. Midtrans termasuk
perusahaan yang berada di dalam Gojek group.

Dengan menggunakan layanan payment gateway di dalam website yang kita buat, maka
akan mempermudah kita dalam menangani pembayaran yang dilakukan oleh customer
secara otomatis dan tanpa perlu melakukan konfirmasi pembayaran secara manual. Dan
enaknya lagi kita sudah disediakan layanan dengan nama SNAP PAY, yang akan
menampilkan beberapa metode pembayaran yang bisa di pilih oleh customer saat
melakukan checkout.

Cara Kerja Midtrans

348
CATATAN:

1. Merchant Backend adalah aplikasi Laravel yang sedang kita buat.


2. Snap Backend adalah backend service dari Midtrans.

Dari ilustrasi gambar alur kerja payment gatewat (Midtrans) di atas, kurang lebih detail
konsepnya seperti berikut ini :

1. Pengguna melakukan request checkout ke dalam Merchant Backend.


2. Merchant Backend membuat permintaan api ke Snap Backend untuk
mendapatkan SNAP_TOKEN.
3. Snap Backend menanggapi permintaan dan mengembalikan response ke Merchant
Backend dengan SNAP_TOKEN.
4. Merchant Backend membuat halaman HTML dan mengirimkannya kembali ke

349
browser.
5. Pengguna memverifikasi detailnya kemudian mengklik tombol bayar dengan kode
JavaScript snap.pay(SNAP_TOKEN). Pengguna kemudian mengisi detail
pembayaran dan mengklik tombol konfirmasi.
6. Snap Pay mengirimkan detail pembayaran ke Snap Backend.
7. Snap Backend memproses detailnya dan meresponse dengan status pembayaran.
kemudian Snap Pay memanggil kembali dengan status yang di dapatkan.
8. Snap Backend membuat notifikasi ke dalam Merchant Backend terkait status
biaya.

Langkah 1 - Daftar Akun di Midtrans


Pertama, kita akan melakukan pendaftaran akun di dalam website Midtrans, ini bertujuan
agar kita nanti mendapatkan Client Key dan Server Key yang digunakan untuk
integrasi.

Silahkan buka URL berikut ini dan lakukan pendaftaran dengan mengisi semua informasi
yang dibutuhkan : https://account.midtrans.com/register

Silahkan diisi semua informasi yang dibutuhkan. Dan untuk BUSINESS NAME bisa diisi
sembarang, karena masih untuk proses pembelajaran. Jika sudah berhasil register, maka
kita akan di arahkan kedalam halaman dashboard Midtrans.

Di dalam halaman dashboard Midtrans terdapat 2 environment, yaitu Sandbox dan


Production. Disini pastikan kita menggunakan yang jenis Sandbox, karena masih dalam
proses testing. Nanti, jika sudah benar-benar fix dan digunakan secara online, maka kita
bisa menggantinya dengan Production.

350
Setelah itu, sekarang klik menu SETTINGS > ACCESS KEYS, maka sekarang kita sudah
mendapatkan kredensial Merchant ID, Client Key dan Server Key.

Langkah 2 - Installassi Package Midtrans


Sekarang, kita lanjutkan untuk proses installasi package Midtrans di dalam project Laravel
yang kita kembangkan. Silahkan jalankan perintah di bawah ini di dalam terminal/CMD dan
pastikan menjalankannya di dalam project Laravel.

351
composer require midtrans/midtrans-php:2.3.2

Silahkan tunggu proses installasi sampai selesai. Dan pastikan harus terhubung dengan
internet, karena package akan diunduh secara online.

REPOSITORY MIDTRANS : https://github.com/Midtrans/midtrans-php

Langkah 3 - Konfigurasi Package Midtrans


Sekarang, kita lanjutkan untuk proses konfigurasi Midtrans di dalam project Laravel.
Silahkan buka file .env kemudian tambahkan kode berikut ini :

MIDTRANS_SERVERKEY=paste_server_key_midtrans_disini
MIDTRANS_CLIENTKEY=paste_client_key_midtrans_disini

Setelah itu kita akan tambahkan config, dimana config ini akan kita gunakan untuk
mendapatkan data dari konfigurasi Midtrans dari file .env di atas.

Silahkan buka file config/services.php, kemudian tambahkan array berikut ini :

//midtrans
'midtrans' => [
// Midtrans server key
'serverKey' => env('MIDTRANS_SERVERKEY'),
// Midtrans client key
'clientKey' => env('MIDTRANS_CLIENTKEY'),
// Isi false jika masih tahap development dan true jika sudah di
production, default false (development)
'isProduction' => env('MIDTRANS_IS_PRODUCTION', false),
'isSanitized' => env('MIDTRANS_IS_SANITIZED', true),
'is3ds' => env('MIDTRANS_IS_3DS', true),
]

Jika file config/services.php di tulis secara lengkap, maka kurang lebih seperti berikut
ini :

<?php

352
return [

/*
|-------------------------------------------------------------------
-------
| Third Party Services
|-------------------------------------------------------------------
-------
|
| This file is for storing the credentials for third party services
such
| as Mailgun, Postmark, AWS and more. This file provides the de
facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/

'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
],

'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],

'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'rajaongkir' => [
'key' => env('RAJAONGKIR_API_KEY')
],

//midtrans
'midtrans' => [
// Midtrans server key
'serverKey' => env('MIDTRANS_SERVERKEY'),
// Midtrans client key
'clientKey' => env('MIDTRANS_CLIENTKEY'),
// Isi false jika masih tahap development dan true jika sudah di
production, default false (development)
'isProduction' => env('MIDTRANS_IS_PRODUCTION', false),

353
'isSanitized' => env('MIDTRANS_IS_SANITIZED', true),
'is3ds' => env('MIDTRANS_IS_3DS', true),
]

];

Sekarang, kita sudah bisa menggunakan Midtrans di dalam project Laravel yang kita
kembangkan.

354
Membuat Restful API Checkout

Sekarang kita akan lanjutkan belajar bagaimana cara membuat Rest API yang fungsinya
untuk proses checkout, di dalam proses checkout nanti kita akan request ke dalam
Midtrans untuk melakukan generate token. Dan token tersebut yang akan digunakan untuk
menampilkan SNAP PAY dari Midtrans agar customer dapat memilih method pembayaran
yang diinginkan.

Langkah 1 - Membuat Resource Checkout


Sekarang kita akan membuat Resource terlebih dahulu, tujuannya agar nanti data yang di
tampilkan bisa menjadi format JSON dengan mudah dan baik.

Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Laravel-nya.

php artisan make:resource CheckoutResource

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file resource baru
dengan nama CheckoutResource.php di dalam folder app/Http/Resources. Silahkan
buka file tersebut dan ubah kode-nya menjadi seperti berikut ini :

355
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class CheckoutResource extends JsonResource


{
//public properti
public $status;
public $message;
/**
* __construct
*
* @param mixed $status
* @param mixed $message
* @param mixed $resource
* @return void
*/
public function __construct($status, $message, $resource)
{
parent::__construct($resource);
$this->status = $status;
$this->message = $message;
}

/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];
}
}

Di atas kita membuat 2 public properti, yang fungsinya nanti untuk menerima data yang
dikirim melalui controller.

356
public $status;
public $message;

Setelah itu kita membuat sebuah method dengan jenis __construct, dimana fungsi ini
akan pertama kali dijalankan ketika class tersebut di panggil. Dan di dalamnya kita parsing
3 variable :

$status - merupakan properti $status yang kita buat di atas, yang mana isinya
nanti akan berupa nilai boolean, yaitu true atau false.
$message - merupakan properti $message yang kita buat di atas, yang mana isinya
nanti akan berupa pesan/message tentang hasil yang dibuat.
$resource - merupakan data yang akan di transformasi, ini nanti akan berupa data
Model yang dikirim dari controller.

Setelah itu, di dalam method toArray kita melakukan return ke 3 variable di atas, kurang
lebih seperti berikut ini :

return [
'success' => $this->status,
'message' => $this->message,
'data' => $this->resource
];

Langkah 2 - Membuat Controller Checkout


kita lanjutkan untuk membuat controller baru yang nantinya digunakan untuk proses
checkout. Silahkan jalankan perintah berikut ini di dalam terminal/CMD :

php artisan make:controller Api\\Web\\CheckoutController

Jika berhasil, maka kita akan mendapatkan 1 file controller baru dengan nama
CheckoutController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Web;

357
use Midtrans\Snap;
use App\Models\Cart;
use App\Models\Invoice;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Http\Resources\CheckoutResource;

class CheckoutController extends Controller


{
/**
* __construct
*
* @return void
*/
public function __construct()
{
//set middleware
$this->middleware('auth:api_customer');

// Set midtrans configuration


\Midtrans\Config::$serverKey =
config('services.midtrans.serverKey');
\Midtrans\Config::$isProduction =
config('services.midtrans.isProduction');
\Midtrans\Config::$isSanitized =
config('services.midtrans.isSanitized');
\Midtrans\Config::$is3ds =
config('services.midtrans.is3ds');
}
/**
* store
*
* @param mixed $request
* @return void
*/
public function store(Request $request)
{

DB::transaction(function() use ($request) {

/**
* algorithm generate no invoice
*/
$length = 10;

358
$random = '';
for ($i = 0; $i < $length; $i++) {
$random .= rand(0, 1) ? rand(0, 9) : chr(rand(ord('a'),
ord('z')));
}

//generate no invoice
$no_invoice = 'INV-'.Str::upper($random);

//store invoice
$invoice = Invoice::create([
'invoice' => $no_invoice,
'customer_id' =>
auth()->guard('api_customer')->user()->id,
'courier' => $request->courier,
'courier_service' => $request->courier_service,
'courier_cost' => $request->courier_cost,
'weight' => $request->weight,
'name' => $request->name,
'phone' => $request->phone,
'city_id' => $request->city_id,
'province_id' => $request->province_id,
'address' => $request->address,
'grand_total' => $request->grand_total,
'status' => 'pending',
]);

//store orders by invoice


foreach (Cart::where('customer_id',
auth()->guard('api_customer')->user()->id)->get() as $cart) {

//insert product ke table order


$invoice->orders()->create([
'invoice_id' => $invoice->id,
'product_id' => $cart->product_id,
'qty' => $cart->qty,
'price' => $cart->price,
]);

}
//remove cart by customer
Cart::with('product')
->where('customer_id',
auth()->guard('api_customer')->user()->id)
->delete();

359
// Buat transaksi ke midtrans kemudian save snap tokennya.
$payload = [
'transaction_details' => [
'order_id' => $invoice->invoice,
'gross_amount' => $invoice->grand_total,
],
'customer_details' => [
'first_name' => $invoice->name,
'email' =>
auth()->guard('api_customer')->user()->email,
'phone' => $invoice->phone,
'shipping_address' => $invoice->address
]
];

//create snap token


$snapToken = Snap::getSnapToken($payload);

//update snap_token
$invoice->snap_token = $snapToken;
$invoice->save();

//make response "snap_token"


$this->response['snap_token'] = $snapToken;

});

//return with Api Resource


return New CheckoutResource(true, 'Checkout Successfully',
$this->response);

}
}

Di atas, pertama kita import Snap dari Midtrans, ini akan kita gunakan untuk proses
generate SNAP_TOKEN.

use Midtrans\Snap;

Setelah itu, kita juga melakukan import untuk Model Cart dan Invoice.

360
use App\Models\Cart;
use App\Models\Invoice;

Selanjutnya, kita juga melakukan import untuk helpers string dari Laravel, ini akan kita
gunakan nantinya untuk membuat generate nomor invoice.

use Illuminate\Support\Str;

Kemudian kita juga melakukan import untuk Http Request dari Laravel, ini digunakan untuk
menerima input yang dikirim melalui form, cookie, url dan lain-lain.

use Illuminate\Http\Request;

Dan kita juga import Facades DB, atau Query Builder, disini akan kita gunakan untuk
memanggil fungsi Database Transaction, dengan menggunakan fungsi ini, maka untuk
proses checkout akan menjadi lebih aman.

use Illuminate\Support\Facades\DB;

Dan agar data yang di masukkan dapat mengembalikan dalam format JSON, tentu kita akan
melakukan import CheckoutResource.

use App\Http\Resources\CheckoutResource;

Dan di dalam class CheckoutController kita menambahkan 2 method baru, yaitu :

1. function __construct - digunakan untuk check middleware


auth:api_customer dan berisi config Midtrans.
2. function store - digunakan untuk proses insert data ke invoice dan order.

function __construct

Method ini merupakan method yang pertama kali di akses jika sebuah class di jalankan, dan
disini kita manfaatkan untuk meletakkan middleware auth:api_customer untuk
memastikan jika class ini hanya bisa diakses jika customer sudah melakukan proses

361
otentikasi. Dan kita juga inisialisasi Midtrans yang berisi config yang sebelumnya sudah
kita buat, dengan tujuan agar bisa global digunakan di dalam class.

//set middleware
$this->middleware('auth:api_customer');

// Set midtrans configuration


\Midtrans\Config::$serverKey = config('services.midtrans.serverKey');
\Midtrans\Config::$isProduction =
config('services.midtrans.isProduction');
\Midtrans\Config::$isSanitized =
config('services.midtrans.isSanitized');
\Midtrans\Config::$is3ds = config('services.midtrans.is3ds');

function store

Method ini akan kita gunakan untuk melakukan proses checkout pembelian, dan disini kita
masukkan ke dalam fungsi Database Transaction, dengan fungsi ini maka akan
meminimalisir adanya kesalahan transaksi, jika transaksi gagal maka akan dirollback dan
jika transaksi berhasil maka baru dilakukan commit.

DB::transaction(function() use ($request) {

//...

Di dalam Database Transaction, pertama kita melakukan generate nomor invoice terlebih
dahulu. Kurang lebih seperti berikut ini :

362
/**
* algorithm generate no invoice
*/
$length = 10;
$random = '';
for ($i = 0; $i < $length; $i++) {
$random .= rand(0, 1) ? rand(0, 9) : chr(rand(ord('a'), ord('z')));
}

//generate no invoice
$no_invoice = 'INV-'.Str::upper($random);

Setelah itu, kita melakukan insert data invoice ke dalam database, kurang lebih seperti
berikut ini :

//store invoice
$invoice = Invoice::create([
'invoice' => $no_invoice,
'customer_id' => auth()->guard('api_customer')->user()->id,
'courier' => $request->courier,
'courier_service' => $request->courier_service,
'courier_cost' => $request->courier_cost,
'weight' => $request->weight,
'name' => $request->name,
'phone' => $request->phone,
'city_id' => $request->city_id,
'province_id' => $request->province_id,
'address' => $request->address,
'grand_total' => $request->grand_total,
'status' => 'pending',
]);

Dari proses insert data invoice di atas, kurang lebih penjelasannya seperti berikut ini :

ATTRIBUTE VALUE KETERANGAN

mengambil dari
variable
$no_invoice yang
invoice $no_invoice
isinya adalah hasil
generate kode
random

363
ATTRIBUTE VALUE KETERANGAN

mengambil ID
customer_id auth()->guard('api_customer')->user()->id customer yang
sedang login

courier $request->courier kurir pengiriman

courier_service $request->courier_service layanan kurir

courier_cost $request->courier_cost biaya ongkos kirim

weight $request->weight berap product

name $request->name nama customer

phone $request->phone telepon customer

city_id $request->city_id ID kota

province_id $request->province_id ID provinsi

address $request->address alamat lengkap

grand_total $request->grand_total grand total

status invoice secara


status pending
default.

Setelah itu, kita akan melakukan insert data lagi dari data carts ke dalam table orders.

//store orders by invoice


foreach (Cart::where('customer_id',
auth()->guard('api_customer')->user()->id)->get() as $cart) {

//insert product ke table order


$invoice->orders()->create([
'invoice_id' => $invoice->id,
'product_id' => $cart->product_id,
'qty' => $cart->qty,
'price' => $cart->price,
]);

Dan kita juga menghapus data carts yang terkait dengan customer.

364
//remove cart by customer
Cart::with('product')
->where('customer_id', auth()->guard('api_customer')->user()->id)
->delete();

Kemudian, kita akan membuat sebuah payload, yang isinya nanti akan kita kirim ke server
Midtrans untuk dibuatkan sebuah SNAP_TOKEN. Kurang lebih isinya seperti berikut ini :

// Buat transaksi ke midtrans kemudian save snap tokennya.


$payload = [
'transaction_details' => [
'order_id' => $invoice->invoice,
'gross_amount' => $invoice->grand_total,
],
'customer_details' => [
'first_name' => $invoice->name,
'email' =>
auth()->guard('api_customer')->user()->email,
'phone' => $invoice->phone,
'shipping_address' => $invoice->address
]
];

Di atas, kita memiliki 2 array di dalam object payload, yaitu :

transaction_details
customer_details

transaction_details

Akan berisi data order_id yang isinya adalah nomor invoice, kemudian untuk
gross_amount kita isi dengan data dari grand_total.

customer_details

Akan berisi data first_name yang akan diisi dengan name *customer yang sedang login.
Dan untuk email akan diisi dengan email dari customer yang sedang login.

Setelah berhasil membuat payload, sekarang kita lanjutkan untuk melakukan generate
SNAP_TOKEN berdasarkan data payload di atas. Kurang lebih seperti berikut ini :

365
//create snap token
$snapToken = Snap::getSnapToken($payload);

Setelah itu, kita akan melakukan update attribute snap_token dari data invoice yang
diinsert di atas dengan isi SNAP_TOKEN dari Midtrans.

//update snap_token
$invoice->snap_token = $snapToken;
$invoice->save();

Kemudian kita buat response dengan key snap_token yang isinya adalah SNAP_TOKEN
dari Midtrans.

$this->response['snap_token'] = $snapToken;

Terakhir, karena data sudah berhasil di commit oleh Database Transaction, maka kita akan
membuat sebuah response dengan isi snap_token di atas.

//return with Api Resource


return New CheckoutResource(true, 'Checkout Successfully',
$this->response);

Langkah 3 - Menambahkan Route


Kita lanjutkan untuk menambahkan route baru untuk checkout, silahkan buka file
routes/api.php kemudian tambahkan route berikut ini di dalam prefix web dan tepatnya
di bawah route /carts/remove.

//checkout route
Route::post('/checkout',
[App\Http\Controllers\Api\Web\CheckoutController::class, 'store'], ['as'
=> 'web']);

Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya

366
dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

Jika belum keluar list route seperti gambar di atas, maka silahkan jalankan perintah berikut
ini di dalam terminal/CMD :

php artisan route:cache

367
Membuat Restful API Notifikasi Handler Payment
Gateway

Ketika melakukan pembayaran di dalam Midtrans, maka Midtrans akan mengirim sebuah
webhook yang berisi informasi pembayaran ke dalam website kita. Disini kita perlu
membuat sebuah notifikasi handler yang berfungsi untuk menerima sebuah reqesut
webhook yang kirim dari Midtrans tersebut

Di dalam notifikasi handler kita bisa melakukan beberapa kondisi yang bisa disesuaikan
dengan keinginan, misalnya saja jika pembayaran sukses, makan akan melakukan update
stock, update status pembayaran dan lain-lain.

Langkah 1 - Membuat Controller Notification Handler


Silahkan jalankan perintah berikut ini di dalam terminal/CMD untuk membuat controller
baru.

php artisan make:controller Api\\Web\\NotificationHandlerController

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 1 file controller baru
dengan nama NotificationHandlerController.php yang berada di dalam folder
app/Http/Controllers/Api/Web. Silahkan buka file tersebut dan ubah semua kode-nya
menjadi seperti berikut ini :

<?php

namespace App\Http\Controllers\Api\Web;

use App\Models\Invoice;
use App\Models\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class NotificationHandlerController extends Controller


{
/**
* index
*
* @param mixed $request
* @return void

368
*/
public function index(Request $request)
{
$payload = $request->getContent();
$notification = json_decode($payload);
$validSignatureKey = hash("sha512", $notification->order_id .
$notification->status_code . $notification->gross_amount .
config('services.midtrans.serverKey'));

if ($notification->signature_key != $validSignatureKey) {
return response(['message' => 'Invalid signature'], 403);
}

$transaction = $notification->transaction_status;
$type = $notification->payment_type;
$orderId = $notification->order_id;

//data tranaction
$data_transaction = Invoice::where('invoice',
$orderId)->first();

if ($transaction == 'capture') {

// For credit card transaction, we need to check whether


transaction is challenge by FDS or not
if ($type == 'credit_card') {

//nothing

} elseif ($transaction == 'settlement') {

/**
* update invoice to success
*/
$data_transaction->update([
'status' => 'success'
]);

//update stock product


foreach($data_transaction->orders()->get() as $order) {

$product =
Product::whereId($order->product_id)->first();
$product->update([

369
'stock' => $product->stock - $order->qty
]);
}

} elseif($transaction == 'pending'){

/**
* update invoice to pending
*/
$data_transaction->update([
'status' => 'pending'
]);

} elseif ($transaction == 'deny') {

/**
* update invoice to failed
*/
$data_transaction->update([
'status' => 'failed'
]);

} elseif ($transaction == 'expire') {

/**
* update invoice to expired
*/
$data_transaction->update([
'status' => 'expired'
]);

} elseif ($transaction == 'cancel') {

/**
* update invoice to failed
*/
$data_transaction->update([
'status' => 'failed'
]);

}
}

370
}

Di atas kita melakukan import Model Invoice dan Product, karena kita akan
menggunakan kedua model tersebut untuk manipulasi data ke database.

use App\Models\Invoice;
use App\Models\Product;

Kemudian kita juga melakukan import untuk Http Request dari Laravel, ini digunakan untuk
menerima input yang dikirim melalui form, cookie, url dan lain-lain.

use Illuminate\Http\Request;

Dan di dalam class NotificationHandlerController kita menambahkan method baru


dengan nama index, method inilah yang akan bertugas menerima request webhook yang
dikirim dari Midtrans.

Di dalam method ini, kita akan melakukan beberapa kondisi, pertama kita akan
mendapatkan content JSON yang dikirim melalui Midtrans dengan kode seperti berikut ini :

$payload = $request->getContent();
$notification = json_decode($payload);

Kemudian kita membuat variable yang isinya adalah data signatur yang akan kita cocokan
dengan variable $notification di atas.

$validSignatureKey = hash("sha512", $notification->order_id .


$notification->status_code . $notification->gross_amount .
config('services.midtrans.serverKey'));

Jika nilai dari $notification dan $validSignaturKey tidak sama, maka akan
melakukan return dengan status code 403.

371
if ($notification->signature_key != $validSignatureKey) {
return response(['message' => 'Invalid signature'], 403);
}

Jika data signatur ditemukan, maka kita akan melakukan query ke dalam table invoices
berdasarkan data order ID/invoice yang dikirim dari Midtrans.

$transaction = $notification->transaction_status;
$type = $notification->payment_type;
$orderId = $notification->order_id;

//data tranaction
$data_transaction = Invoice::where('invoice', $orderId)->first();

Setelah itu, kita akan melakukan pengecekan kondisi pembayaran, kurang lebih kode-nya
seperti berikut ini :

if ($transaction == 'capture') {

// For credit card transaction, we need to check whether transaction


is challenge by FDS or not
if ($type == 'credit_card') {

//nothing

} elseif ($transaction == 'settlement') {

/**
* update invoice to success
*/
$data_transaction->update([
'status' => 'success'
]);

//update stock product


foreach($data_transaction->orders()->get() as $order) {

$product = Product::whereId($order->product_id)->first();
$product->update([

372
'stock' => $product->stock - $order->qty
]);
}

} elseif($transaction == 'pending'){

/**
* update invoice to pending
*/
$data_transaction->update([
'status' => 'pending'
]);

} elseif ($transaction == 'deny') {

/**
* update invoice to failed
*/
$data_transaction->update([
'status' => 'failed'
]);

} elseif ($transaction == 'expire') {

/**
* update invoice to expired
*/
$data_transaction->update([
'status' => 'expired'
]);

} elseif ($transaction == 'cancel') {

/**
* update invoice to failed
*/
$data_transaction->update([
'status' => 'failed'
]);

373
}

Di atas jika nilai $transaction bernilai settlement, maka pembayaran berhasil


dilakukan dan di dalamnya kita melakukan proses update stock di dalam data product.

/**
* update invoice to success
*/
$data_transaction->update([
'status' => 'success'
]);

//update stock product


foreach($data_transaction->orders()->get() as $order) {

$product = Product::whereId($order->product_id)->first();
$product->update([
'stock' => $product->stock - $order->qty
]);
}

Langkah 2 - Menambahkan Route Notification Handler


Kita lanjutkan untuk menambahkan route baru untuk notification handler, silahkan buka file
routes/api.php kemudian tambahkan route berikut ini di dalam prefix web dan tepatnya
di bawah route /checkout.

//notification handler route


Route::post('/notification',
[App\Http\Controllers\Api\Web\NotificationHandlerController::class,
'index'], ['as' => 'web']);

Untuk memastikan penambahan route kita di atas berhasil, kita bisa memverivikasinya
dengan menjalankan perintah berikut ini di dalam terminal/CMD :

php artisan route:list

374
375
Apa itu Nuxt.js ?

Nuxt.js merupakan web application framework berbasis Vue.js, Node.js, Webpack dan
Babel.js yang bersifat open source dan di rancang untuk pembuatan sebuah aplikasi atau
website Universal yang bisa di render dari sisi server atau menjadi sebuah website statis.

Jika kita mengembangkan sebuah aplikasi maupun website berbasis Vue.js yang
menggunakan SPA atau Single Page Application tentu saja kita akan kesusahan dalam
mengatasi permasalah pada SEO (search engine optimization), oleh sebab itu kita bisa
menggunakan Nuxt.js untuk mengatasi permasalahan ini.

Sejarah Nuxt.js
Nuxt.js pertama kali dikembangkan oleh 2 orang dari negara Prancis, yaitu Alexandre
Chopin dan Sébastien Chopin, yang mana dirilis perdana pada tanggal 26 Oktober 2016.
Sekarang Nuxt.js sudah dikembangkan oleh banyak developer handal diseluruh dunia.

Kenapa Harus Nuxt.js ?

MODULAR

Nuxt.js didasarkan pada arsitektur modular yang efektif. Kita dapat memilih lebih dari
50 modul untuk membuat pengembangan aplikasi yang lebih cepat dan lebih mudah.
Kita tidak harus "reinventing the wheel" untuk mendapatkan manfaat PWA,
menambahkan Google Analytics ke halaman website atau menghasilkan peta
situs/sitemap.

RENDERING

Dengan menggunakan Nuxt.js maka kita bisa memilih mode apa yang akan kita
gunakan di dalam aplikasi atau website yang sedang dikembangkan. Mode paling
populer di Nuxt.js adalah SSR atau juga bisa disebut dengan "Universal", dimana
aplikasi yang dikembangkan akan di render disisi server menggunakan Node.js.

Kita juga bisa memilih menggunakan Mode Statis, yang artinya aplikasi atau website
kita tidak memerlukan server tetapi masih memiliki manfaat SEO karena Nuxt akan
melakukan pra-render terhadap semua halaman website dan menyertakan HTML
yang diperlukan.

STRUKTUR FOLDER

376
Secara default aplikasi Nuxt.js dimaksudkan untuk memberikan titik awal yang bagus
untuk aplikasi kecil dan besar. Kita bebas untuk mengatur aplikasi kita sesuai
kebutuhan dan dapat membuat direktori lain jika kita membutuhkannya.

CATATAN ! : untuk mempelajari Nuxt.js setidaknya kita harus mengetahui sedikit tentang
Vue.js

377
Installasi Nuxt.js

Di dalam Nuxt.js kita memiliki 2 cara untuk memulai membuat sebuah project baru, yaitu
menggunakan perintah create-nuxt-app atau membuatnya secara manual.

CATATAN! : tidak wajib mengikuti langkah-langkah di bawah ini. Hanya sebagai gambaran
bagaimana cara membuat project baru di Nuxt.js

Cara 1 - Menggunakan Perintah create-nuxt-app

Untuk memulai dengan cepat, kita dapat menggunakan perintah create-nuxt-app. Pastikan
kita telah menginstal npx (npx tersedia secara default sejak npm 5.2.0) atau npm v6.1 atau
yarn. Berikut ini beberapa cara menggunakan create-nuxt-app untuk memulai membuat
project baru.

YARN

yarn create nuxt-app <project-name>

NPX

npx create-nuxt-app <project-name>

NPM

npm init nuxt-app <project-name>

Silahkan ganti <project-name> dengan nama project yang kita inginkan.

Ini akan menanyakan beberapa pertanyaan (nama, opsi Nuxt, kerangka UI, TypeScript,
linter, kerangka pengujian, dll.), Ketika dijawab, itu akan menginstal semua dependensi.
Langkah selanjutnya adalah menuju ke folder project dan menjalankannya:

378
cd <project-name>

npm run dev

Aplikasi sekarang berjalan di http://localhost:3000.

Cara 2 - Mulai dari awal/manual


Membuat project Nuxt.js dari awal hanya membutuhkan satu file dan satu direktori. Dalam
contoh khusus ini, kita akan menggunakan terminal untuk membuat direktori dan file.

Langkah 1 - Membuat Project Baru

Untuk memulai, buat direktori kosong dengan nama project yang kita inginkan dan masuk
ke dalamnya:

mkdir <project-name>

cd <project-name>

Silahkan ganti <project-name> dengan nama project yang kita inginkan.

Kemudian buat file bernama package.json:

touch package.json

Buka file package.json di text editor dan isi dengan konten JSON ini:

379
{
"name": "my-app",
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"generate": "nuxt generate",
"start": "nuxt start"
}
}

Apa itu file package.json?

File package.json seperti kartu ID project kita. Jika kita tidak tahu apa itu file
package.json, kita bisa membaca sekilas di dokumentasi npm.

Langkah 2 - Install nuxt

Setelah package.json dibuat, tambahkan nuxt ke project kita melalui npm atau yarn
seperti dibawah ini:

npm install nuxt

Perintah ini akan ditambahkan nuxt sebagai dependency pada project kita dan akan
ditambahkan ke package.json kita secara otomatis. Direktori node_modules juga akan
dibuat yang mana semua paket yang terinstal dan dependensi mereka disimpan.

Langkah 3 - Buat halaman pertama

Nuxt.js mengubah setiap *.vue file di dalam direktori pages sebagai route untuk aplikasi.

Buat direktori pages di project kita:

mkdir pages

Lalu buat file index.vue di dalam direktori pages:

380
touch pages/index.vue

Halaman ini penting untuk dipanggil index.vue karena ini akan menjadi halaman default
yang ditampilkan Nuxt saat aplikasi dijalankan. Ini adalah beranda dan harus disebut index.

Buka file index.vue di text editor dan tambahkan konten berikut:

<template>
<h1>Hello world!</h1>
</template>

Langkah 4 - Menjalankan project

Jalankan project kita dengan mengetik perintah berikut di bawah ini di terminal/CMD:

npm run dev

Aplikasi sekarang berjalan di http://localhost:3000.

Di atas kita telah belajar bagaimana memulai membuat project Nuxt baru dengan 2 cara,
yaitu dengan perintah create-nuxt-app dan membuatnya secara manual.
Di dalam buku ini nanti kita akan praktek menggunakan yang cara create-nuxt-app,
karena lebih simple dan mudah.

381
Memahami Struktur Folder di Nuxt.js

Setelah sebelumnya kita sudah belajar bagaimana cara installasi di Nuxt Js, maka sekarang
kita akan belajar memahami struktur folder yang ada di dalam Nuxt.js. Berikut ini penjelasan
tentang folder-folder yang ada di dalam Nuxt.js setelah kita berhasil melakukan installasi.

.nuxt

Direktori .nuxt bisa disebut juga sebagai direktori build. Direktori ini dibuat secara
dinamis dan disembunyikan secara default. Di dalam direktori ini, kita dapat
menemukan file yang dibuat secara otomatis saat menjalankan nuxt dev atau
menggunakan artefak build ketika menjalankan nuxt build. Memodifikasi file-file ini
sangat bagus untuk keperluan debugging, tetapi ingat bahwa itu adalah file yang
dihasilkan setelah kita menjalankan perintah dev atau build. Jadi apapun yang
disimpan di .nuxt akan dibuat ulang.

Direktori .nuxt tidak boleh dimasukkan ke sistem version control (GIT) kita dan
harus diabaikan melalui file .gitignore karena akan dibuat secara otomatis saat
menjalankan nuxt dev atau nuxt build.

assets

Direktori assets berisi asset kita yang belum terkompilasi seperti berkas Style/Sass,
gambar atau font.

382
components

Direktori component berisi komponen Vue.js kita. Komponen adalah bagian-bagian


berbeda dari halaman Anda dan dapat digunakan kembali serta diimpor ke pages,
layouts, dan bahkan components lainnya.

layouts

Layouts adalah bagian yang sangatlah membantu ketika kita ingin mengubah
tampilan dan nuansa pada aplikasi Nuxt.js, baik memasukkan sidebar maupun
memiliki layout berbeda untuk perangkat mobile dan desktop.

Direktori ini tidak dapat diubah namanya tanpa konfigurasi khusus.

middleware

Direktori middleware berisi aplikasi middleware kita. Middleware memungkinkan kita


menentukan fungsi kustom yang dapat dijalankan sebelum merender halaman atau
sekelompok halaman (layout).

Shared Middleware harus ditempatkan di direktori middleware/. Nama berkas akan


menjadi nama middleware (middleware/auth.js akan menjadi middleware auth).
Kita juga dapat mendefinisikan middleware khusus halaman dengan menggunakan
fungsi secara langsung, lihat middleware anonim.

node_modules

Direktori ini akan berisi library atau package yang kita install di dalam aplikasi atau
website menggunakan NPM atau Yarn.

pages

Direktori pages berisikan semua halaman dan route dari aplikasi kita. Nuxt.js
membaca semua file .vue dalam direktori ini dan secara otomatis membuat
konfigurasi route di dalam aplikasi atau website kita.

plugins

Direktori plugins mengandung plugins Javascript yang ingin kita jalankan sebelum
memulai aplikasi Vue.js. Lokasi ini merupakan tempat untuk menambahkan plugins
Vue dan untuk memasukkan fungsi-fungsi atau konstanta-konstanta (constants).

383
Setiap kali kita membutuhkan penggunaan Vue.use(), kita harus membuat berkas
dalam direktori plugins/ dan menambahkan route ke plugins dalam
nuxt.config.js.

static

Direktori static langsung dipetakan ke dasar (root) server dan mengandung file-file
yang kemungkinan besar tidak diubah-ubah. File-file yang berada dalam direktori
akan secara otomatis dijalankan oleh Nuxt dan dapat diakses melalui URL di project
kita. Contohnya seperti berikut ini :

/static/robots.txt akan tersedia di


http://localhost:3000/robots.txt
/static/favicon.ico akan tersedia di localhost:3000/favicon.ico

store

Direktori store mengandung file Vuex Store. Vuex Store sudah termasuk dalam
Nuxt.js, tetapi dimatikan secara bawaan. Membuat file index.js dalam direktori ini
akan mengaktifkan Vuex store tersebut secara otomatis.

nuxt.config

Secara bawaan, Nuxt.js dikonfigurasi untuk mencakup sebagian besar kasus


penggunaan. Konfigurasi bawaan ini dapat ditimpa dengan file nuxt.config.js.

Di atas merupakan penjelasan singkat tentang fungsi-fungsi dari folder dan file yang ada di
dalam Nuxt.js.

384
Rendering

Di dalam Nuxt.js terdapat 2 jenis rendering yang bisa kita gunakan, yaitu SSR atau biasa
disebut Server Side Rendering dan Static Site.

Server Sider Rendering

situs dirender di server setiap kali pengguna meminta halaman, oleh karena itu
diperlukan server untuk dapat melayani halaman pada setiap permintaan.

Static sites

sangat mirip dengan aplikasi yang dirender sisi server dengan perbedaan utama
adalah bahwa situs statis dirender pada waktu dimuat pertama kali, oleh karena itu
tidak diperlukan server. Berpindah dari satu halaman ke halaman lain berada di sisi
klien.

Untuk mengaktifkan dari salah satu rendering di atas, kita bisa masuk di dalam file
nuxt.config.js. Kemudian kita bisa atur jenis yang ingin kita gunakan.

SSR Server Side Rendering

export default {
ssr: true
}

Static Site

export default {
ssr: false
}

385
Target Deployment

Di dalam Nuxt.js kita juga bisa melakukan konfigurasi untuk target deployment dari aplikasi
atau website yang kita bangun, disini kita bisa membuat 2 pilihan, yaitu dengan Hosting
Statis dan Server Hosing.

Hosting Statis

Untuk hosting statis (hosting tanpa pe-render-an di sisi server), kita perlu
menambahkan properti target dengan nilai static pada file nuxt.config.js.

export default {
target: 'static'
}

Server Hosting

Untuk hosting server, properti target harus bernilai server (ini merupakan nilai
bawaan dari properti target), ini biasanya digunakan jika aplikasi atau website
menggunakan mode SSR atau Server Side Rendering.

export default {
target: 'server'
}

Dari sini bisa kita simpulkan, jika ingin melakukan deploy aplikasi atau website yang tidak
membutuhkan SEO, kita bisa menggunakan target static tapi jika ingin aplikasi yang
dibuat lebih SEO friendly, maka kita bisa gunakan target server.

386
Routing

Nuxt.js akan secara otomatis membuat konfigurasi vue-router berdasarkan turunan file di
dalam direktori pages. Ketika kita membuat file .vue di direktori pages, maka kita akan
memiliki routing paling basic tanpa tambahan konfigurasi lainnya.

Terkadang kita mungkin perlu membuat route dinamis atau route nested atau route yang
perlu konfigurasi lebih lanjut. Pada bagian ini kita akan belajar untuk mengoptimalkan
konfigurasi router.

Basic Routes
Dari sebuah direktori seperti ini :

pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue

Maka akan otomatis di generate menjadi konfigurasi seperti berikut ini :

387
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'user',
path: '/user',
component: 'pages/user/index.vue'
},
{
name: 'user-one',
path: '/user/one',
component: 'pages/user/one.vue'
}
]
}

Dynamic Routes
Untuk membuat route dinamis kita perlu membuat file yang diawali dengan nama (_)
sebelum .vue atau sebelum nama dari direktori. Kita juga dapat memberikan nama file
atau direktori apapun yang kita mau namun kita harus memberikan awalan dengan garis
bawah.

Dari sebuah direktori seperti ini :

pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue

Maka akan otomatis di generate menjadi konfigurasi seperti berikut ini :

388
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'users-id',
path: '/users/:id?',
component: 'pages/users/_id.vue'
},
{
name: 'slug',
path: '/:slug',
component: 'pages/_slug/index.vue'
},
{
name: 'slug-comments',
path: '/:slug/comments',
component: 'pages/_slug/comments.vue'
}
]
}

Dengan konsep seperti ini maka kita tidak perlu sudah payah lagi dalam menangani sebuah
routing di dalam aplikasi berbasis Vue.js, karena semua sudah otomatis tergenerate sesuai
dengan file-file yang ada di dalam folder pages.

389
Meta Tags dan SEO

Nuxt.js memberikan kita 3 cara berbeda untuk menambahkan data meta ke aplikasi Anda:

1. Secara global menggunakan file nuxt.config.js.


2. Secara lokal menggunakan properti head sebagai objek.
3. Secara lokal menggunakan properti head sebagai fungsi yang memungkinkan kita
untuk mengakses data dan properti-properti computed.

Secara Global
Nuxt.js memungkinkan kita untuk mendefinisikan semua tag <meta> bawaan untuk aplikasi
di dalam file nuxt.config.js dengan menggunakan properti head. Ini sangat berguna
untuk menambahkan tag title dan description bawaan untuk tujuan SEO atau untuk
menyetel viewport atau untuk menambahkan favicon.

export default {
head: {
title: 'Judul situs web saya',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: 'Deskripsi untuk website saya'
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
}
}

Hal ini akan membuat semua halaman aplikasi atau website memiliki judul dan deskripsi
yang sama

Secara Lokal
Kita juga bisa menambahkan judul dan meta untuk setiap halaman menggunakan method
head di dalam tag script pada setiap komponen page.

390
<script>
export default {
head: {
title: 'Ini halaman beranda',
meta: [
{
hid: 'description',
name: 'description',
content: 'Deskripsi untuk halaman beranda'
}
],
}
}
</script>

Contoh di atas menggunakan head sebagai objek untuk menyetel judul dan deskripsi untuk
halaman atau component itu saja.

391
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
data() {
return {
title: 'Halaman beranda'
}
},
head() {
return {
title: this.title,
meta: [
{
hid: 'description',
name: 'description',
content: 'Deskripsi untuk halaman beranda'
}
]
}
}
}
</script>

Contoh di atas menggunakan head sebagai fungsi untuk menyetel judul dan deskripsi hanya
untuk halaman atau component itu saja. Dan dengan menggunakan head sebagai fungsi,
kita bisa mengakses data dan properti-properti computed.

392
Data Fetching

Di dalam Nuxt.js kita memiliki 2 cara untuk mendapatkan data dari sebuah API atau
application programming interface. Disini kita dapat menggunakan metode fetch atau
metode asyncData.

Menggunakan fetch Hook


Hook fetch di Nuxt.js dipanggil setelah instance komponen dibuat pada sisi server: this
juga masih tersedia di dalamnya. Seperti contoh pada kode berikut ini :

export default {
async fetch() {
console.log(this)
}
}

Hook fetch harus mengembalikan sebuah promise (baik secara eksplisit atau implisit
menggunakan async / await) yang akan diselesaikan saat :

Di server sebelum halaman awal dirender.


Pada klien beberapa saat setelah komponen dipasang atau mounted.

Hal tersebut mengekspos $fetchState pada tingkatan komponen dengan properti sebagai
berikut ini:

pending merupakan sebuah Boolean, mengizinkan kita untuk menampilkan


placeholder ketika fetch dipanggil pada sisi klien
error dapat berupa antara null atau Error dan mengizinkan kita untuk
menampilkan pesan error
timestamp merupakan waktu dari terakhir kali fetching, berguna untuk menyimpan
cache dengan keep-alive

Kita juga memiliki akses ke this.$fetch(), ini berguna jika kita ingin memanggil hook
fetch di dalam komponen. Contohnya seperti berikut ini :

393
<template>
<p v-if="$fetchState.pending">Fetching mountains...</p>
<p v-else-if="$fetchState.error">An error occurred :(</p>
<div v-else>
<h1>Nuxt Mountains</h1>
<ul>
<li v-for="mountain of mountains">{{ mountain.title }}</li>
</ul>
<button @click="$fetch">Refresh</button>
</div>
</template>

<script>
export default {
data() {
return {
mountains: []
}
},
async fetch() {
this.mountains = await fetch(
'https://api.nuxtjs.dev/mountains'
).then(res => res.json())
}
}
</script>

Caching

Kita dapat menggunakan direktif keep-alive di komponen <nuxt/> dan <nuxt-child/>


untuk menyimpan pemanggilan fetch pada halaman yang kita telah kunjungi:

<template>
<nuxt keep-alive />
</template>

Kita juga dapat melakukan spesifikasi pada props yang dikirimkan ke <keep-alive>
dengan mengirimkan keep-alive-props ke komponen <nuxt>.

394
<nuxt keep-alive :keep-alive-props="{ max: 10 }" />

Menggunakan activated Hook


Nuxt akan secara langsung mengisi this.$fetchState.timestamp (timestamp) dari
terakhir kali fetch dipanggil (termasuk render sisi server). Kita juga dapat menggunakan
properti ini dikombinasikan dengan hook activated untuk menambahkan cache selama 30
detik ke fetch:

<template> ... </template>

<script>
export default {
data() {
return {
posts: []
}
},
activated() {
// Call fetch again if last fetch more than 30 sec ago
if (this.$fetchState.timestamp <= Date.now() - 30000) {
this.$fetch()
}
},
async fetch() {
this.posts = await fetch('https://api.nuxtjs.dev/posts').then(res
=>
res.json()
)
}
}
</script>

Melakukan navigasi ke halaman yang sama tidak akan memanggil fetch kembali jika
terakhir memanggil fetch masih kurang dari 30 detik yang lalu.

Menggunakan AsyncData Hook

395
asyncData hanya dapat digunakan pada halaman (pages) dan kita tidak memiliki akses ke
this di dalam hook tersebut.

asyncData adalah hook lain untuk fetching data universal. Tidak seperti hook fetch,
yang mengharuskan kita untuk mengatur sebuah properti di dalam component untuk
menyimpan async state, sederhananya di dalam asyncData akan mengembalikan nilai
state langsung di dalam component. Berikut ini contohnya :

<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.description }}</p>
</div>
</template>

<script>
export default {
async asyncData({ params, $http }) {
const post = await
$http.$get(`https://api.nuxtjs.dev/posts/${params.id}`)
return { post }
}
}
</script>

Tidak seperti fetch, promise yang dikembalikan oleh hook asyncData diselesaikan selama
transisi route. Nuxt akan menunggu hook asyncData selesai sebelum menavigasi ke
halaman berikutnya atau menampilkan pesan kesalahan.

396
Berkenalan Dengan Vuex

Ketika kita mengembangkan aplikasi dengan Vue Js, terkadang kita akan kesulitan saat ingin
mengelola data yang berulang-ulang di dalam semua component. Karena kita akan
mendeklarasikan berulang-ulang juga untuk mengelola data tersebut. Dengan begitu, maka
aplikasi yang kita kembangkan menjadi tidak efisien, karena terlalu banyak melakukan hal
yang sama. Dengan permasalahan tersebut, maka Vuex bisa menjadi jawaban atas semua
masalah di atas. Jadi apa itu Vuex ?

Apa itu Vuex ?


Menurut situs resminya di https://vuex.vuejs.org/, kurang lebih seperti berikut ini :

Vuex is a state management pattern + library for Vue.js applications. It serves as a


centralized store for all the components in an application, with rules ensuring that the state
can only be mutated in a predictable fashion.

Jadi secara sederhana, Vuex merupakan state manajemen pattern dan library untuk Vue Js
yang digunakan untuk membuat store secara centralized untuk semua komponen yang ada
di dalam aplikasi.

Kapan Saya Harus Menggunakannya?


Vuex digunakan ketika aplikasi yang kita kembangkan menjadi semakin besar dan sulitnya
mengelola data, dengan demikian menggunakan Vuex merupakan cara terbaik untuk
masalah ini. Tapi jika aplikasi yang kita kembangkan masih bersekala kecil, maka kita tidak
perlu menggunakan Vuex, karena masih bisa ditangani oleh beberapa props dan emit.

Konsep di dalam Vuex

397
Inti dari Vuex adalah sebuah store yang akan digunakan sebagai kontainer dari semua
state yang ada di dalam aplikasi. Di dalam store memilki beberapa bagian-bagian dengan
fungsinya masing-masing, diantaranya adalah :

State
State merupakan sebuah objek yang digunakan untuk mendefinisikan dan menyimpan
semua data yang ada di dalam aplikasi secara reactive. Berikut ini contoh menggunakan
state untuk menyimpan sebuah data :

//state
export const state = () => ({
//state "name"
name: 'Fika Ridaul Maulayya',
//state "gender"
gender: 'male'

})

398
Sekarang, kita dapat mengakses state name dan gender langsung secara global di dalam
semua component aplikasi. Contoh menggunakan state di dalam component kita bisa
seperti berikut ini :

computed: {
name() {
return this.$store.state.name // state "name"
}
gender() {
return this.$store.state.gender // state "gender"
}
}

Dan untuk menampilkan di template, kita bisa menggunakan kode seperti berikut ini :

<h1>Nama Lengkap: {{ name }}</h1> <!-- output "Fika Ridaul Maulayya" -->

<h1>Jenis kelamin: {{ gender }}</h1> <!-- output "male" -->

Getters
Getters digunakan untuk mendapatkan data dari state, lalu apa bedanya dengan
mengakses state langsung ? bukankah kita bisa langsung mengambil data dari state seperti
yang di lakukan di dalam contoh di atas ?. Jadi dengan menggunakan Getters kita bisa
mengolah data yang ada di dalam state sebelum digunakan di dalam component. Kurang
lebih contohnya seperti berikut ini :

399
//state
export const state = () => ({
//state "todos"
todos: [
{
title: 'Task 1',
completed: false
},
{
title: 'Task 2',
completed: true
},
{
title: 'Task 3',
completed: false
}
]

})

//getters
export const getters = () => ({
//getter get length todos status completed (true)
doneTodos(state){
return state.todos.filter(function(item){
return item.completed == true
}).length
},
//getter get length todos status pending (false)
pendingTodos(state){
return state.todos.filter(function(item){
return item.completed == false
}).length
},

})

Sekarang, kita dapat mengakses Getters doneTodos dan pendingTodos langsung secara
global di dalam semua component aplikasi. Contoh menggunakan Getters di dalam
component kita bisa seperti berikut ini :

400
computed: {
done() {
return this.$store.getters.doneTodos // getters "doneTodos"
}
pending() {
return this.$store.getters.pendingTodos // getters "pendingTodos"
}
}

Dan untuk menampilkan di template, kita bisa menggunakan kode seperti berikut ini :

<h1>Done: {{ done }}</h1> <!-- output "1" -->

<h1>Pending: {{ pending }}</h1> <!-- output "2" -->

Mutations
Mutations merupakan salah satu store yang digunakan untuk melakukan perubahan nilai
padat state secara reactive. Jadi mutation ini bisa dibilang mirip event yang ada di dalam
store. Mutation terdiiri dari sebuah string nama dan handlernya.

Kurang lebih contohnya seperti berikut ini :

401
//state
export const state = () => ({
//state "count"
count: 1

})

//mutations
export const mutations = () => ({
increment (state) {
// mutate state
state.count++
}

})

Di atas, kita punya sebuah mutations dengan nama increment yang memiliki handler,
dimana jika di panggil maka akan menambah nilai dari state count sebanyak 1. Kita bisa
memanggil mutations di dalam component seperti berikut ini :

this.$store.commit('increment')

Actions
Actions bisa di ibaratkan sama dengan mutations, tapi tentu saja ada perbedaannya,
jadi konsepnya adalah actions akan memanggil mutation dan mutation yang akan
mengubah state-nya. Dan actions ini bersifat asynchronous. Kurang lebih alurnya seperti
berikut ini :

action -> mutation -> state

KETERANGAN: pengguna melakukan sebuah action, kemudian action memanggil mutation


untuk melakukan perubahan data di dalam state.

402
//state
export const state = () => ({
//state "count"
count: 1

})

//mutations
export const mutations = () => ({
//mutation "SET_COUNT"
SET_COUNT(state) {
state.count++
}

})

//actions
export const actions = () => ({
increment({ commit }) {
//commit ke mutation SET_COUNT
commit.commit('SET_COUNT')
}

})

Di atas, kita mempunyai actions yang bernama increment, yang mana di dalamnya
melakukan sebuah commit ke dalam mutation yang bernama SET_COUNT dan di dalam
SET_COUNT kita menambah nilai dari state count sebanyak 1.

Dan untuk menggunakan/memanggil action di dalam global component, kita bisa


menggunakan kode seperti berikut ini :

this.$store.dispatch('increment')

403
Membuat Project Baru di Nuxt.js

Sekarang kita akan mulai untuk membuat project baru dengan Nuxt.js, yang mana project
ini akan dijadikan sebagai frontend dari website toko online yang sedang kita kembangkan.
Fungsinya yaitu mengkonsumsi atau menggunakan Rest API yang sebelumnya sudah kita
buat di dalam Laravel.

Langkah 1 - Membuat Project di Nuxt.js


INFORMASI ! : pastikan komputer yang digunakan sudah terinstall Node.js dan NPM,
karena Nuxt.js memebutuhkan Node.js untuk proses installasi dan menjalankan
projectnya.

Sekarang kita bisa memembuat project baru di Nuxt.js dengan menggunakan NPX (npx
tersedia secara default sejak npm 5.2.0). Silahkan masuk ke dalam folder dimana kita akan
menyimpan project-nya. Dan jalankan perintah berikut ini di dalam terminal/CMD :

npx create-nuxt-app@3.7.0 nuxt-ecommerce

Perintah di atas digunakan untuk membuat project Nuxt.js baru dengan nama nuxt-
ecommerce dengan versi 3.7.0. Jika keluar pilihan pertanyaan, bisa ikuti langkah-
langkanya seperti berikut ini :

PERINTAH AKSI

Project name: Tekan ENTER

Programming language: Pilih JavaScript dan ENTER

Package manager: Pilih Npm dan ENTER

UI framework: Pilih Bootstrap Vue dan ENTER

Nuxt.js modules: Pilih Axios dengan tekan SPACE, kemudian ENTER

Linting tools: Tekan ENTER

Testing framework: Pilih None dan ENTER

Rendering mode: Pilih Universal (SSR / SSG) dan ENTER

Deployment target: Pilih Server (Node.js hosting) dan ENTER

404
PERINTAH AKSI

Development tools: Pilih jsconfig.json dengan tekan SPACE dan ENTER

What is your GitHub username? Tekan ENTER

Version control system: Pilih Git dan ENTER

Setelah itu, silahkan tunggu proses installasi Nuxt.js sampai selesai, disini akan memakan
waktu lebih lama jika sebelumnya belum pernah melakukan installasi Nuxt.js.

Langkah 2 - Menjalankan Project Nuxt.js


Setelah proses installasi selesai, sekarang kita lanjutkan untuk menjalankan project Nuxt.js
kita. Silahkan jalankan perintah berikut ini :

cd nuxt-ecommerce

Perintah cd di atas digunakan untuk masuk ke dalam folder nuxt-ecommerce. Setelah itu,
jalankan perintah berikut ini :

npm run dev

405
Silahkan tunggu proses compile sampai selesai dan jika sudah selsai, maka aplikasi Nuxt.js
kita akan dijalankan menggunakan port 3000 di dalam localhost.

Silahkan buka di browser dengan mengetikkan http://localhost:3000 jika berhasil, maka


kuurang lebih seperti berikut ini :

Langkah 3 - Konfigurasi Base URL API


Setelah itu kita juga akan melakukan konfigurasi untuk Base URL dari endpoint API ang
akan kita gunakan, disini tentu saja kita akan memanggil endpoint yang sudah kita buat di
Laravel.

Silahkan buka file nuxt.config.js, kemudian cari kode berikut ini :

axios: {},

Kemudian ubah kode-nya menjadi seperti berikut ini :

axios: {
baseURL: 'http://localhost:8000'
},

Di atas kita atur untuk Base URL dari API kita adalah http://localhost:8000, dimana domain

406
tersebut merupakan aplikasi Laravel kita yang sedang berjalan.

407
Kustomisasi Progress Bar di Nuxt.js

Secara default di dalam Nuxt.js sudah tersedian sebuah progress bar dan akan ditampilkaan
saat kita melakukan perpindahan antar route. Disini kita dapat menyesuaikan,
menonaktifkan atau dapat membuat component sendiri untuk progress bar.

Kustomisasi Progress Bar


Silahkan buka file nuxt.config.js dan tambahkan properti berikut ini di dalamnya :

export default {
loading: {
color: 'white', // <-- color
height: '5px' // <-- height
}
}

Kode di atas digunakan untuk menampilkan progress bar berwarna putih dan mempunyai
tinggi 5 pixel.

Jika file nuxt.config.js ditulis secara lengkap, kurang lebih seperti berikut ini :

export default {

loading: {
color: 'white', // <-- color
height: '5px' // <-- height
},
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'nuxt-ecommerce',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1'
},
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }

408
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},

// Global CSS: https://go.nuxtjs.dev/config-css


css: [
],

// Plugins to run before rendering page:


https://go.nuxtjs.dev/config-plugins
plugins: [
],

// Auto import components: https://go.nuxtjs.dev/config-components


components: true,

// Modules for dev and build (recommended):


https://go.nuxtjs.dev/config-modules
buildModules: [
],

// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
],

// Axios module configuration: https://go.nuxtjs.dev/config-axios


axios: {
baseURL: 'http://localhost:8000'
},

// Build Configuration: https://go.nuxtjs.dev/config-build


build: {
}
}

409
Konfigurasi SSR dan Target Deployment

Sekarang kita akan konfigurasi di dalam project Nuxt.js untuk jenis rendering yang
digunakan dan target deployment untuk production.

Langkah 1 - Konfigurasi SSR (Server Side Rendering)


Karena kita akan membutuhkan SEO atau search engine optimization, maka kita perlu
melakukan konfigurasi rendering menggunakan SSR di dalam Nuxt.js.

Silahkan buka file nuxt.config.js, kemudian tambahkan kode berikut ini :

//rendering mode SSR


ssr: true,

Langkah 2 - Konfigurasi Target Deployment


Agar website kita dapat menggunakan SSR di server production, maka kita juga perlu
melakukan sedikit konfigurasi tambahan. Silahkan buka file nuxt.config.js, kemudian
tambahkan kode berikut ini :

// Target Deployment
target: 'server',

Dan jika file nuxt.config.js ditulis secara lengkap, maka kurang lebih seperti berikut ini :

export default {

// Target Deployment
target: 'server',

//rendering mode SSR


ssr: true,

loading: {
color: 'white', // <-- color
height: '5px' // <-- height

410
},

// Global page headers: https://go.nuxtjs.dev/config-head


head: {
title: 'nuxt-ecommerce',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1'
},
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},

// Global CSS: https://go.nuxtjs.dev/config-css


css: [
],

// Plugins to run before rendering page:


https://go.nuxtjs.dev/config-plugins
plugins: [
],

// Auto import components: https://go.nuxtjs.dev/config-components


components: true,

// Modules for dev and build (recommended):


https://go.nuxtjs.dev/config-modules
buildModules: [
],

// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
],

// Axios module configuration: https://go.nuxtjs.dev/config-axios

411
axios: {
baseURL: 'http://localhost:8000'
},

// Build Configuration: https://go.nuxtjs.dev/config-build


build: {
}
}

412
Integrasi Dengan CSS dan JavaScript External (Template
Dashboard CoreUI)

Pada tahap kali ini kita semua akan belajar bagaimana cara mengintegrasikan CSS dan
JavaScript dari sebuah teemplate dashboard di dalam Nuxt.js. Dan kali ini kita semua akan
menggunakan template dashboard yang bernama Core UI.

Langkah 1 - Unduh Assets Template


Disini saya sudah menyediakan assets yang diambil dari Core UI, yaitu file CSS, JavaScript
dan Image. Untuk itu, silahkan unduh asset tersebut di link berikut ini :

https://drive.google.com/file/d/10NgnoLbqGC1DD5_xUV8dNHr7bcaoI6p7/view?usp=sharing

Jika sudah berhasil terunduh, silahkan extract file-nya dan kita akan mendapatkan 3 folder
di dalamnya.

Langkah 2 - Konfigurasi CSS


Silahkan buat folder baru di dalam project Nuxt.js dengan nama assets, setelah itu
silahkan copy folder CSS dari hasil extract dan paste di dalam folder assets tersebut,
kurang lebih seperti berikut ini hasilnya :

413
Langkah 3 - Konfigurasi JavaScript dan Image
Sekarang, kita lanjutkan untuk asset JavaScript dan Image. Silahkan copy folder
javascript dan images dari hasil extract dan paste ke dalam folder static yang berada
di dalam project Nuxt.js. Kurang lebih seperti berikut ini :

414
Langkah 4 - Konfigurasi Global Assets di Nuxt.js
Sekarang kita lanjutkan untuk konfigurasi dari file assets yang sudah kita tambahkan di atas
agar dapat digunakan secara global di dalam project Nuxt.js. Silahkan buka file
nuxt.config.js, kemudian kode-nya menjadi seperti berikut ini :

export default {

// Target Deployment
target: 'server',

//rendering mode SSR


ssr: true,

loading: {
color: 'white', // <-- color
height: '5px' // <-- height
},

// Global page headers: https://go.nuxtjs.dev/config-head


head: {
title: 'nuxt-ecommerce',
htmlAttrs: {
lang: 'en'
},
meta: [{
charset: 'utf-8'
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: ''
}
],
link: [{
rel: 'icon',
type: 'image/x-icon',
href: '/images/logo.png'
},
{
rel: 'stylesheet',

415
href:
'https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600
;700&display=swap'
},
{
rel: 'stylesheet',
href:
'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.
css'
},
],
script: [
{ src: '/js/coreui.bundle.min.js' },
{ src: 'https://app.sandbox.midtrans.com/snap/snap.js', 'data-
client-key': 'paste_client_Key_midtrans_disini' },
]
},

// Global CSS: https://go.nuxtjs.dev/config-css


css: [
'@/assets/css/style.min.css',
'@/assets/css/custom.css',
],

// Plugins to run before rendering page:


https://go.nuxtjs.dev/config-plugins
plugins: [
],

// Auto import components: https://go.nuxtjs.dev/config-components


components: true,

// Modules for dev and build (recommended):


https://go.nuxtjs.dev/config-modules
buildModules: [
],

// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
],

// Axios module configuration: https://go.nuxtjs.dev/config-axios

416
axios: {
baseURL: 'http://localhost:8000'
},

// Build Configuration: https://go.nuxtjs.dev/config-build


build: {
}
}

Dari perubahan kode di atas, kita menambahkan array script, yang mana di dalanya kita
memanggil file JavaScript yang berada di dalam folder static/js, file tersebut adalah :

/js/coreui.bundle.min.js
https://app.sandbox.midtrans.com/snap/snap.js

CATATAN ! : untuk snap.js silahkan isi data-client-key dengan client key yang di
dapatkan dari Midtrans.

{ src: 'https://app.sandbox.midtrans.com/snap/snap.js', 'data-client-


key': 'paste_client_key_midtrans_disini' },

Di atas kita juga menambahkan 2 file CSS dari folder assets/css, kedua file tersebut
adalah :

/css/style.min.css
/css/custom.css

Tambahan, untuk menggunakan template Core UI di dalam Nuxt.js, maka kita perlu
melakukan installasi Core Icon secara manual. Silahkan jalankan perintah berikut ini di
dalam terminal/CMD :

npm install @coreui/icons@2.0.1 --save

Sampai disini maka kita sudah berhasil melakukan integrasi file-file assets dari template
dashboard Core UI di dalam project Nuxt.js.

417
INFORMASI !
Silahkan lakukan restart server Nuxt.js agar assets yang sudah kita tambahkan dapat
dicompile ulang.
Untuk melakukan restart, cukup jalankan CTRL + C di dalam terminal/CMD yang
menjalankan project-nya dan jalankan npm run dev lagi untuk menjalankan project-nya

418
Installasi dan Konfigurasi Nuxt Auth

Pada materi kali ini kita semua akan belajar bagaimana cara installasi dan konfigurasi
module Nuxt Auth di dalam Nuxt.js. Disini kita nanti akan membuat 2 strategy atau level
untuk proses login-nya, yaitu admin dan customer.

Langkah 1 - Installasi Module Nuxt Auth

Pertama kita akan lakukan installasi module nuxt auth terlebih dahulu, silahkan jalankan
perintah di bawah ini di dalam terminal/CMD dan tentunya di dalam project Nuxt.js :

npm i @nuxtjs/auth-next@5.0.0-1622918202.e815752

Silahkan tunggu proses installasi sampai selesai dan akan membutuhkan beberapa waktu.

Langkah 2 - Konfigurasi Nuxt Auth

Setelah proses installasi selsai, kita akan lanjutkan untuk proses konfigurasinya. Sekarang
silahkan buka file nuxt.config.js, kemudian cari kode berikut ini :

// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
],

Setelah itu, kita akan tambahkan module nuxt auth di dalam konfigurasi modules,
silahkan ubah kode di atas menjadi seperti berikut ini :

419
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
//https://dev.auth.nuxtjs.org/
'@nuxtjs/auth-next',
],

Kemudian kita akan menambahkan lagi untuk konfigurasi dari strategy otentikasinya di
dalam file nuxt.config.js dan tepatnya di bawah array modules.

Disini kita akan menggunakan strategy local dari module nuxt auth dan membuat 2
strategy, yaitu admin dan customer. Kurang lebih seperti berikut ini :

auth: {
strategies: {

//strategy "admin"
admin: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/admin/login',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/admin/logout',
method: 'post'
},
user: {
url: '/api/admin/user',

420
method: 'get',
propertyName: 'user'
}
},
},

//strategy "customer"
customer: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/customer/login',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/customer/logout',
method: 'post'
},
user: {
url: '/api/customer/user',
method: 'get',
propertyName: 'user'
}
},
},

},
},

Dari penambahan kode di atas, kita membuat 2 jenis strategy, yaitu : admin dan
customer, kedua strategy tersebut memiliki konfigurasi yang sama dan yang membedakan
hanya pada URL endpoint-nya saja. Dari konfigurasi di atas, kurang lebih detailnya seperti
berikut ini :

Strategy Admin

421
NAMA ENDPOINT / URL METHOD KETERANGAN

Endpoint yang digunakan untuk proses login


login /api/admin/login POST
admin.

Endpoint yang digunakan untuk proses logout


logout /api/admin/logout POST
admin.

Endpoint yang digunakan untuk mendapatkan


user /api/admin/user GET
data user setelah behasil login.

Strategy Customer

NAMA ENDPOINT / URL METHOD KETERANGAN

Endpoint yang digunakan untuk proses


login /api/customer/login POST
login customer.

Endpoint yang digunakan untuk proses


logout /api/customer/logout POST
logout customer.

Endpoint yang digunakan untuk


user /api/customer/user GET mendapatkan data customer setelah
behasil login.

Jika file nuxt.config.js di tulis secara lengkap, kurang lebih seperti berikut ini :

export default {

// Target Deployment
target: 'server',

//rendering mode SSR


ssr: true,

loading: {
color: 'white', // <-- color
height: '5px' // <-- height
},

// Global page headers: https://go.nuxtjs.dev/config-head


head: {
title: 'nuxt-ecommerce',
htmlAttrs: {
lang: 'en'
},

422
meta: [{
charset: 'utf-8'
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: ''
}
],
link: [{
rel: 'icon',
type: 'image/x-icon',
href: '/images/logo.png'
},
{
rel: 'stylesheet',
href:
'https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600
;700&display=swap'
},
{
rel: 'stylesheet',
href:
'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.
css'
},
],
script: [
{ src: '/js/coreui.bundle.min.js' },
{ src: 'https://app.sandbox.midtrans.com/snap/snap.js', 'data-
client-key': 'SB-Mid-client-bWcHM3-QSyGV2hhw' },
]
},

// Global CSS: https://go.nuxtjs.dev/config-css


css: [
'@/assets/css/style.min.css',
'@/assets/css/custom.css',
],

// Plugins to run before rendering page:


https://go.nuxtjs.dev/config-plugins

423
plugins: [
],

// Auto import components: https://go.nuxtjs.dev/config-components


components: true,

// Modules for dev and build (recommended):


https://go.nuxtjs.dev/config-modules
buildModules: [
],

// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
//https://dev.auth.nuxtjs.org/
'@nuxtjs/auth-next',
],

auth: {
strategies: {
//strategy "admin"
admin: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/admin/login',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/admin/logout',
method: 'post'
},
user: {

424
url: '/api/admin/user',
method: 'get',
propertyName: 'user'
}
},
},
//strategy "customer"
customer: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/customer/login',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/customer/logout',
method: 'post'
},
user: {
url: '/api/customer/user',
method: 'get',
propertyName: 'user'
}
},
},
},
},

// Axios module configuration: https://go.nuxtjs.dev/config-axios


axios: {
baseURL: 'http://localhost:8000'
},

// Build Configuration: https://go.nuxtjs.dev/config-build


build: {
}

425
}

Langkah 3 - Menambahkan Vuex Store


Setelah menambahkan Nuxt Auth, maka kita perlu menambahkan sebuah Vuex Store
agar tidak terjadi error, meskipun file yang akan dibuat masih kosong. Silahkan buat file
baru dengan nama index.js di dalam folder store. Dengan begini maka aplikasi kita
tidak akan menampilkan pesan warning atau error.

426
Membuat Middleware Role

Setelah berhasil melakukan installasi dan konfigurasi Nuxt Auth di dalam project Nuxt.js,
maka sekarang kita akan lanjutkan untuk membuat beberapa middleware, yaitu :

1. uathenticated - untuk mengecek apakah user sudah login atau belum.


2. isAdmin - untuk mengecek apakah yang login itu admin atau bukan.
3. isCustomer - untuk mengecek apakah yang login itu customer atau bukan.

Langkah 1 - Membuat Middleware authenticated

Middleware ini akan kita gunakan di dalam halaman login admin maupun customer, jadi
ketika user sudah berhasil login dan mengakses halaman login lagi, maka akan di arahkan
ke halaman dashboard admin atau customer.

Silahkan buat folder baru dengan nama middleware di dalam project Nuxt.js, setelah itu
silahkan buat file baru dengan nama authenticated.js di dalam folder tersebut dan
masukkan kode berikut ini :

export default function({ $auth, redirect }) {

//check loggedIn "true"


if($auth.loggedIn) {

//check role admin


if($auth.strategy.name == "admin") {

return redirect('/admin/dashboard')

//check role customer


if($auth.strategy.name == "customer") {

return redirect('/customer/dashboard')

}
}

427
Di atas kita melakukan check apakah $auth.loggedIn bernilai true, jika IYA maka user
tersebut sudah berhasil melakukan login. Dan di dalamnya kita melakukan pengecekan
sebuah kondisi dari strategy yang digunakan.

//check loggedIn "true"


if($auth.loggedIn) {
//...
}

Jika strategy bernilai admin, maka kita akan lakukan redirect / arahakan ke halaman
dashboard admin, yaitu /admin/dashboard.

//check role admin


if($auth.strategy.name == "admin") {

return redirect('/admin/dashboard')

Tapi, jika nilai dari strategy-nya adalah customer, maka kita akan redirect / arahkan ke
halaman dashboard cutsomer, yaitu /customer/dashboard.

428
//check role customer
if($auth.strategy.name == "customer") {

return redirect('/customer/dashboard')

Langkah 2 - Membuat Middleware isAdmin

Sekarang kita lanjutkan membuat middleware untuk pengecekan role admin, silahkan buat
file baru dengan nama isAdmin.js di dalam folder middleware dan masukkan kode
berikut ini :

export default function({ $auth, redirect }) {

//check loggedIn "false"


if(!$auth.loggedIn) {
return redirect('/admin/login')
}

//check admin role


if($auth.strategy.name != "admin") {
return redirect('/admin/login')
} else {
return
}

429
Di atas kita menambahkan kode untuk middleware isAdmin, di dalamnya kita membuat 2
kondisi, yang pertama untuk pengecekan jika user belum login.

//check loggedIn "false"


if(!$auth.loggedIn) {
return redirect('/admin/login')
}

Jika nilai dari $auth.loggedIn bernilai false, maka akan di arahkan ke halaman
/admin/login.

Kemudian untuk kondisi kedua, kita melakukan pengecekan apakah nama strategy-nya
admin atau bukan. Kurang lebih seperti berikut ini :

//check admin role


if($auth.strategy.name != "admin") {
return redirect('/admin/login')
} else {
return
}

Jika nama strategy bukan admin, maka akan di redirect ke halaman /admin/login dan
jika admin maka akan return true, artinya di izinkan.

430
Langkah 3 - Membuat Middleware isCustomer

Middleware ini sama dengan yang di atas, bedanya digunakan untuk customer dan semua
isinya sama. Silahkan buat file baru dengan nama isCustomer.js di dalam folder
middleware dan masukkan kode berikut ini :

export default function({ $auth, redirect }) {

//check loggedIn "false"


if(!$auth.loggedIn) {
return redirect('/customer/login')
}

//check customer role


if($auth.strategy.name != "customer") {
return redirect('/customer/login')
} else {
return
}

INFORMASI : di dalam Nuxt.js untuk menggunakan middleware, kita cukup memanggil


nama file dari middleware-nya di dalam component.

431
Installasi dan Konfigurasi Vue Star Rating

karena akan membangun sebuah website toko online, maka kita perlu menambahkan fitur
rating dan ulasan di dalam website tersebut. Dan di dalam Vue.js / Nuxt.js ada library atau
package yang akan mepermudah kita dalam pembuatannya. Library tersebut bernama Vue
Star Rating.

Langkah 1 - Installasi Vue Star Rating


Sekarang, silahkan jalankan perintah berikut ini di dalam terminal/CMD untuk melakukan
installasi library atau package Vue Star Rating.

npm install vue-star-rating@1.7.0

Silahkan tunggu proses insstallasinya salmpai selesai.

Langkah 2 - Konfigurasi Vue Star Rating di Nuxt.js


Setelah berhasil terinstall, sekarang kita lanjutkan untuk melakukan register component dari
Vue Star Rating secara global di dalam project Nuxt.js.

Silahkan buat folder baru dengan nama plugins dan di dalam folder tersebut silahkan buat
file baru dengan nama vue-star-rating.js dan masukkan kode berikut ini :

import Vue from 'vue'


import VueStarRating from 'vue-star-rating'

Vue.component('vue-star-rating', VueStarRating);

432
Di atas pertama kita melakukan import Vue dari vue.

import Vue from 'vue'

Setelah itu, kita juga import VueStarRating dari vue-star-rating.

import VueStarRating from 'vue-star-rating'

Kemudian, kita register atau membuat component dengan nama vue-star-rating yang
di ambil dari VueStarRating di dalam Vue.js

Vue.component('vue-star-rating', VueStarRating);

Langkah 3 - Register Plugin di Nuxt Config


Setelah itu, agar component vue-star-rating di atas dapat digunakan secara global,
maka kita perlu melakukan register di dalam file config.

Silahkan buka file nuxt.config.js kemudian cari kode berikut ini :

433
plugins: [
],

Dan ubah menjadi seperti berikut ini :

plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
],

Di atas kita register di dalam config plugins dengan mode client. Sekarang kita sudah
bisa menggunakan component tersebut secara global di dalam project Nuxt.js kita.

434
Installasi dan Konfigurasi Chart.js

Karena akan membuat tampilkan dashboard yang interaktif dengan menambahkan grafik
atau chart, maka kita juga akan melakukan installasi dan konfigurasi library tambahan di
dalam Nuxt.js, yaitu VueChart.js dan Cart.js.

Langkah 1 - Installasi VueChart.js dan Chart.js

Silahkan jalankan perintah berikut ini di dalam terminal/CMD dan pastikan sudah berada di
dalam project Nuxt.js-nya.

npm install vue-chartjs@3.5.1

npm install chart.js@2.9.4

Silahkan tunggu hingga proses installasinya selesai.

Langkah 2 - Konfigurasi VueChart.js

Setelah berhasil menambahkan library atau package di dalam project, maka sekarang kita
lanjutkan untuk melakukan konfigurasi di dalam folder plugins, agar library tersebut dapat
digunakan secara global.

Silahkan buat file baru dengan nama chart.js di dalam folder plugins dan masukkan
kode berikut ini di dalamnya.

import Vue from 'vue'


import {
Line
} from 'vue-chartjs'

Vue.component('line-chart', {
extends: Line,

//props
props: ['data'],

435
//method
methods: {
//format price
formatPrice(value) {
let val = (value/1).toFixed(0).replace('.', ',')
return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g,
".")
},
},

//computed
computed: {
options() {
return {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxes: [{
ticks: {
callback: (value, index, values) => {
return `Rp. ${this.formatPrice(value)}`;
},
},
}],
},
tooltips: {
enabled: true,
callbacks: {
label: ((tooltipItems, data) => {
console.log(this)
return `Rp. ${this.formatPrice(tooltipItems.yLabel)}`
})
}
},
beginZero: true
}
},
},

//mounted
mounted() {
this.renderChart(this.data, this.options)
}
})

436
Langkah 3 - Register Plugin di Nuxt Config
Sekarang, agar plugin di atas dapat digunakan secara global, maka kita perlu melakukan
register di dalam file nuxt config. Silahkan buka file nuxt.config.js kemudian cari kode
berikut ini :

plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
],

Kemudian, ubah kodenya menjadi seperti berikut ini :

plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
{ src: '~/plugins/chart.js', mode: 'client' },
]

Di atas di melakukan register chart.js dengan mode client di dalam config plugins.
Dan sekarang kita sudah bisa menggunakan plugin tersebut secara global.

437
Installasi CKEDITOR dan Sweet Alert 2

Pada materi kali ini kita semua akan belajar bagaimana cara menambahkan library atau
package baru di dalam Nuxt.js, yaitu CKEDITOR dan Sweet Alert2.

Langkah 1 - Installasi CKEDITOR


CKEDITOR merupakan rich text editor yang digunakan di dalam sebuah website untuk
menulis sebuah konten dengan lebih mudah, karena terdapat banyak fitur di dalamnya,
seperti memformat tulisan menjadi tebal, miring, dan lain-lain.

Dan sekarang kita akan menambahkan library ini di dalam project Nuxt.js yang nantinya
akan digunakan untuk membuat kontent / deskripsi dari data product. Silahkan jalankan
perintah dibawah ini di dalam terminal/CMD dan pastikan sudah berada di dalam project
Nuxt.js-nya.

npm install @blowstack/ckeditor-nuxt@0.6.0

Silahkan tunggu proses installasinya sampai selesai.

Langkah 2 - Installasi Sweet Alert2


Sweet Alert2 merupakan library JavaScript yang digunakan untuk mepercantik sebuah
alert di dalam JavaScript, kita tahu bahwa alert bawaan JavaScript itu sangat
menyedikahkan tampilannya :( dan dengan adanya Sweet Alert2 maka untuk tampilannya
nanti bisa berubah menjadi lebih cantik.

Sekarang kita akan lakukan installasi Sweet Alert2 di dalam project Nuxt.js, silahkan
jalankan perintah berikut ini di dalam terminal/CMD :

npm install -S vue-sweetalert2@4.3.1

Silahkan tunggu proses installasinya sampai selesai.

Langkah 3 - Konfigurasi Sweet Alert2


Setelah berhasil melakukan installasi, sekarang kita lanjutkan agar Sweet Alert2 tersebut

438
dapat digunakan secara global di dalam project Nuxt.js.

Silahkan buka file nuxt.config.js kemudian cari kode berikut ini :

modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
//https://dev.auth.nuxtjs.org/
'@nuxtjs/auth-next',
],

Kemudian ubah menjadi seperti berikut ini :

modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
//https://dev.auth.nuxtjs.org/
'@nuxtjs/auth-next',
//https://github.com/avil13/vue-sweetalert2
'vue-sweetalert2/nuxt',
],

Di atas kita menambahkan module baru, yaitu Sweet Alert2.

439
Membuat Global Helpers dengan Mixins

Sekarang kita akan lanjutkan untuk membuat fungsi sendiri atau helpers yang akan kita
gunakan secara berulang-ulang di dalam project Nuxt.js. Disini kita akan menggunakan
Mixins untuk membuat helpers yang sifatnya global.

Langkah 1 - Membuat Global Helpers


Silahkan buat file baru dengan nama mixins.js di dalam folder plugins dan masukkan
kode berikut ini di dalamnya.

import Vue from 'vue'

const mixin = {
methods: {
//format price
formatPrice(value) {
let val = (value/1).toFixed(0).replace('.', ',')
return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".")
},

//calculate discount
calculateDiscount(product) {
return product.price - (product.price
*(product.discount)/100)
},

},

Vue.mixin(mixin)

440
Di atas, pertama kita import Vue dari vue.

import Vue from 'vue'

Setelah itu, kita buat variable dengan nama mixin dan di dalamnya kita buat 2 method
baru, yaitu :

1. formatPrice - digunakan untuk mengubah angka menjadi format mata uang.


2. calculateDiscount - digunakan untuk menghitung diskon dari product.

Setelah itu, kita register variable mixin tersebut di dalam Instanece Vue.mixin, tujuannya
agar bisa digunakan secara global di dalam project.

Vue.mixin(mixin)

Langkah 2 - Register Plugin di Nuxt Config


Sekarang, agar plugin di atas dapat digunakan secara global, maka kita perlu melakukan
register di dalam file nuxt config. Silahkan buka file nuxt.config.js kemudian cari kode
berikut ini :

441
plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
{ src: '~/plugins/chart.js', mode: 'client' },
],

Kemudian ubah menjadi seperti berikut ini :

plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
{ src: '~/plugins/chart.js', mode: 'client' },
{ src: '~/plugins/mixins.js' },
],

Di atas kita melakukan register mixins.js di dalam config plugins.

442
Membuat Proses Login Admin

Di dalam materi kali ini kita semua akan belajar bagaimana cara membuat proses otentikasi
untuk admin di dalam project Nuxt.js. Setelah sebelumnya kita sudah melakukan installasi
dan konfigurasi module Nuxt Auth, maka sekarang kita tinggal menggunakan dan
mengimplementasikannya.

Langkah 1 - Membuat Layout Auth


Pertama kita akan membuat layout terlebih dahulu, layout ini digunakan untuk induk
template dari halaman view login nantinya.

Silahkan buat folder baru dengan nama layouts dan di dalam folder tersebut silahkan buat
file baru dengan nama auth.vue, kemudian masukkan kode berikut ini di dalamnya.

443
<template>
<div class="c-app flex-row align-items-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-4">

<Nuxt />

</div>
</div>
</div>
</div>
</template>

<script>
export default {

}
</script>

<style>
body {
font-family: 'Quicksand', sans-serif;
font-size: initial!important;
}

.form-control {
height: initial;
font-size: 1rem;
}

.c-app {
background-color: #BCCAD8!important;
}
</style>

444
Di dalam template, kita menambahkan kode <Nuxt />, dimana kode tersebut akan
digunakan untuk merender sebuah view yang akan menggunakan layout ini.

Dan pada style, kita menambahkan sedikit kode untuk custom CSS agar tampilan dari
website yang kita buat lebih cantik dan bagus.

Langkah 2 - Membuat View dan Proses Login


Sekarang kita akan lanjutkan untuk membuat sebuah view atau component yang kita
gunakan untuk proses otentikasi. Silahkan buat folder baru dengan nama admin di dalam
folder pages dan di dalam folder admin silahkan buat file baru dengan nama login.vue,
setelah itu masukkan kode berikut ini :

<template>
<div class="fade-in">
<div class="text-center mb-4">
<nuxt-link to="/" class="text-black">
<img src="/images/logo.png" width="50">
<h3 class="mt-2 font-weight-bold">MI STORE</h3>
</nuxt-link>
</div>
<div class="card-group">
<div class="card border-top-orange border-0 shadow-sm rounded">
<div class="card-body">
<h1>Login</h1>
<p class="text-muted">Sign In to your account</p>

445
<div v-if="validation.message" class="mt-2">
<b-alert show variant="danger">{{ validation.message }}</b-
alert>
</div>
<form @submit.prevent="login">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-envelope"></i>
</span>
</div>
<input class="form-control" v-model="user.email" :class="{
'is-invalid': validation.email }" type="email" placeholder="Email
Address">
</div>
<div v-if="validation.email" class="mt-2">
<b-alert show variant="danger">{{ validation.email[0]
}}</b-alert>
</div>
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" v-model="user.password"
:class="{ 'is-invalid': validation.password }" type="password"
placeholder="Password">
</div>
<div v-if="validation.password" class="mt-2">
<b-alert show variant="danger">{{ validation.password[0]
}}</b-alert>
</div>
<div class="row">
<div class="col-12">
<button class="btn btn-warning shadow-sm rounded-sm px-4
w-100" type="submit">LOGIN</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</template>

446
<script>
export default {

//middleware
middleware: 'authenticated',

//layout
layout: 'auth',

//meta
head() {
return {
title: 'Login - Administrator',
}
},

data() {
return {
//state user
user: {
email: '',
password: '',
},
//validation
validation: []
}
},

methods: {
async login() {
await this.$auth.loginWith('admin', {
data: {
email: this.user.email,
password: this.user.password
}
})

.then(() => {

//redirect
this.$router.push({
name: 'admin-dashboard'
})

})

447
.catch(error => {
//assign validation
this.validation = error.response.data
})
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan middleware authenticated.

//middleware
middleware: 'authenticated',

Setelah itu, kita atur layout induk yang digunakan, disini kita akan atur dengan layout yang
sebelumnya sudah kita buat, yaitu uath.

//layout
layout: 'auth',

Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
mengubah title yang ada di website kita.

//meta
head() {
return {
title: 'Login - Administrator',
}
},

Dan di dalam data function kita membuat 2 state baru, yaitu user dan validation.

448
data() {
return {
//state user
user: {
email: '',
password: '',
},
//validation
validation: []
}
},

Untuk state user di dalamnya berisi key lagi, yang tujuannya untuk menampung data yang
di ketik di dalam form. Dan untuk state validation akan digunakan untuk menampung
response validasi yang di dapatkan dari Rest API.

Kemudian kita membuat 1 method baru dengan nama login. Di dalam method tersebut
kita melakukan proses login dengan konfigurasi module Nuxt Auth.

await this.$auth.loginWith('admin', {
data: {
email: this.user.email,
password: this.user.password
}
})

Di atas, kita melakukan proses login menggunakan strategy admin dan data yang
dikirimkan diambil dari state user.email dan user.password yang mana isinya diambil
dari input form.

Jika proses otentikasi berhasil, maka kita akan di redirect atau di arahkan ke dalam route
yang bernama admin-dashboard.

449
.then(() => {

//redirect
this.$router.push({
name: 'admin-dashboard'
})

})

Tapi, jika proses otentikasi gagal, maka akan melakukan assign sebuah response dari Rest
API ke dalam state validation.

.catch(error => {
//assign validation
this.validation = error.response.data
})

Di dalam template, pada bagian form action kita arahkan ke dalam method yang sudah kita
buat di atas, yaitu login.

<form @submit.prevent="login">

//...

</form>

Dan untuk input form, kita menggunakan directive v-model untuk menerima inputan dari
browser, yang mana isinya kita arahkan ke state user dengan masing-masing key.

Input Email

<input class="form-control" v-model="user.email" :class="{ 'is-invalid':


validation.email }" type="email" placeholder="Email Address">

Input Password

450
<input class="form-control" v-model="user.password" :class="{ 'is-
invalid': validation.password }" type="password" placeholder="Password">

Untuk menampilkan error validasi, kita bisa dengan seperti berikut ini :

<div v-if="validation.email" class="mt-2">


<b-alert show variant="danger">{{ validation.email[0] }}</b-alert>
</div>

<div v-if="validation.password" class="mt-2">


<b-alert show variant="danger">{{ validation.password[0] }}</b-alert>
</div>

Kode di atas melakukan sebuah kondisi, jika validation.email dan


validation.password memiliki sebuah value, maka akan menampilkan alert di dalamnya
dengan memberikan pesan yang di dapatkan dari Rest API.

Langkah 3 - Uji Coba Proses Login


Sekarang kita lanjutkan untuk uji coba proses otentikasi, silahkan buka halaman login di
URL berikut ini http://localhost:3000/admin/login dan jika berhasil kurang lebih tampilannya
seperti berikut ini :

451
Sekarang silahkan klik LOGIN tanpa mengisi input apapun, maka kita akan mendapatkan
sebuah error validasi kurang lebih seperti berikut ini :

Validasi tersebut kita dapatkaan dari Rest API Laravel, sekarang kita coba memasukkaan
data email dan password yang belum tersedia di dalam table users. Jika berhasil maka
kita akan mendapatkan sebuah pesan error login gagal kurang lebih seperti berikut ini :

Error tersebut muncul, karena data email dan password yang kita masukkan belum
terdaftar di dalam table users. Sekarang kita lanjutkan untuk memasukkan email dan
password yang ada di dalam table users, silahkan masukkan email dan password
berikut ini :

452
INPUT VALUE

email admin@gmail.com

password password
CATATAN ! : silahkan disesuaikan dengan data email dan password yang kalian buat.

Setelah itu silahkan klik LOGIN dan jika berhasil maka kita akan mendapatkan tampilan
seperti berikut ini :

Di atas sebenarnya kita sudah berhasil melakukan proses otentikasi, error tersebut muncul
karena kita belum memiliki halaman dashboard.

453
Menampilkan Data Products

Setelah berhasil membuat Vuex untuk Admin Product, sekarang kita akan mencoba
membuat sebuah component / view yang digunakan untuk menampilkan data.

Konsepnya nanti component atau view tersebut akan memanggil action di dalam Vuex
untuk melakukan http request ke dalam endpoint, setelah data di dapatkan, kita bisa ambil
data tersebut dengan cara memanggil state yang ada di dalam Vuex.

Kita juga akan belajar untuk membuat fitur pencarian data dan membuat pagination untuk
membatasi jumlah data yang di tampilkan di setiap halaman, tujuannya agar data tidak di
tampilkan semua.

Langkah 1 - Menampilkan Data Products


Silahkan buat folder baru dengan nama products di dalam folder pages/admin dan di
dalam folder products silahkan buat file baru dengan nama index.vue kemudian
masukkan kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-layer-
group"></i> PRODUCTS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-products-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control"
placeholder="cari berdasarkan nama product">

454
<div class="input-group-append">
<button class="btn btn-warning"><i
class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="products.data"


:fields="fields" show-empty>
</b-table>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Products - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Product Name',
key: 'title',
},
{
label: 'Category Name',
key: 'category.name'

455
},
{
label: 'Stock',
key: 'stock',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/product/getProductsData')
},

//computed
computed: {

//products
products() {
return this.$store.state.admin.product.products
},
},

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

456
Setelah itu, kita juga atur untuk meta title dari view atau component ini.

//meta
head() {
return {
title: 'Products - Administrator',
}
},

Setelah itu, di dalam data function kita menambahkan 1 state baru dengan nama fields,
state tersebut akan di gunakan untuk membuat header dari sebuah table.

//table header
fields: [{
label: 'Product Name',
key: 'title',
},
{
label: 'Category Name',
key: 'category.name'
},
{
label: 'Stock',
key: 'stock',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

Dan kita menggunakan hook asyncData untuk memanggil atau dispatch ke dalam action
yang ada di Vuex, yaitu getProductsData. Action tersebut yang sudah kita buat di materi
sebelumnya, dan berada di dalam file store/admin/product.js.

457
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/product/getProductsData')
},

Jika proses fetching http request berhasil dilakukan oleh action di Vuex, maka kita akan
memanggil data dari hasil response tersebut di dalam state yang ada di Vuex, yaitu
products.

Disini kita menggunakan properti computed untuk memanggil state products yang ada di
dalam Vuex. Kurang lebih seperti berikut ini :

//computed
computed: {

//products
products() {
return this.$store.state.admin.product.products
},
},

Setelah itu, kita tinggal menampilkan data yang kita dapatkan dari computed di dalam
template. Disini kita menggunakan component dari Bootstrap Vue, yaitu <b-table>,
kurang lebih seperti berikut ini :

<b-table striped bordered hover :items="products.data" :fields="fields"


show-empty>
</b-table>

Di atas, untuk properti :items merupakan isi dari table yang akan ditampilkan dan disini
kita berikan sebuah value dari computer properti, yaitu products.data.

Dan untuk properti :fields akan digunakan untuk menampilkan header dari table dan
value-nya akan kita isi dengan state fields yang sebelumnya sudah kita buat di dalam
data function.

458
Langkah 2 - Mengaktifkan Link Menu
Setelah berhasil membuat view atau component, maka secara otomatis Nuxt.js juga akan
membuatkan kita route dari file yang sudah kita buat di dalam halaman pages. Sekarang
kita akan mengaktifkan menu tersebut di dalam file component Sidebar.

Silahkan buka file components/admin/sidebar.vue, kemudian cari kode berikut ini :

<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-link">


<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
layers"></use>
</svg> Products</a>
</li>

Dan ubahlah menjadi seperti berikut ini :

<li class="c-sidebar-nav-item">
<nuxt-link :to="{name: 'admin-products'}" class="c-sidebar-nav-
link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
layers"></use>
</svg> Products
</nuxt-link>
</li>

Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam admin-products.

Sekarang silahkan klik menu Products yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/admin/products, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :

459
Langkah 3 - Membuat Fitur Pencarian
Sekarang kita lanjutkan untuk membuat fitur pencarian di dalam table, fitur ini akan
mempermudah kita jika ingin mencari data tertentu.

Silahkan buka pages/admin/products/index.vue kemudian ubah semua kode-nya


menjadi seperti berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-layer-
group"></i> PRODUCTS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-products-

460
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama product">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="products.data"


:fields="fields" show-empty>
</b-table>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Products - Administrator',
}
},

//data function
data() {
return {
//table header

461
fields: [{
label: 'Product Name',
key: 'title',
},
{
label: 'Category Name',
key: 'category.name'
},
{
label: 'Stock',
key: 'stock',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/product/getProductsData')
},

//computed
computed: {

//products
products() {
return this.$store.state.admin.product.products
},
},

//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"

462
this.$store.commit('admin/product/SET_PAGE', 1)

//dispatch on action "getProductsData"


this.$store.dispatch('admin/product/getProductsData',
this.search)
},
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.

//state search
search: ''

Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :

<input type="text" class="form-control" v-model="search"


@keypress.enter="searchData" placeholder="cari berdasarkan nama
product">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i class="fa fa-
search"></i>
SEARCH
</button>
</div>

Di atas pada bagian input kita menambahkan event @keypress.enter dimana di


dalamnya akan memanggil method yang bernama searchData, di dalam button juga sama
kita menambahkan event @click yang kita arahkan ke dalam method searcData.

Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :

463
//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/product/SET_PAGE', 1)

//dispatch on action "getProductsData"


this.$store.dispatch('admin/product/getProductsData', this.search)
},

Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.

Dan yang kedua, melakukan dispatch ke dalam action Vuex yang bernama
getProductsData dan kita parsing sebuah parameter yaitu this.search yang isinya
adalah kata kunci yang di dapatkan dari input form.

Sekarang silahkan refresh / reload halaman products dan jika berhasil maka kita bisa
mencoba proses pencarian kurang lebih seperti berikut ini :

Langkah 4 - Membuat Fitur Pagination


Setelah berhasil membuat fitur pencarian, sekarang kita lanjutkan untuk menambahkan fitur
pagination, fitur ini digunakan untuk menampilkan button navigasi ke halaman-halaman
yang lain.

464
Silahkan buka file pages/admin/products/index.vue, kemudian ubah semua kode-nya
menjadi seperti berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-layer-
group"></i> PRODUCTS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-products-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama product">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="products.data"


:fields="fields" show-empty>
</b-table>

<!-- pagination -->


<b-pagination align="right"
:value="products.current_page" :total-rows="products.total"
:per-page="products.per_page" @change="changePage"

465
aria-controls="my-table"></b-pagination>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Products - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Product Name',
key: 'title',
},
{
label: 'Category Name',
key: 'category.name'
},
{
label: 'Stock',
key: 'stock',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}

466
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/product/getProductsData')
},

//computed
computed: {

//products
products() {
return this.$store.state.admin.product.products
},
},

//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/product/SET_PAGE', 1)

//dispatch on action "getProductsData"


this.$store.dispatch('admin/product/getProductsData',
this.search)
},
//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/product/SET_PAGE', page)

//dispatch on action "getProductsData"


this.$store.dispatch('admin/product/getProductsData',
this.search)
},
}

467
}
</script>

<style>

</style>

Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state products yang ada di dalam Vuex.

<b-pagination align="right" :value="products.current_page" :total-


rows="products.total" :per-page="products.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

Dan ketika navigasi pagination di klik, maka akan menjalankan event @change, yang
mengarah ke dalam method changePage.

Di dalam method changePage kita menjalankan 2 perintah, yang pertaman melakukan


commit ke dalam mutation yang bernama SET_PAGE dengan parameter yang diambil dari
component pagination di atas.

//commit to mutation "SET_PAGE"


this.$store.commit('admin/product/SET_PAGE', page)

Dan yang kedua menjalankan dispatch ke dalam action getProductsData dan


menambahkan parameter this.seach, karena bisa jadi kita melakukan navigasi
pagination dari hasil pencarian data.

//dispatch on action "getProductsData"


this.$store.dispatch('admin/product/getProductsData', this.search)

Sekarang kita bisa mencoba dengan melakukan refresh di halaman products atau bisa
melihatnya melalui link http://localhost:3000/admin/products dan jika berhasil maka kita
akan mendapatkan tampilan kurang lebih seperti berikut ini :

468
469
Membuat Proses Insert Data Product

Setelah berhasil menampilkan data, membuat pencarian dan sekaligus membuat fitur
pagination, maka sekarang kita akan lanjutkan belajar bagaimana cara membuat proses
insert data product ke dalam database menggunakan Rest API dan Vuex di dalam project
Nuxt.js.

Langkah 1 - Menambahkan Action di Vuex Product


Pertama, kita akan menambahkan sebuah action dulu di dalam Vuex, fungsinya untuk
melakukan http request dan mengirim data ke dalam server. Silahkan buka file
store/admin/product.js dan ubah kode-nya menjadi seperti berikut ini :

//state
export const state = () => ({

//products
products: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {

//set value state "products"


state.products = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

470
//actions
export const actions = {

//get products data


getProductsData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/products" with method "GET"


this.$axios.get(`/api/admin/products?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_PRODUCTS_DATA"


commit('SET_PRODUCTS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store product
storeProduct({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/products" with method "POST"


this.$axios.post('/api/admin/products', payload)

//success
.then(() => {

//dispatch action "getProductsData"


dispatch('getProductsData')

//resolve promise
resolve()

471
})

//error
.catch(error => {
reject(error)
})

})
},

Di atas kita menambahkan 1 action baru dengan nama storeProduct.

//store product
storeProduct({ dispatch, commit }, payload) {
//...
}

Di dalam action tersebut kita melakukan http request menggunakan Axios dengan method
POST ke dalam endpoint /api/admin/products dan kita tambahkan parameter payload
yang nantinya berisi data yang dikirimkan dari component / view.

//store to Rest API "/api/admin/products" with method "POST"


this.$axios.post('/api/admin/products', payload)

Jika proses http request untuk sending data di atas berhasil dilakukan, maka kita akan
menjalankan dispatch ke dalam action yang bernama getProductsData, tujuannya
untuk memperbarui data yang akan ditampilkan.

//dispatch action "getProductsData"


dispatch('getProductsData')

Langkah 2 - Menambahkan Action di Vuex Category


Karena nanti kita akan menampilkan list data category di dalam form tambah data product,
maka sekarang kita harus membuat 1 action lagi yang akan digunakan untuk mendapatkan

472
semua list category dari database.

Silahkan buka file store/admin/category.js, kemudian ubah semua kode-nya menjadi


seperti berikut ini :

//state
export const state = () => ({

//categories
categories: [],

//page
page: 1,

//category
category: {}

})

//mutations
export const mutations = {

//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {

//set value state "categories"


state.categories = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {

//set value state "category"


state.category = payload
},

473
//actions
export const actions = {

//get categories data


getCategoriesData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/categories" with method


"GET"
this.$axios.get(`/api/admin/categories?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_CATEGORIES_DATA"


commit('SET_CATEGORIES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store category
storeCategory({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/categories" with method


"POST"
this.$axios.post('/api/admin/categories', payload)

//success
.then(() => {

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

//resolve promise

474
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//get detail category


getDetailCategory({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//get to Rest API "/api/admin/categories/:id" with method


"GET"
this.$axios.get(`/api/admin/categories/${payload}`)

//success
.then(response => {

//commit to mutation "SET_CATEGORY_DATA"


commit('SET_CATEGORY_DATA', response.data.data)

//resolve promise
resolve()

})

})

},

//update category
updateCategory({ dispatch, commit }, { categoryId, payload }) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/categories/:id" with method


"POST"
this.$axios.post(`/api/admin/categories/${categoryId}`,

475
payload)

//success
.then(() => {

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//destroy category
destroyCategory({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {
//delete to Rest API "/api/admin/categories/:id" with method
"DELETE"
this.$axios.delete(`/api/admin/categories/${payload}`)

//success
.then(() => {

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

//resolve promise
resolve()

})

})

},

//get list all categories

476
getListAllCategories({ commit, state }, payload) {

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/categories" with method "GET"


this.$axios.get('/api/web/categories')
//success
.then((response) => {

//commit ti mutation "SET_CATEGORIES_DATA"


commit('SET_CATEGORIES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Di atas kita menambahkan 1 action baru dengan nama getListAllCategories, action


tersebut akan digunakan untuk mendapatkan semua list data category yang nanti akan di
tampilkan menjadi select option di halaman tambah data product.

//get list all categories


getListAllCategories({ commit, state }, payload) {

//...
}

Di dalam action tersebut, kita melakukan http request menggunakan Axios dengan method
GET ke dalam endpoint /api/web/categories.

//fetching Rest API "/api/web/categories" with method "GET"


this.$axios.get('/api/web/categories')

Jika berhasil, maka akan kita commit ke dalam mutation yang bernama

477
SET_CATEGORIES_DATA dengan memberikan parameter berupa data response dari Rest
API.

//commit ti mutation "SET_CATEGORIES_DATA"


commit('SET_CATEGORIES_DATA', response.data.data)

Langkah 3 - Membuat View Tambah Product


Setelah berhasil menambahkan beberapa action di dalam Vuex, sekarang kita lanjutkan
untuk membuat component / view yang digunakan untuk menampilkan halaman form
tambah data product, sekaligus untuk proses insert-nya.

Silahkan buat folder baru dengan nama create di dalam folder page/admin/products
dan di dalam folder create tersebut silahkan buat file baru dengan nama index.vue dan
masukkan kode berikut ini di dalamnya :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> ADD NEW PRODUCT</span>
</div>
<div class="card-body">

<form @submit.prevent="storeProduct">

<div class="form-group">
<label>GAMBAR</label>
<input type="file" @change="handleFileChange"
class="form-control">
<div v-if="validation.image" class="mt-2">
<b-alert show variant="danger">{{
validation.image[0] }}</b-alert>
</div>
</div>

478
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>NAMA PRODUCT</label>
<input type="text" v-model="product.title"
placeholder="Masukkan Nama Product"
class="form-control">
<div v-if="validation.title" class="mt-2">
<b-alert show variant="danger">{{
validation.title[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>CATEGORY</label>
<select class="form-control" v-
model="product.category_id">
<option value="">-- select category --
</option>
<option v-for="category in categories"
:key="category.id" :value="category.id">{{ category.name }}</option>
</select>
<div v-if="validation.category_id" class="mt-2">
<b-alert show variant="danger">{{
validation.category_id[0] }}</b-alert>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>WEIGHT (Gram)</label>
<input type="number" v-model="product.weight"
placeholder="Masukkan Berat Product (Gram)"
class="form-control">
<div v-if="validation.weight" class="mt-2">
<b-alert show variant="danger">{{
validation.weight[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">

479
<label>STOCK</label>
<input type="number" v-model="product.stock"
placeholder="Masukkan Stock Product"
class="form-control">
<div v-if="validation.stock" class="mt-2">
<b-alert show variant="danger">{{
validation.stock[0] }}</b-alert>
</div>
</div>
</div>
</div>

<div class="form-group">
<label>DESCRIPTION</label>
<client-only placeholder="loading...">
<ckeditor-nuxt v-model="product.description"
:config="editorConfig" />
</client-only>
<div v-if="validation.description" class="mt-2">
<b-alert show variant="danger">{{
validation.description[0] }}</b-alert>
</div>
</div>

<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>PRICE</label>
<input type="number" v-model="product.price"
placeholder="Masukkan Harga Product"
class="form-control">
<div v-if="validation.price" class="mt-2">
<b-alert show variant="danger">{{
validation.price[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>DISCOUNT (%)</label>
<input type="number" v-model="product.discount"
placeholder="Masukkan Discount Product (%)"
class="form-control">
<div v-if="validation.discount" class="mt-2">
<b-alert show variant="danger">{{
validation.discount[0] }}</b-alert>

480
</div>
</div>
</div>
</div>

<button class="btn btn-info mr-1 btn-submit"


type="submit"><i class="fa fa-paper-plane"></i>
SAVE</button>
<button class="btn btn-warning btn-reset"
type="reset"><i class="fa fa-redo"></i>
RESET</button>

</form>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Add New Product - Administrator',
}
},

components: {
'ckeditor-nuxt': () => {
if (process.client) {
return import('@blowstack/ckeditor-nuxt')
}
},
},

data() {
return {

481
//state product
product: {
image: '',
title: '',
category_id: '',
description: '',
weight: '',
price: '',
stock: '',
discount: ''
},
//state validation
validation: [],
//config CKEDITOR
editorConfig: {
removePlugins: ['Title'],
}
}
},

//hook "asyncData"
async asyncData({ store }) {

//get list all categories


await store.dispatch('admin/category/getListAllCategories')
},

//computed
computed: {

//categories
categories() {
return this.$store.state.admin.category.categories
},
},

methods: {

//handle file upload


handleFileChange(e) {

//get image
let image = this.product.image = e.target.files[0]

//check fileType
if (!image.type.match('image.*')) {

482
//if fileType not allowed, then clear value and set null
e.target.value = ''

//set state "product.image" to null


this.product.image = null

//show sweet alert


this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})
}

},

//method "storeProduct"
async storeProduct() {

//define formData
let formData = new FormData();

formData.append('image', this.product.image)
formData.append('title', this.product.title)
formData.append('category_id', this.product.category_id)
formData.append('description', this.product.description)
formData.append('weight', this.product.weight)
formData.append('price', this.product.price)
formData.append('stock', this.product.stock)
formData.append('discount', this.product.discount)

//sending data to action "storeProduct" vuex


await this.$store.dispatch('admin/product/storeProduct',
formData)

//success
.then(() => {

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,

483
timer: 2000
})

//redirect route "admin-products"


this.$router.push({
name: 'admin-products'
})

})

//error
.catch(error => {

//assign error to state "validation"


this.validation = error.response.data
})
}
}

}
</script>

<style>
.ck-editor__editable {
min-height: 200px;
}
</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

484
//meta
head() {
return {
title: 'Add New Product - Administrator',
}
},

Kemudian kita mendaftarkan CKEDITOR di dalam properti components agar kita dapat
menggunakan CKEDITOR tersebut di dalam template.

'ckeditor-nuxt': () => {
if (process.client) {
return import('@blowstack/ckeditor-nuxt')
}
},

Dan di dalam data function kita membuat beberapa state, diantara yaitu product yang di
di dalamnya terdapat beberapa key lagi, state ini akan kita gunakan untuk menampung
sebuah value yang di kirim dari form sebelum dikirimkan ke Vuex.

//state product
product: {
image: '',
title: '',
category_id: '',
description: '',
weight: '',
price: '',
stock: '',
discount: ''
},

Masih di dalam data function, untuk state validation akan digunakan untuk menampung
sebuah error validasi yang di dapatkan dari Rest API.

485
//state validation
validation: [],

Dan kita membuat sedikit konfigurasi tambahan untuk CKEDITOR, yaitu menghapus plugin
title, karena kita tidak membutuhkan plugin tersebut di dalam kasus ini.

//config CKEDITOR
editorConfig: {
removePlugins: ['Title'],
}

Setelah itu, kita menggunakan hook asyncData untuk melakukan dispatch atau memanggil
sebuah action yang bernama getListAllCategories yang berada di dalam file
store/admin/category.js. Action tersebut akan digunakan untuk mendapatkan list
data category melalui Rest API.

//hook "asyncData"
async asyncData({ store }) {

//get list all categories


await store.dispatch('admin/category/getListAllCategories')
},

Setelah itu, kita akan ambil data category tersebut menggunakan properti computed, dan di
dalamnya akan mengambil langsung ke dalam state yang bernama categories yang
berada di dalam store/admin/category.js.

//computed
computed: {

//categories
categories() {
return this.$store.state.admin.category.categories
},
},

Kemudian di dalam method kita membuat 2 fungsi, yaitu :

486
handleFileChange
storeProduct

handleFileChange

Method ini akan dijalankan ketika input file di dalam template diubah, karena di dalam input
tersebut kita memberikan event @change yang mengarah ke dalam method ini.

<input type="file" @change="handleFileChange" class="form-control">

Di damana di dalam method handleFileChange pertama kita akan melakukan assign file
yang dipilih dari komputer ke dalam state category.image.

//get image
let image = this.product.image = e.target.files[0]

Setelah itu, kita membuat kondisi untuk memeriksa apakah file yang diupload tersebut
menggunakan ekstensi image atau bukan. Jika file yang di pilih buka format image, maka
kita akan melakukan beberapa aksi, yang pertama mengosongkan input file di formnya
terlebih dahulu.

//if fileType not allowed, then clear value and set null
e.target.value = ''

Setelah itu, state product.image kita juga set ke null.

//set state "product.image" to null


this.product.image = null

Kemudian kita tampilkan sebuah alert atau pesan bahwa file yang akan di upload tidak
sesuai dengan yang di harapkan.

487
//show sweet alert
this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})

storeProduct

Method ini akan kita gunakan untuk menampung data yang diinputkan dari form sebelum
data tersebut dikirimkan ke dalam Vuex. Pertama kita melakukan inisialisasi sebuah
FormData.

//define formData
let formData = new FormData();

Kemudian, kita membuat beberapa append formData yang isinya adalah data yang di
dapatkan dari input form.

formData.append('image', this.product.image)
formData.append('title', this.product.title)
formData.append('category_id', this.product.category_id)
formData.append('description', this.product.description)
formData.append('weight', this.product.weight)
formData.append('price', this.product.price)
formData.append('stock', this.product.stock)
formData.append('discount', this.product.discount)

Setelah itu, kita akan kirim formData di atas ke dalam action Vuex yang bernama
storeProduct.

//sending data to action "storeProduct" vuex


await this.$store.dispatch('admin/product/storeProduct', formData)

Jika proses sending data berhasil, maka kita akan menampilkan alert menggunakan Sweet

488
Alert2.

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Dan kita arahkan atau redirect ke dalam route yang bernama admin-products.

//redirect route "admin-products"


this.$router.push({
name: 'admin-products'
})

Tapi, jika proses sending data gagal dilakukan, maka kita akan melakukan assign error
validasi yang di dapatkan dari Rest API ke dalam state yang bernama validation.

//assign error to state "validation"


this.validation = error.response.data

Di dalam template, untuk form action kita arahkan ke dalam method yang sudah kita buat di
atas, yaitu storeProduct.

<form @submit.prevent="storeProduct">

//...
</form>

Untuk menampilkan select option categories, kita ambil datanya dari computed properti,
dan kita menggunakan directive v-for, karena data yang ditampilkan lebih dari satu atau
jamak.

489
<option v-for="category in categories" :key="category.id"
:value="category.id">{{ category.name }}</option>

Langkah 3 - Uji Coba Proses Insert Data Product


Sekarang kita akan lakukan uji coba untuk proses insert data product. Silahkaan klik button
TAMBAH yang berada di halaman index products atau bisa menggunakan link berikut ini
http://localhost:3000/admin/products/create jika berhasil maka kita akan mendapatkan hasil
seperti berikut ini :

490
491
Membuat Proses Edit dan Update Data Product

Setelah berhasil membuat proses insert data ke dalam database dan juga menampilkan
data, maka sekarang kita akan lanjutkan untuk membuat fitur edit dan update data ke
dalam database menggunakan Rest API dan Vuex di dalam Nuxt.js.

Langkah 1 - Menambahkan State, Mutation dan Action di Vuex


Pertama-tama kita akan menambahkan sebuah state, mutation dan action baru di
dalam Vuex Admin Product. Silahkan buka file store/admin/product.js dan ubah
semua kode-nya menjadi seperti berikut ini :

//state
export const state = () => ({

//products
products: [],

//page
page: 1,

//product
product: {}

})

//mutations
export const mutations = {

//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {

//set value state "products"


state.products = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

492
//mutation "SET_PRODUCT_DATA"
SET_PRODUCT_DATA(state, payload) {

//set value state "product"


state.product = payload
},

//actions
export const actions = {

//get products data


getProductsData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/products" with method "GET"


this.$axios.get(`/api/admin/products?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_PRODUCTS_DATA"


commit('SET_PRODUCTS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store product
storeProduct({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/products" with method "POST"


this.$axios.post('/api/admin/products', payload)

493
//success
.then(() => {

//dispatch action "getProductsData"


dispatch('getProductsData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//get detail product


getDetailProduct({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//get to Rest API "/api/admin/products/:id" with method


"GET"
this.$axios.get(`/api/admin/products/${payload}`)

//success
.then(response => {

//commit to mutation "SET_PRODUCT_DATA"


commit('SET_PRODUCT_DATA', response.data.data)

//resolve promise
resolve()

})

})

},

//update product
updateProduct({ dispatch, commit }, { productId, payload }) {

494
//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/products/:id" with method


"POST"
this.$axios.post(`/api/admin/products/${productId}`,
payload)

//success
.then(() => {

//dispatch action "getProductsData"


dispatch('getProductsData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

Dari perubahan kode di atas, pertama kita menambahkan 1 state baru dengan nama
product, state tersebut nantinya akan kita gunakan untuk menyimpan detail data product.

//product
product: {}

Kemudian kita juga menambahkan 1 mutation baru yang bernama SET_PRODUCT_DATA,


mutation tersebut nantinya akan kita gunakan untuk mengubah isi dari state product di
atas, dengan data response yang dikirimkan oleh action.

495
//mutation "SET_PRODUCT_DATA"
SET_PRODUCT_DATA(state, payload) {

//set value state "product"


state.product = payload
},

Dan di dalam action, kita menambahkan 2 method baru, yaitu :

getDetailProduct - digunakan untuk mendapatkan detail data product.


updateProduct - digunakan untuk melakukan proses update ke dalam database.

getDetailProduct

Action ini akan kita gunakan untuk mendapatkan detail data product berdasarkan ID, disini
kita melakukan http request menggunakan Axios dengan method GET ke dalam endpoint
/api/admin/products/:id.

//get to Rest API "/api/admin/products/:id" with method "GET"


this.$axios.get(`/api/admin/products/${payload}`)

Jika data berhasil ditemukan di dalam database, maka kita akan lakukan commit ke dalam
mutation yang bernama SET_PRODUCT_DATA dengan memberikan parameter berupada
data response yang di dapatkan dari Rest API.

//commit to mutation "SET_PRODUCT_DATA"


commit('SET_PRODUCT_DATA', response.data.data)

updateProduct

Action ini akan kita gunakan untuk melakukan proses update data ke dalam database. Disini
kita melakukan http request ke dalam endpoint /api/admin/products/:id
menggunakan Axios dengan method POST. Disini kita juga menambahkan parameter
payload yang isinya nanti diambil dari component / view.

496
//store to Rest API "/api/admin/products/:id" with method "POST"
this.$axios.post(`/api/admin/products/${productId}`, payload)

Jika proses sending data berhasil dilakukan, maka kita akan menjalankan dispatch atau
memanggil sebuah action yang bernama getProductsData, tujuannya agar kita
melakukan fetching ulang dengan data yang baru.

//dispatch action "getProductsData"


dispatch('getProductsData')

Langkah 2 - Menambahkan Button Edit


Sekarang kita akan lanjutkan untuk menambahkan sebuah button untuk proses navigasi ke
dalam halaman edit data product. Silahkan buka file
pages/admin/products/index.vue, kemudian cari kode berikut ini :

<b-table striped bordered hover :items="products.data" :fields="fields"


show-empty>
</b-table>

Dan ubahlah menjadi seperti berikut ini :

<b-table striped bordered hover :items="products.data" :fields="fields"


show-empty>
<template v-slot:cell(actions)="row">
<b-button :to="{name: 'admin-products-edit-id', params: {id:
row.item.id}}" variant="info" size="sm">
EDIT
</b-button>
</template>
</b-table>

Di atas kita menambahkan 1 button baru untuk kita gunakan dalam proses edit data,
dimana di dalam button tersebut kita arahkan ke dalam route yang bernama admin-
products-edit-id dan kita tambahkan juga parameter berupa data ID params: {id:
row.item.id}.

497
Sekarang jika kita coba refresh halaman index products, maka kita akan mendapatkan
kurang lebih tampilannya seperti berikut ini :

Langkah 3 - Membuat View Edit Product


Langkah selanjutnya, kita akan membuat sebuah component / view yang digunakan untuk
menampilkan data yang akan di edit dan sekaligus membuat proses update-nya.

Silahkan buat folder baru dengan nama edit di dalam folder pages/admin/products dan
di dalam folder edit silahkan buat file baru dengan nama _id.vue dan masukkan kode
berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> EDIT PRODUCT</span>
</div>
<div class="card-body">

<form @submit.prevent="updateProduct">

498
<div class="form-group">
<label>GAMBAR</label>
<input type="file" @change="handleFileChange"
class="form-control">
</div>

<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>NAMA PRODUCT</label>
<input type="text" v-model="product.title"
placeholder="Masukkan Nama Product"
class="form-control">
<div v-if="validation.title" class="mt-2">
<b-alert show variant="danger">{{
validation.title[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>CATEGORY</label>
<select class="form-control" v-
model="product.category_id">
<option value="">-- select category --
</option>
<option v-for="category in categories"
:key="category.id" :value="category.id">{{ category.name }}</option>
</select>
<div v-if="validation.category_id" class="mt-2">
<b-alert show variant="danger">{{
validation.category_id[0] }}</b-alert>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>WEIGHT (Gram)</label>
<input type="number" v-model="product.weight"
placeholder="Masukkan Berat Product (Gram)"
class="form-control">
<div v-if="validation.weight" class="mt-2">
<b-alert show variant="danger">{{

499
validation.weight[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>STOCK</label>
<input type="number" v-model="product.stock"
placeholder="Masukkan Stock Product"
class="form-control">
<div v-if="validation.stock" class="mt-2">
<b-alert show variant="danger">{{
validation.stock[0] }}</b-alert>
</div>
</div>
</div>
</div>

<div class="form-group">
<label>DESCRIPTION</label>
<client-only placeholder="loading...">
<ckeditor-nuxt v-model="product.description"
:config="editorConfig" />
</client-only>
<div v-if="validation.description" class="mt-2">
<b-alert show variant="danger">{{
validation.description[0] }}</b-alert>
</div>
</div>

<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>PRICE</label>
<input type="number" v-model="product.price"
placeholder="Masukkan Harga Product"
class="form-control">
<div v-if="validation.price" class="mt-2">
<b-alert show variant="danger">{{
validation.price[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>DISCOUNT (%)</label>

500
<input type="number" v-model="product.discount"
placeholder="Masukkan Discount Product (%)"
class="form-control">
<div v-if="validation.discount" class="mt-2">
<b-alert show variant="danger">{{
validation.discount[0] }}</b-alert>
</div>
</div>
</div>
</div>

<button class="btn btn-info mr-1 btn-submit"


type="submit"><i class="fa fa-paper-plane"></i>
UPDATE</button>
<button class="btn btn-warning btn-reset"
type="reset"><i class="fa fa-redo"></i>
RESET</button>

</form>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Edit Product - Administrator',
}
},

components: {
'ckeditor-nuxt': () => {
if (process.client) {
return import('@blowstack/ckeditor-nuxt')

501
}
},
},

data() {
return {
//state product
product: {
image: '',
title: '',
category_id: '',
description: '',
weight: '',
price: '',
stock: '',
discount: ''
},
//state validation
validation: [],
//config CKEDITOR
editorConfig: {
removePlugins: ['Title'],
}
}
},

//hook "asyncData"
async asyncData({ store, route }) {

//get list all categories


await store.dispatch('admin/category/getListAllCategories')

//get detail product


await store.dispatch('admin/product/getDetailProduct',
route.params.id)
},

//computed
computed: {

//categories
categories() {
return this.$store.state.admin.category.categories
},
},

502
//mounted
mounted() {
this.product.title =
this.$store.state.admin.product.product.title
this.product.category_id =
this.$store.state.admin.product.product.category_id
this.product.weight =
this.$store.state.admin.product.product.weight
this.product.stock =
this.$store.state.admin.product.product.stock
this.product.description =
this.$store.state.admin.product.product.description
this.product.price =
this.$store.state.admin.product.product.price
this.product.discount =
this.$store.state.admin.product.product.discount
},

methods: {

//handle file upload


handleFileChange(e) {

//get image
let image = this.product.image = e.target.files[0]

//check fileType
if (!image.type.match('image.*')) {

//if fileType not allowed, then clear value and set null
e.target.value = ''

//set state "product.image" to null


this.product.image = null

//show sweet alert


this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})
}

},

503
//method "updateProduct"
async updateProduct() {

//define formData
let formData = new FormData();

formData.append('image', this.product.image)
formData.append('title', this.product.title)
formData.append('category_id', this.product.category_id)
formData.append('description', this.product.description)
formData.append('weight', this.product.weight)
formData.append('price', this.product.price)
formData.append('stock', this.product.stock)
formData.append('discount', this.product.discount)
formData.append("_method", "PATCH")

//sending data to action "updateProduct" vuex


await this.$store.dispatch('admin/product/updateProduct', {
productId: this.$route.params.id,
payload: formData
})

//success
.then(() => {

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

//redirect route "admin-products"


this.$router.push({
name: 'admin-products'
})

})

//error
.catch(error => {

//assign error to state "validation"


this.validation = error.response.data

504
})
}
}

}
</script>

<style>
.ck-editor__editable {
min-height: 200px;
}
</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

//meta
head() {
return {
title: 'Edit Product - Administrator',
}
},

Kemudian kita mendaftarkan CKEDITOR di dalam properti components agar kita dapat
menggunakan CKEDITOR tersebut di dalam template.

'ckeditor-nuxt': () => {
if (process.client) {
return import('@blowstack/ckeditor-nuxt')
}
},

Dan di dalam data function kita membuat beberapa state, diantara yaitu product yang di

505
di dalamnya terdapat beberapa key lagi, state ini akan kita gunakan untuk menampung
sebuah value yang di kirim dari form sebelum dikirimkan ke Vuex.

//state product
product: {
image: '',
title: '',
category_id: '',
description: '',
weight: '',
price: '',
stock: '',
discount: ''
},

Masih di dalam data function, untuk state validation akan digunakan untuk menampung
sebuah error validasi yang di dapatkan dari Rest API.

//state validation
validation: [],

Dan kita membuat sedikit konfigurasi tambahan untuk CKEDITOR, yaitu menghapus plugin
title, karena kita tidak membutuhkan plugin tersebut di dalam kasus ini.

//config CKEDITOR
editorConfig: {
removePlugins: ['Title'],
}

Setelah itu, kita menggunakan hook asyncData untuk memanggil atau melakukan dispatch
ke dalam action Vuex, disini kita melakukan dispatch ke dalam 2 action, yaitu :

getListAllCategories - action ini berada di dalam file


store/admin/category.js dan fungsinya untuk melakukan http request ke server
untuk mendapatkan list data categories.
getDetailProduct - action ini berada di dalam file store/admin/product.js
dan digunakan untuk mendapatkan detail data product, dimana kita juga
menambahkan parameter berupa ID yang di ambil dari URL browser.

506
//hook "asyncData"
async asyncData({ store, route }) {

//get list all categories


await store.dispatch('admin/category/getListAllCategories')

//get detail product


await store.dispatch('admin/product/getDetailProduct',
route.params.id)
},

Setelah itu, kita akan ambil data category tersebut menggunakan properti computed, dan di
dalamnya akan mengambil langsung ke dalam state yang bernama categories yang
berada di dalam store/admin/category.js.

//computed
computed: {

//categories
categories() {
return this.$store.state.admin.category.categories
},
},

Dan di dalam properti mounted kita melakukan assign state yang sudah kita buat di dalam
data function dengan value yang di dapatkan daari state Vuex. Data inilah yang akan
ditampilkan di dalam form edit.

507
//mounted
mounted() {
this.product.title = this.$store.state.admin.product.product.title
this.product.category_id =
this.$store.state.admin.product.product.category_id
this.product.weight = this.$store.state.admin.product.product.weight
this.product.stock = this.$store.state.admin.product.product.stock
this.product.description =
this.$store.state.admin.product.product.description
this.product.price = this.$store.state.admin.product.product.price
this.product.discount =
this.$store.state.admin.product.product.discount
},

Kemudian di dalam method kita membuat 2 fungsi, yaitu :

handleFileChange
updateProduct

handleFileChange

Method ini akan dijalankan ketika input file di dalam template diubah, karena di dalam input
tersebut kita memberikan event @change yang mengarah ke dalam method ini.

<input type="file" @change="handleFileChange" class="form-control">

Di damana di dalam method handleFileChange pertama kita akan melakukan assign file
yang dipilih dari komputer ke dalam state category.image.

//get image
let image = this.product.image = e.target.files[0]

Setelah itu, kita membuat kondisi untuk memeriksa apakah file yang diupload tersebut
menggunakan ekstensi image atau bukan. Jika file yang di pilih buka format image, maka
kita akan melakukan beberapa aksi, yang pertama mengosongkan input file di formnya
terlebih dahulu.

508
//if fileType not allowed, then clear value and set null
e.target.value = ''

Setelah itu, state product.image kita juga set ke null.

//set state "product.image" to null


this.product.image = null

Kemudian kita tampilkan sebuah alert atau pesan bahwa file yang akan di upload tidak
sesuai dengan yang di harapkan.

//show sweet alert


this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})

updateProduct

Method ini akan kita gunakan untuk menampung data yang dikirim dari form sebelum kita
kirimkan ke dalam Vuex untuk di proses menggunakan Rest API.

Pertama-tama kita akan inisialisasi sebuah FormData terlebih dahulu, tujuannya untuk
menampung data yang ada ke dalam 1 tempat.

//define formData
let formData = new FormData();

Setelah itu, kita melakukan append beberapa data di dalam formData, dimana isinya
adalah state yang kita buat di dalam data function dan value-nya adalah input dari form.

509
formData.append('image', this.product.image)
formData.append('title', this.product.title)
formData.append('category_id', this.product.category_id)
formData.append('description', this.product.description)
formData.append('weight', this.product.weight)
formData.append('price', this.product.price)
formData.append('stock', this.product.stock)
formData.append('discount', this.product.discount)
formData.append("_method", "PATCH")

Untuk proses update, jangan lupa menambahkan key _method dengan value PATCH.

Setelah itu, kita kirimkan formData tersebut ke dalam action yang ada di Vuex dengan
nama updateProduct.

//sending data to action "updateProduct" vuex


await this.$store.dispatch('admin/product/updateProduct', {
productId: this.$route.params.id,
payload: formData
})

Di atas kita mengirim 2 data ke dalam Vuex, yaitu product ID yang di dapatkan dari URL
dan payload yang berisi formData.

Jika proses sending data berhasil dilakukan di dalam Vuex, maka kita akan menampilkan
sebuah alert menggunakan Sweet Alert2.

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Dan akan kita arahkan / redirect ke dalam route yang bernama admin-products.

510
//redirect route "admin-products"
this.$router.push({
name: 'admin-products'
})

tapi, jika data gagal dikirimkan, maka akan melakukan assign error response validasi dari
Rest API ke dalam state validation.

//assign error to state "validation"


this.validation = error.response.data

Langkah 4 - Uji Coba Proses Update Data Product


Sekarang kita akan lakukan uji coba untuk proses edit dan update data product. Silahkan
klik button EDIT di salah satu data yang ada di halaman index products, jika berhasil maka
kita akan mendapatkan halaman seperti berikut ini :

Setelah itu, silahkan diubah isi sesuai dengan kebutuhan dan untuk gambar bisa kita isi atau
kosongkan, setelah itu silahkan klik UPDATE dan jika berhasil maka kita akan mendapatkan
pesan berhasil update seperti berikut ini :

511
512
Membuat Proses Delete Data Product

Sekarang kita akan lanjutkan untuk membuat proses delete data product di dalam Nuxt.js,
disini kita akan menambahkan sebuah action terlebih dahulu, action inilah yang akan
digunakan untuk melakukan http request ke dalam server menggunakan Rest API untuk
menjalankan proses delete data.

Langkah 1 - Menambahkan Action di Vuex


Pertama, kita akan menambahkan action yang fungsinya untuk delete data product.
Silahkan buka file store/admin/product.js kemudian ubah semua kode-nya menjadi
seperti berikut ini :

//state
export const state = () => ({

//products
products: [],

//page
page: 1,

//product
product: {}

})

//mutations
export const mutations = {

//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {

//set value state "products"


state.products = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload

513
},

//mutation "SET_PRODUCT_DATA"
SET_PRODUCT_DATA(state, payload) {

//set value state "product"


state.product = payload
},

//actions
export const actions = {

//get products data


getProductsData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/products" with method "GET"


this.$axios.get(`/api/admin/products?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_PRODUCTS_DATA"


commit('SET_PRODUCTS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store product
storeProduct({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/products" with method "POST"

514
this.$axios.post('/api/admin/products', payload)

//success
.then(() => {

//dispatch action "getProductsData"


dispatch('getProductsData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//get detail product


getDetailProduct({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//get to Rest API "/api/admin/products/:id" with method


"GET"
this.$axios.get(`/api/admin/products/${payload}`)

//success
.then(response => {

//commit to mutation "SET_PRODUCT_DATA"


commit('SET_PRODUCT_DATA', response.data.data)

//resolve promise
resolve()

})

})

},

515
//update product
updateProduct({ dispatch, commit }, { productId, payload }) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/products/:id" with method


"POST"
this.$axios.post(`/api/admin/products/${productId}`,
payload)

//success
.then(() => {

//dispatch action "getProductsData"


dispatch('getProductsData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//destroy products
destroyProduct({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {
//delete to Rest API "/api/admin/products/:id" with method
"DELETE"
this.$axios.delete(`/api/admin/products/${payload}`)

//success
.then(() => {

//dispatch action "getProductsData"


dispatch('getProductsData')

//resolve promise

516
resolve()

})

})

Dari penambahan kode di atas, kita menambahkan 1 action baru dengan nama
destroyProduct.

//destroy products
destroyProduct({ dispatch, commit }, payload) {

//...
}

Di dalam action tersebut, kita melakukan http request menggunakan Axios dengan method
DELETE ke dalam endpoint api/admin/products/:id.

//delete to Rest API "/api/admin/products/:id" with method "DELETE"


this.$axios.delete(`/api/admin/products/${payload}`)

Jika proses delete berhasil dilakukan, maka kita akan melakukan dispatch atau menjalankan
sebuah action lagi dengan nama getProductsData, tujuannya agar setelah proses delete,
data yang ditampilkan bisa diperbarui.

//dispatch action "getProductsData"


dispatch('getProductsData')

Langkah 2 - Menambahkan Button dan Fungsi Delete


Setelah berhasil menambahkan action di dalam Vuex, maka kita akan lanjutkan untuk
menampilkan button delete di dalam table dan membuat fungsi delete. Silahkan buka file
pages/admin/products/index.vue, kemudian ubah semua kode-nya menjadi seperti

517
berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-layer-
group"></i> PRODUCTS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-products-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama product">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="products.data"


:fields="fields" show-empty>
<template v-slot:cell(actions)="row">
<b-button :to="{name: 'admin-products-edit-id',
params: {id: row.item.id}}" variant="info" size="sm">
EDIT
</b-button>
<b-button variant="danger" size="sm"
@click="destroyProduct(row.item.id)">DELETE</b-button>

518
</template>
</b-table>

<!-- pagination -->


<b-pagination align="right"
:value="products.current_page" :total-rows="products.total"
:per-page="products.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Products - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Product Name',
key: 'title',
},
{
label: 'Category Name',
key: 'category.name'
},
{
label: 'Stock',
key: 'stock',

519
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/product/getProductsData')
},

//computed
computed: {

//products
products() {
return this.$store.state.admin.product.products
},
},

//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/product/SET_PAGE', 1)

//dispatch on action "getProductsData"


this.$store.dispatch('admin/product/getProductsData',
this.search)
},
//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/product/SET_PAGE', page)

520
//dispatch on action "getProductsData"
this.$store.dispatch('admin/product/getProductsData',
this.search)
},

//method "destroyProduct"
destroyProduct(id) {
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {

//dispatch to action "deleteCategory" vuex


this.$store.dispatch('admin/product/destroyProduct', id)
.then(() => {

//feresh data
this.$nuxt.refresh()

//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

})
}
})
}
}

}
</script>

<style>

521
</style>

Di atas kita menambahkan 1 button baru, dimana button tersebut kita berikan event
@click yang mengarah ke dalam method yang bernama destroyProduct dan di dalam
method tersebut kita berikan parameter berupa ID dari product.

<b-button variant="danger" size="sm"


@click="destroyProduct(row.item.id)">DELETE</b-button>

Dan untuk method destroyProduct, di dalamnya kita menambahkan sebuah konfirmasi


alert menggunakan Sweet Alert2, yang fungsinya untuk menampilkan jendela konfirmasi
apakah yakin data akan benar-benar dihapus.

this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
})

Jika button konfirmasi di klik, maka kita akan melakukan dispatch atau memanggil sebuah
action dengan nama destroyProduct yang mana action tersebut berada di dalam
store/admin/product.js. Dan di dalam dispatch kita tambahkan parameter ID dari
data product.

//dispatch to action "deleteCategory" vuex


this.$store.dispatch('admin/product/destroyProduct', id)

Jika proses delete data menggunakan Vuex sukses dilakukan, maka kita akan melakukan
refresh project Nuxt.js menggunakan kode berikut ini :

522
//feresh data
this.$nuxt.refresh()

Setelah itu, kita tampilkan alert menggunakan Sweet Alert2 untuk status sukses delete data.

//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Langkah 3 - Uji Coba Proses Delete Data Product


Sekarang kita bisa mencobanya dengan membuka halaman products atau bisa melalui link
berikut ini http://localhost:3000/admin/products, jika berhasil maka kita akan mendapatkan
tampilan kurang lebih seperti berikut ini :

Jika kita klik button DELETE di salah satu data yang ada, maka akan menampilkan sebuah
konfirmasi kurang lebih seperti berikut ini :

523
Dan jika kita klik YA, HAPUS!, maka data akan berhasil dihapus dan kita mendapatkan
hasil seperti berikut ini :

524
konfigurasi Vuex Admin Invoice

Sebelum kita menampilkan data invoice dan detail datanya, maka kita perlu melakukan
konfigurasi Vuex-nya terlebih dahulu, yaitu membuat state, mutation dan juga action.
Tujuan menggunakan Vuex tidak lain karena kemudahan dalam proses maintenance dan
pengembangan kedepannya.

Konfigurasi Vuex Admin Invoice


Silahkan buat file baru dengan nama invoice.js di dalam folder store/admin/ dan
setelah itu masukkan kode berikut ini di dalamnya.

//state
export const state = () => ({

//invoices
invoices: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {

//set value state "invoices"


state.invoices = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

525
//actions
export const actions = {

//get invoices data


getInvoicesData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/invoices" with method "GET"


this.$axios.get(`/api/admin/invoices?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_INVOICES_DATA"


commit('SET_INVOICES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Dari penambahan kode di atas, pertama kita menambahkan 2 state baru, yaitu :

invoices - yang akan digunakan untuk menyimpan data invoices yang di dapatkan
dari Rest API.
page - yang digunakan untuk menyimpan nomor pagination halaman.

//invoices
invoices: [],

//page
page: 1,

Dan di dalam mutations, kita menambahkan 2 method baru, yaitu :

526
SET_INVOICES_DATA - digunakan untuk mengubah isi dari state invoices.
SET_PAGE - diguanakn untuk mengubah isi dari state page.

SET_INVOICES_DATA

Mutation ini akan digunakan untuk mengubah isi dari state invoices dengan data yang
dikirimkan melalui action. Dimana data tersebut akan di dapatkan dari hasil response Rest
API.

//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {

//set value state "invoices"


state.invoices = payload
},

SET_PAGE

Mutation ini akan digunakan untuk mengubah isi dari state page, dan datanya akan diambil
dari component / view yang mengirimkan parameter berupa nomor pagination.

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

Kemudian di dalam actions kita menambahkan 1 method baru yang bernama


getInvoicesData.

//get invoices data


getInvoicesData({ commit, state }, payload) {

//...
}

Di dalam action tersebut, pertama kita membuat sebuah variable baru dengan nama
search dan isinya akan diambil dari parameter yang bernama payload dan isinya nanti
akan di dapatkan dari component / view.

527
//search
let search = payload ? payload : ''

Setelah itu, kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/admin/invoices?q=""&page="". Untuk parameter q akan diisi
dari variable search dan untuk parameter page akan diambil dari state yang bernama
page.

//fetching Rest API "/api/admin/invoices" with method "GET"


this.$axios.get(`/api/admin/invoices?q=${search}&page=${state.page}`)

Jika proses fetching http request berhasil dilakukan, maka kita akan lakukan commit ke
dalam mutation yang bernama SET_INVOICES_DATA dengan menambahkan parameter
berupa data response yang di dapatkan dari Rest API.

//commit ti mutation "SET_INVOICES_DATA"


commit('SET_INVOICES_DATA', response.data.data)

528
Menampilkan Data Invoices

Setelah menambahkan state, mutation dan action di dalam Vuex, maka sekarang kita
bisa gunakan untuk menampilkan data di dalam component / view. Setelah data berhasil
ditampilkan, kita juga akan belajar membuat fitur pencarian data beserta pagination, yaitu
menampilkan navigasi untuk berpindah-pindah ke halaman lain.

Langkah 1 - Menampilkan Data Invoice


Silahkan buat folder baru dengan nama invoices di dalam folder pages/admin dan di
dalam folder invoices silahkan buat file baru dengan nama index.vue kemudian
masukkan kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-
cart"></i> INVOICE</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control"
placeholder="cari berdasarkan no. invoice">
<div class="input-group-append">
<button class="btn btn-warning"><i class="fa
fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="invoices.data"


:fields="fields" show-empty>

529
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'"
class="btn btn-sm btn-primary"><i class="fa fa-circle-notch fa-
spin"></i> {{ row.item.status }}</button>
<button v-if="row.item.status == 'success'"
class="btn btn-sm btn-success"><i class="fa fa-check-circle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'expired'"
class="btn btn-sm btn-warning-2"><i class="fa fa-exclamation-
triangle"></i> {{ row.item.status }}</button>
<button v-if="row.item.status == 'failed'"
class="btn btn-sm btn-danger"><i class="fa fa-times-circle"></i> {{
row.item.status }}</button>
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Invoices - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [
{

530
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Customer',
key: 'customer.name'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/invoice/getInvoicesData')
},

//computed
computed: {

//invoices
invoices() {
return this.$store.state.admin.invoice.invoices
},
},

}
</script>

<style>

</style>

531
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

//meta
head() {
return {
title: 'Invoices - Administrator',
}
},

Di dalam data function, kita membuat state baru dengan nama fields, state tersebut
nantinya akan digunakan dan di generate menjadi sebuah header di dalam table.

532
//table header
fields: [{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Customer',
key: 'customer.name'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

Dan untuk memanggil action Vuex, kita menggunakan hook asyncData dan di dalamnya
kita melakukan dispatch ke dalam action Vuex yang bernama getInvoicesData.

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/invoice/getInvoicesData')
},

Jika proses http request di dalam Vuex berhasil, maka kita sudah bisa mendapatkan datanya
dengan cara memanggil state yang ada di dalam Vuex.

Sekarang kita akan coba mengambil datanya menggunakan computed properti, kurang
lebih seperti berikut ini :

533
//computed
computed: {

//invoices
invoices() {
return this.$store.state.admin.invoice.invoices
},
},

Di atas, kita membuat method dengan nama invoices dan di dalamnya kita mengambil
data dari state yang bernama invoices yang berada di dalam
store/admin/invoice.js.

Dan untuk menampilkan data tersebut di dalam template, kita menggunakan component
dari Boostrap Vue, yaitu <b-table>. Kurang lebih seperti berikut ini :

<b-table striped bordered hover :items="invoices.data" :fields="fields"


show-empty>

//...
</b-table>

Di atas, ada properti yang bernama :items, properti tersebut akan berisi data yang ada di
dalam table dan isinya kita ambilkan dari method invoices yang ada di dalam properti
computed.

Dan untuk properti :fields akan digunakan untuk melakukan render table header dan
untuk isinya akan diambilkan dari state yang bernama fields yang kita buat di dalam data
function.

Dan di dalam <b-table> kita melakukan custom 2 data untuk di tampilkan, yang pertama
untuk menampilkan attribute grand_total, kita tampilkan menggunakan helper
formatPrice.

<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>

Dan yang kedua, kita gunakan untuk menampilkan button untuk status invoice.

534
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'" class="btn btn-sm btn-
primary"><i class="fa fa-circle-notch fa-spin"></i> {{ row.item.status
}}</button>
<button v-if="row.item.status == 'success'" class="btn btn-sm btn-
success"><i class="fa fa-check-circle"></i> {{ row.item.status
}}</button>
<button v-if="row.item.status == 'expired'" class="btn btn-sm btn-
warning-2"><i class="fa fa-exclamation-triangle"></i> {{ row.item.status
}}</button>
<button v-if="row.item.status == 'failed'" class="btn btn-sm btn-
danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>

Langkah 2 - Mengaktifkan Link Menu


Setelah berhasil membuat view atau component, maka secara otomatis Nuxt.js juga akan
membuatkan kita route dari file yang sudah kita buat di dalam halaman pages. Sekarang
kita akan mengaktifkan menu tersebut di dalam file component Sidebar.

Silahkan buka file components/admin/sidebar.vue, kemudian cari kode berikut ini :

<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-link">


<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
cart"></use>
</svg> Invoices</a>
</li>

Dan ubahlan menjadi seperti berikut ini :

535
<li class="c-sidebar-nav-item">
<nuxt-link :to="{name: 'admin-invoices'}" class="c-sidebar-nav-
link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
cart"></use>
</svg> Invoices
</nuxt-link>
</li>

Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam admin-invoices.

Sekarang silahkan klik menu Invoices yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/admin/invoices, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :

Di atas, tidak menampilkan data apapun, karena kita memang belum memiliki data di dalam
table invoices.

Langkah 3 - Membuat Fitur Pencarian


Sekarang kita lanjutkan untuk membuat fitur pencarian di dalam table, fitur ini akan
mempermudah kita jika ingin mencari data tertentu.

536
Silahkan buka pages/admin/invoices/index.vue kemudian ubah semua kode-nya
menjadi seperti berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-
cart"></i> INVOICE</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan no. invoice">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="invoices.data"


:fields="fields" show-empty>
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'"
class="btn btn-sm btn-primary"><i class="fa fa-circle-notch fa-
spin"></i> {{ row.item.status }}</button>
<button v-if="row.item.status == 'success'"
class="btn btn-sm btn-success"><i class="fa fa-check-circle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'expired'"
class="btn btn-sm btn-warning-2"><i class="fa fa-exclamation-

537
triangle"></i> {{ row.item.status }}</button>
<button v-if="row.item.status == 'failed'"
class="btn btn-sm btn-danger"><i class="fa fa-times-circle"></i> {{
row.item.status }}</button>
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Invoices - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [
{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Customer',
key: 'customer.name'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{

538
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/invoice/getInvoicesData')
},

//computed
computed: {

//invoices
invoices() {
return this.$store.state.admin.invoice.invoices
},
},

//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/invoice/SET_PAGE', 1)

//dispatch on action "getInvoicesData"


this.$store.dispatch('admin/invoice/getInvoicesData',
this.search)
}

539
}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.

//state search
search: ''

Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :

<input type="text" class="form-control" v-model="search"


@keypress.enter="searchData" placeholder="cari berdasarkan no. invoice">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i class="fa fa-
search"></i>
SEARCH
</button>
</div>

Di atas pada bagian input kita menambahkan event @keypress.enter dimana di


dalamnya akan memanggil method yang bernama searchData, di dalam button juga sama
kita menambahkan event @click yang kita arahkan ke dalam method searcData.

Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :

540
//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/invoice/SET_PAGE', 1)

//dispatch on action "getInvoicesData"


this.$store.dispatch('admin/invoice/getInvoicesData', this.search)
}

Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.

Dan yang kedua melakukan dispatch ke dalam action Vuex yang bernama
getInvoicesData dan kita parsing sebuah parameter yaitu this.search yang isinya
adalah kata kunci yang di dapatkan dari input form.

INFORMASI : Karena kita belum memiliki data apapun, maka kita belum bisa mencobanya
saat ini.

Langkah 4 - Membuat Fitur Pagination


Setelah berhasil membuat fitur pencarian, sekarang kita lanjutkan untuk menambahkan fitur
pagination, fitur ini digunakan untuk menampilkan button navigasi ke halaman-halaman
yang lain.

Silahkan buka file pages/admin/invoices/index.vue, kemudian ubah semua kode-nya


menjadi seperti berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-
cart"></i> INVOICE</span>
</div>
<div class="card-body">

541
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan no. invoice">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="invoices.data"


:fields="fields" show-empty>
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'"
class="btn btn-sm btn-primary"><i class="fa fa-circle-notch fa-
spin"></i> {{ row.item.status }}</button>
<button v-if="row.item.status == 'success'"
class="btn btn-sm btn-success"><i class="fa fa-check-circle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'expired'"
class="btn btn-sm btn-warning-2"><i class="fa fa-exclamation-
triangle"></i> {{ row.item.status }}</button>
<button v-if="row.item.status == 'failed'"
class="btn btn-sm btn-danger"><i class="fa fa-times-circle"></i> {{
row.item.status }}</button>
</template>
</b-table>

<!-- pagination -->


<b-pagination align="right"
:value="invoices.current_page" :total-rows="invoices.total"
:per-page="invoices.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

</div>
</div>
</div>
</div>
</div>

542
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Invoices - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [
{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Customer',
key: 'customer.name'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

543
//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/invoice/getInvoicesData')
},

//computed
computed: {

//invoices
invoices() {
return this.$store.state.admin.invoice.invoices
},
},

//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/invoice/SET_PAGE', 1)

//dispatch on action "getInvoicesData"


this.$store.dispatch('admin/invoice/getInvoicesData',
this.search)
},

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/invoice/SET_PAGE', page)

//dispatch on action "getInvoicesData"


this.$store.dispatch('admin/invoice/getInvoicesData',
this.search)
},

544
}
</script>

<style>

</style>

Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state invoices yang ada di dalam Vuex.

<!-- pagination -->


<b-pagination align="right" :value="invoices.current_page" :total-
rows="invoices.total" :per-page="invoices.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

Kemudian kita membuat 1 method baru dengan nama changePage, kurang lebih seperti
berikut ini :

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/invoice/SET_PAGE', page)

//dispatch on action "getInvoicesData"


this.$store.dispatch('admin/invoice/getInvoicesData', this.search)
},

Method di atas akan dijalankan ketika kita melakukan klik pada navigasi pagination. Dan di
dalam method tersebut kita menjalankan 2 perintah.

Yang pertama, kita melakukan commit ke dalam mutation yang bernama SET_PAGE yang
berada di dalam store/admin/invoice.js dan untuk parameternya berupa nomor
pagination yang di dapatkan dari component <b-paginate>.

545
//commit to mutation "SET_PAGE"
this.$store.commit('admin/invoice/SET_PAGE', page)

Yang kedua, kita melakukan dispatch ke dalam action yang bernama getInvoicesData,
tujuannya agar data yang ditampilkan sesuai dengan nomor pagination-nya. Dan kita
tambahkan parameter this.search apabila kita melakukan navigasi di dalam hasil
pencarian.

//dispatch on action "getInvoicesData"


this.$store.dispatch('admin/invoice/getInvoicesData', this.search)

Sekarang kita bisa mencoba dengan melakukan refresh di halaman invoices atau bisa
melihatnya melalui link http://localhost:3000/admin/invoices dan jika berhasil maka kita
akan mendapatkan tampilan kurang lebih seperti berikut ini :

546
Menampilkan Detail Data Invoice

Sebelum membuat component / view untuk menampilkan detail data invoice, maka kita
harus menambahkan sebuah state, mutation dan action terlebih dahulu di dalam Vuex.

Langkah 1 - Menambahkan State, Mutations dan Action di dalam


Vuex
Pertama kita akan menambahkan state dan action terlebih dahulu di dalam Vuex.
Silahkan buka file store/admin/invoice.js dan ubah kode-nya menjadi seperti berikut
ini :

//state
export const state = () => ({

//invoices
invoices: [],

//page
page: 1,

//invoice
invoice: {}

})

//mutations
export const mutations = {

//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {

//set value state "invoices"


state.invoices = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

547
//mutation "SET_INVOICE_DATA"
SET_INVOICE_DATA(state, payload) {

//set value state "invoice"


state.invoice = payload
},
}

//actions
export const actions = {

//get invoices data


getInvoicesData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/invoices" with method "GET"


this.$axios.get(`/api/admin/invoices?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_INVOICES_DATA"


commit('SET_INVOICES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//get detail invoice


getDetailInvoice({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//get to Rest API "/api/admin/invoices/:id" with method


"GET"
this.$axios.get(`/api/admin/invoices/${payload}`)

548
//success
.then(response => {

//commit to mutation "SET_INVOICE_DATA"


commit('SET_INVOICE_DATA', response.data.data)

//resolve promise
resolve()

})

})

},

Di atas, pertama kita menambahkan 1 state lagi dengan nama invoice. State tersebut
nantinya akan kita gunakan untuk menyimpan detail data invoice.

//invoice
invoice: {}

Kemudian di dalam mutation kita menambahkan 1 method baru dengan nama


SET_INVOICE_DATA, fungsinya akan kita gunakan untuk mengubah isi dari state invoice
dengan data yang dikirmkan dari action.

//mutation "SET_INVOICE_DATA"
SET_INVOICE_DATA(state, payload) {

//set value state "invoice"


state.invoice = payload
},

Dan di dalam action, kita menambahkan 1 method bari dengan nama getDetailInvoice.

549
//get detail invoice
getDetailInvoice({ commit }, payload) {

//...
}

Di dalam action tersebut, kita melakukan http request menggunakan Axios dengan method
GET ke dalam endpoint /api/admin/invoices/:id.

//get to Rest API "/api/admin/invoices/:id" with method "GET"


this.$axios.get(`/api/admin/invoices/${payload}`)

Jika proses http request tersebut berhasil dilakukan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_INVOICE_DATA dengan memberikan parameter berupa
data response dari Rest API.

//commit to mutation "SET_INVOICE_DATA"


commit('SET_INVOICE_DATA', response.data.data)

Langkah 2 - Membuat View Detail Invoice


Setelah menambahkan state, mutation dan action, sekarang kita lanjutkan membuat
sebuab component / view untuk menampilkan data.

Silahkan buat folder baru dengan nama show di dalam folder pages/admin/invoices dan
di dalam folder show silahkan buat file baru dengan nama _id.vue, kemudian masukkan
kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-

550
cart"></i> DETAIL INVOICE</span>
</div>
<div class="card-body">

<table class="table table-bordered">


<tr>
<td style="width: 25%">
NO. INVOICE
</td>
<td style="width: 1%">:</td>
<td>
{{ invoice.invoice }}
</td>
</tr>
<tr>
<td>
FULL NAME
</td>
<td>:</td>
<td>
{{ invoice.name }}
</td>
</tr>
<tr>
<td>
PHONE
</td>
<td>:</td>
<td>
{{ invoice.phone }}
</td>
</tr>
<tr>
<td>
COURIER / SERVICE / COST
</td>
<td>:</td>
<td>
{{ invoice.courier }} / {{ invoice.courier_service
}} / Rp.
{{ formatPrice(invoice.courier_cost) }}
</td>
</tr>
<tr>
<td>
CITY

551
</td>
<td>:</td>
<td>
{{ invoice.city.name }}
</td>
</tr>
<tr>
<td>
PROVINCE
</td>
<td>:</td>
<td>
{{ invoice.province.name }}
</td>
</tr>
<tr>
<td>
ADDRESS
</td>
<td>:</td>
<td>
{{ invoice.address }}
</td>
</tr>
<tr>
<td>
GRAND TOTAL
</td>
<td>:</td>
<td>
Rp. {{ formatPrice(invoice.grand_total) }}
</td>
</tr>
<tr>
<td>
STATUS
</td>
<td>:</td>
<td>
<button v-if="invoice.status == 'pending'"
class="btn btn-primary"><i class="fa fa-circle-
notch fa-spin"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'success'"
class="btn btn-success"><i class="fa fa-check-
circle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'expired'"

552
class="btn btn-warning-2"><i class="fa fa-
exclamation-triangle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'failed'"
class="btn btn-danger"><i class="fa fa-times-
circle"></i> {{ invoice.status }}</button>
</td>
</tr>
</table>

</div>
</div>

<div class="card border-0 rounded shadow-sm border-top-


orange mt-4">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
shopping-cart"></i> DETAIL ITEMS</span>
</div>
<div class="card-body">
<table class="table"
style="border-style: solid !important;border-color:
rgb(198, 206, 214) !important;">
<tbody>

<tr v-for="order in invoice.orders" :key="order.id"


style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="order.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ order.product.title }}</b></h5>
<table class="table-borderless" style="font-
size: 14px">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ order.qty
}}</b></td>
</tr>
</table>
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{

553
formatPrice(order.price) }}</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>

</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Detail Invoices - Administrator',
}
},

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/invoice/getDetailInvoice',
route.params.id)
},

//computed
computed: {
invoice() {
return this.$store.state.admin.invoice.invoice
}
},

}
</script>

<style>

554
</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

//meta
head() {
return {
title: 'Detail Invoices - Administrator',
}
},

Kemudian di dalam hook asyncData kita melakukan dispatch ke dalam action yang
bernama getDetailInvoice dengan memberikan parameter berupa invoice ID yang di
dapatkan dari URL browser.

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/invoice/getDetailInvoice',
route.params.id)
},

Jika proses di dalam Vuex berhasil dilakukan, maka sekarang kita tinggal memanggil
datanya dari state invoice yang ada di dalam Vuex.

Disini kita membuat computed properti yang di dalamnya kita membuat method yang
bernama invoice dan isinya memanggil state yang ada di dalam Vuex yang bernama
invoice.

555
//computed
computed: {
invoice() {
return this.$store.state.admin.invoice.invoice
}
},

Dan untuk menampilkan di dalam template, contohnya kita bisa seperti berikut ini :

<tr>
<td>
FULL NAME
</td>
<td>:</td>
<td>
{{ invoice.name }}
</td>
</tr>

Di atas, digunakan untuk menaggil attribute name dari method yang kita buat di dalam
computed properti yang bernama invoice.

Di dalam response JSON invoice kita memiliki turunan JSON lagi yaitu data orders, dan
untuk menampilkan data orders tersebut kita harus menggunakan directive v-for, karena
datanya bisa jadi lebih dari 1 atau jamak.

<tr v-for="order in invoice.orders" :key="order.id" style="background:


#edf2f7;">

//...
</tr>

INFORMASI : kita belum bisa melihat hasilnya sekarang, karena kita belum memiliki data di
dalam table invoices.

556
Konfigurasi Vuex Admin Customer

Pada materi kali ini kita akan belajar untuk membuat sebuah module Vuex untuk Customer,
dimana kita akan menambahkan state, mutations dan actions.

Konfigurasi Vuex Customer


Silahkan buat file baru dengan nama customner.js di dalam folder store/admin/ dan
setelah itu masukkan kode berikut ini di dalamnya.

//state
export const state = () => ({

//customers
customers: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_CUSTOMERS_DATA"
SET_CUSTOMERS_DATA(state, payload) {

//set value state "customers"


state.customers = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

//actions
export const actions = {

557
//get customers data
getCustomersData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/customers" with method "GET"


this.$axios.get(`/api/admin/customers?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_CUSTOMERS_DATA"


commit('SET_CUSTOMERS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Dari perubahan kode di atas, pertama kita menambahkan 2 state baru, yaitu :

customers - digunakan untuk menyimpan data customers.


page - digunakan untuk menyimpan nomor pagination halaman.

//customers
customers: [],

//page
page: 1,

Kemudian di dalam mutations kita menambahkan 2 method baru, yaitu :

SET_CUSTOMERS_DATA
SET_PAGE

558
SET_CUSTOMERS_DATA

Mutation ini akan digunakan untuk mengupdate nilai dari state customers dengan data
yang dikirimkan dari action, yaitu berupa data response dari Rest API.

//mutation "SET_CUSTOMERS_DATA"
SET_CUSTOMERS_DATA(state, payload) {

//set value state "customers"


state.customers = payload
},

SET_PAGE

Mutation ini akan digunakan untuk mengubah isi dari state page dengan data yang
dikirimkan oleh component / view, yaitu berupa nomor untuk pagination halaman.

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

Dan di dalam actions kita membuat 1 method baru dengan nama getCustomersData.

//get customers data


getCustomersData({ commit, state }, payload) {

//...
}

Di dalam action tersebut, pertama kita membuat sebuah variable baru dengan nama
search dan isinya akan diambil dari parameter yang bernama payload dan isinya nanti
akan di dapatkan dari component / view.

559
//search
let search = payload ? payload : ''

Kemudian kita melakukan http request menggunakan Axios dengan method GET ke dalam
endpoint /api/admin/customers?q=""&page="". Untuk parameter q akan diisi dari
variable search dan untuk parameter page akan diambil dari state yang bernama page.

//fetching Rest API "/api/admin/customers" with method "GET"


this.$axios.get(`/api/admin/customers?q=${search}&page=${state.page}`)

Jika proses http request berhasil dilakukan, maka kita akan melakukan commit ke dalam
mutation yang bernama SET_CUSTOMERS_DATA dengan memberikan parameter berupa
data response dari Rest API.

//commit ti mutation "SET_CUSTOMERS_DATA"


commit('SET_CUSTOMERS_DATA', response.data.data)

560
Menampilkan Data Customer

Di materi kali ini kita akan belajar bagaimana cara menampilkan data customer di dalam
component / view. Dan kita juga akan belajar membuat fitur pencarian data beserta fitur
pagination.

Langkah 1 - Menampilkan Data Customers


Silahkan buat folder baru dengan nama customers di dalam folder pages/admin dan di
dalam folder customers silahkan buat file baru dengan nama index.vue kemudian
masukkan kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
user"></i> CUSTOMERS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control"
placeholder="cari berdasarkan nama customer">
<div class="input-group-append">
<button class="btn btn-warning"><i class="fa fa-
search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="customers.data"


:fields="fields" show-empty>
</b-table>

561
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Customers - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Customer Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Joined',
key: 'created_at'
}
],
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/customer/getCustomersData')
},

562
//computed
computed: {

//customers
customers() {
return this.$store.state.admin.customer.customers
},
},

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

//meta
head() {
return {
title: 'Customers - Administrator',
}
},

Kemudian di dalam data function kita membuat 1 state dengan nama fields, state
tersebut nantinya akan digunakan untuk membuat header di dalam table.

563
//table header
fields: [{
label: 'Customer Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Joined',
key: 'created_at'
}
],

Dan di dalam hook asyncData, kita memanggil atau melakukan dispatch ke dalam action
Vuex yang bernama getCustomersData, dimana action tersebut berada di dalam
store/admin/customer.js.

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/customer/getCustomersData')
},

Setelah di dalam action berhasil melakukan http request ke dalam server dan mendapatkan
datanya, maka sekarang kita tinggal akses data tersebut dari state customers yang ada di
dalam Vuex.

Disini kita menggunakan computed properti untuk mendapatkan data tersebut.

//computed
computed: {

//customers
customers() {
return this.$store.state.admin.customer.customers
},
},

Di dalam computed properti di atas, kita membuat method dengan nama customers yang

564
isinya memanggil sebuah state yang bernama customers di dalam
store/admin/customer.js.

Setelah itu, kita akan tampilkan datanya di dalam template menggunakan component dari
Bootstrap Vue, yaitu <b-table>. Kurang lebih seperti berikut ini :

<b-table striped bordered hover :items="customers.data" :fields="fields"


show-empty>
</b-table>

Di atas, untuk properti :items akan digunakan untuk menampilkan isi dari table dan disini
kita berikan value dari computed properti, yaitu customers.

Dan untuk properti :fields akan digunakan untuk menampilkan header dari sebuah table
dan untuk isinya kita berikan dari state yang bernama fields yang ada di dalam data
function.

Langkah 2 - Mengaktifkan Link Menu


Setelah berhasil membuat view atau component, maka secara otomatis Nuxt.js juga akan
membuatkan kita route dari file yang sudah kita buat di dalam halaman pages. Sekarang
kita akan mengaktifkan menu tersebut di dalam file component Sidebar.

Silahkan buka file components/admin/sidebar.vue, kemudian cari kode berikut ini :

<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-link">


<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
user"></use>
</svg> Customers</a>
</li>

Dan ubahlah menjadi seperti berikut ini :

565
<li class="c-sidebar-nav-item">
<nuxt-link :to="{name: 'admin-customers'}" class="c-sidebar-nav-
link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
user"></use>
</svg> Customers
</nuxt-link>
</li>

Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam admin-customers.

Sekarang silahkan klik menu Customers yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/admin/customers, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :

Langkah 3 - Membuat Fitur Pencarian


Sekarang kita lanjutkan untuk membuat fitur pencarian di dalam table, fitur ini akan
mempermudah kita jika ingin mencari data tertentu.

Silahkan buka pages/admin/customers/index.vue kemudian ubah semua kode-nya


menjadi seperti berikut ini :

566
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
user"></i> CUSTOMERS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama customer">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="customers.data"


:fields="fields" show-empty>
</b-table>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout

567
layout: 'admin',

//meta
head() {
return {
title: 'Customers - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Customer Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Joined',
key: 'created_at'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/customer/getCustomersData')
},

//computed
computed: {

//customers
customers() {
return this.$store.state.admin.customer.customers
},
},

568
//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/customer/SET_PAGE', 1)

//dispatch on action "getCustomersData"


this.$store.dispatch('admin/customer/getCustomersData',
this.search)
},

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.

//state search
search: ''

Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :

569
<input type="text" class="form-control" v-model="search"
@keypress.enter="searchData" placeholder="cari berdasarkan nama
customer">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i class="fa fa-
search"></i>
SEARCH
</button>
</div>

Di atas pada bagian input kita menambahkan event @keypress.enter dimana di


dalamnya akan memanggil method yang bernama searchData, di dalam button juga sama
kita menambahkan event @click yang kita arahkan ke dalam method searcData.

Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/customer/SET_PAGE', 1)

//dispatch on action "getCustomersData"


this.$store.dispatch('admin/customer/getCustomersData', this.search)
},

Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.

Dan yang kedua melakukan dispatch ke dalam action Vuex yang bernama
getCustomersData dan kita parsing sebuah parameter yaitu this.search yang isinya
adalah kata kunci yang di dapatkan dari input form.

Sekarang silahkan refresh / reload halaman customers dan jika berhasil maka kita bisa
mencoba proses pencarian kurang lebih seperti berikut ini :

570
Langkah 4 - Membuat Fitur Pagination
Setelah berhasil membuat fitur pencarian, sekarang kita lanjutkan untuk menambahkan fitur
pagination, fitur ini digunakan untuk menampilkan button navigasi ke halaman-halaman
yang lain.

Silahkan buka file pages/admin/customers/index.vue, kemudian ubah semua kode-


nya menjadi seperti berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
user"></i> CUSTOMERS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari

571
berdasarkan nama customer">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="customers.data"


:fields="fields" show-empty>
</b-table>

<!-- pagination -->


<b-pagination align="right"
:value="customers.current_page" :total-rows="customers.total"
:per-page="customers.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Customers - Administrator',
}
},

//data function
data() {
return {
//table header

572
fields: [{
label: 'Customer Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Joined',
key: 'created_at'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/customer/getCustomersData')
},

//computed
computed: {

//customers
customers() {
return this.$store.state.admin.customer.customers
},
},

//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/customer/SET_PAGE', 1)

//dispatch on action "getCustomersData"


this.$store.dispatch('admin/customer/getCustomersData',
this.search)
},

573
//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/customer/SET_PAGE', page)

//dispatch on action "getCustomersData"


this.$store.dispatch('admin/customer/getCustomersData',
this.search)
},

}
</script>

<style>

</style>

Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state customers yang ada di dalam Vuex.

<b-pagination align="right" :value="customers.current_page" :total-


rows="customers.total" :per-page="customers.per_page"
@change="changePage" aria-controls="my-table"></b-pagination>

Kemudian kita membuat 1 method baru dengan nama changePage, method tersebut akan
di jalankan ketika kita melakukan navigasi di button pagination. kurang lebih seperti berikut
ini :

574
//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/customer/SET_PAGE', page)

//dispatch on action "getCustomersData"


this.$store.dispatch('admin/customer/getCustomersData', this.search)
},

Method di atas akan dijalankan ketika kita melakukan klik pada navigasi pagination. Dan di
dalam method tersebut kita menjalankan 2 perintah.

Yang pertama, kita melakukan commit ke dalam mutation yang bernama SET_PAGE yang
berada di dalam store/admin/customer.js dan untuk parameternya berupa nomor
pagination yang di dapatkan dari component <b-paginate>.

//commit to mutation "SET_PAGE"


this.$store.commit('admin/customer/SET_PAGE', page)

Yang kedua, kita melakukan dispatch ke dalam action yang bernama getCustomersData,
tujuannya agar data yang ditampilkan sesuai dengan nomor pagination-nya. Dan kita
tambahkan parameter this.search apabila kita melakukan navigasi di dalam hasil
pencarian.

//dispatch on action "getCustomersData"


this.$store.dispatch('admin/customer/getCustomersData', this.search)

Sekarang kita bisa mencoba dengan melakukan refresh di halaman customers atau bisa
melihatnya melalui link http://localhost:3000/admin/customers dan jika berhasil maka kita
akan mendapatkan tampilan kurang lebih seperti berikut ini :

575
576
Konfigurasi Vuex Admin Slider

Pada tahap kali ini kita akan belajar seperti sebelum-sebelumnya, yaitu menambahkan
sebuah state, mutation dan action yang nantinya akan digunakan untuk proses
menampilkan data sliders dari Rest API.

Konfigurasi Vuex Slider


Silahkan buat file baru dengan nama slider.js di dalam folder store/admin/ dan
setelah itu masukkan kode berikut ini di dalamnya.

//state
export const state = () => ({

//sliders
sliders: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {

//set value state "sliders"


state.sliders = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

//actions

577
export const actions = {

//get sliders data


getSlidersData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/sliders" with method "GET"


this.$axios.get(`/api/admin/sliders?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_SLIDERS_DATA"


commit('SET_SLIDERS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Dari penambahan kode di atas, pertama kita membuat 2 state baru yaitu sliders dan
page.

//sliders
sliders: [],

//page
page: 1,

State sliders akan digunakan untuk menyimpan data yang di dapatkan dari Rest API,
yang nantinya akan ditampilkan di dalam component / view.

Sedangkan untuk state page, digunakan untuk menyimpan nomor pagination halaman,
secara default kita berikan angka 1.

578
Kemudian, di dalam mutations kita membuat 2 method baru, yaitu :

SET_SLIDERS_DATA
SET_PAGE

SET_SLIDERS_DATA

Mutation ini akan kita gunakan untuk mengubah isi dari state sliders dengan data yang
dikirimkan oleh action, data tersebut merupakan response dari Rest API yang berupa data
sliders dari database.

//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {

//set value state "sliders"


state.sliders = payload
},

SET_PAGE

Mutation ini digunakan untuk mengubah isi dari state page dengan nomor pagination
halaman, dimana datanya nanti akan dikirimkan langsung melalui component / view.

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

Dan di dalam actions, kita membuat 1 method baru dengan nama getSlidersData,
method tersebut yang akan digunakan untuk melakukan http request ke dalam Rest API.

//get sliders data


getSlidersData({ commit, state }, payload) {

//...
}

Di dalam action tersebut, pertama kita membuat sebuah variable dengan nama search,

579
yang mana isinya akan diambil dari payload yang nantinya datanya akan dikirim dari
component / view.

//search
let search = payload ? payload : ''

Setelah itu, kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/admin/sliders?q=""&page="". Untuk parameter q akan diisi dari
variable search dan untuk parameter page akan diambil dari state yang bernama page.

//fetching Rest API "/api/admin/sliders" with method "GET"


this.$axios.get(`/api/admin/sliders?q=${search}&page=${state.page}`)

Jika proses fetching http request berhasil dilakukan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_SLIDERS_DATA dengan memberikan parameter berupa
response data yang didapatkan dari Rest API.

//commit ti mutation "SET_SLIDERS_DATA"


commit('SET_SLIDERS_DATA', response.data.data)

580
Membuat Layout Admin

Setelah berhasil membuat proses otentikasi di Nuxt.js, sekarang kita lanjutkan belajar untuk
membuat layout untuk halaman admin, yaitu memecah beberapa bagian template menjadi
component yang berbeda, tujuannya agar terlihat lebih rapi dan bisa digunakan secara
berulang-ulang.

Langkah 1 - Membuat Component Header


Pertama kita akan membuat component Header terlebih dahulu, silahkan buat folder baru
dengan nama admin di dalam folder components dan di dalam folder admin tersebut
silahkan buat file baru dengan nama header.vue, kemudian masukkan kode berikut ini di
dalamnya :

<template>
<header class="c-header c-header-light c-header-fixed c-header-with-
subheader">
<button class="c-header-toggler c-class-toggler d-lg-none mfe-auto"
type="button" data-target="#sidebar"
data-class="c-sidebar-show">
<svg class="c-icon c-icon-lg">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
menu"></use>
</svg>
</button>
<button class="c-header-toggler c-class-toggler mfs-3 d-md-down-
none" type="button" data-target="#sidebar"
data-class="c-sidebar-lg-show" responsive="true">
<svg class="c-icon c-icon-lg">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
menu"></use>
</svg>
</button>
<ul class="c-header-nav ml-auto mr-4">

<li class="c-header-nav-item dropdown"><a class="c-header-nav-


link" data-toggle="dropdown" href="#" role="button"
aria-haspopup="true" aria-expanded="false">
<div class="c-avatar"><img class="c-avatar-img"
:src="`https://ui-

581
avatars.com/api/?name=${user.name}&amp;background=4e73df&amp;color=fffff
f&amp;size=100`">
</div>
</a>
<div class="dropdown-menu dropdown-menu-right pt-0">
<div class="dropdown-header bg-light py-2
rounded"><strong>QUICK MENU</strong></div><a class="dropdown-item"
href="#">
<svg class="c-icon mr-2">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
folder"></use>
</svg> Categories</a><a class="dropdown-item" href="#">
<svg class="c-icon mr-2">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
layers"></use>
</svg> Products</a><a class="dropdown-item" href="#">
<svg class="c-icon mr-2">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
user"></use>
</svg> Customers</a>
<div class="dropdown-divider"></div>
<div class="dropdown-header bg-light py-2
rounded"><strong>ORDERS</strong></div><a class="dropdown-item" href="#">
<svg class="c-icon mr-2">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
cart"></use>
</svg> Invoices</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" role="button" @click="logout">
<svg class="c-icon mr-2">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-account-
logout"></use>
</svg> Logout
</a>
</div>
</li>
</ul>
</header>
</template>

<script>

582
export default {

//computed
computed: {
user() {
return this.$auth.user
}
},

//method
methods: {

//method "logout"
async logout() {

//logout auth
await this.$auth.logout()

//redirect route admin login


this.$router.push({
name: 'admin-login'
})

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita membuat sebuah properti computed, yang
mana di dalamnya kita mencoba untuk mendapatkan data user yang sedang login.

583
computed: {
user() {
return this.$auth.user
}
},

Di atas kita buat method user(), yang di dalamnya melakukan return ke dalam
this.$auth.user, yang mana isinya adalah data user yang sedang login. Dan di dalam
template kita bisa menampilkan data user seperti berikut ini :

{{ user.name }}

Kemudian kita juga menambahkan 1 method baru dengan nama logout, dimana method
tersebut akan di jalankan ketika link di template di klik.

<a class="dropdown-item" role="button" @click="logout">


<svg class="c-icon mr-2">
<use xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
account-logout"></use>
</svg> Logout
</a>

Dan di dalam method logout ini kita melakukan destroy data user yang sedang login
menggunakan kode berikut ini :

//logout auth
await this.$auth.logout()

Jika proses logout berhasil, maka kita arahkan ke dalam route admin-login.

//redirect route admin login


this.$router.push({
name: 'admin-login'
})

584
Langkah 2 - Membuat Component Sidebar
Setelah berhasil membuat component untuk Header, maka sekarang kita lanjutkan untuk
membuat component untuk Sidebar, component ini akan kita gunakan untuk menampilkan
list menu yang tersedia di dalam halalaman admin nantinya.

Silahkan buat file baru dengan nama sidebar.vue di dalam folder components/admin,
kemudian masukkan kode berikut ini :

<template>
<ul class="c-sidebar-nav">

<li class="c-sidebar-nav-item"><nuxt-link :to="{name: 'admin-


dashboard'}" class="c-sidebar-nav-link" href="index.html">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
speedometer"></use>
</svg> Dashboard</nuxt-link></li>

<li class="c-sidebar-nav-title">MASTER DATA</li>

<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-


link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
folder"></use>
</svg> Categories</a>
</li>

<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-


link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
layers"></use>
</svg> Products</a>
</li>

<li class="c-sidebar-nav-title">ORDERS</li>
<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-
link">
<svg class="c-sidebar-nav-icon">

585
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
cart"></use>
</svg> Invoices</a>
</li>

<li class="c-sidebar-nav-title">OTHERS</li>

<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-


link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
user"></use>
</svg> Customers</a>
</li>

<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-


link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
laptop"></use>
</svg> Sliders</a>
</li>

<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-


link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
group"></use>
</svg> Users</a>
</li>

<li class="c-sidebar-nav-divider"></li>
</ul>
</template>

<script>
export default {

}
</script>

<style scoped>

586
a.nuxt-link-active {
background: rgba(255,255,255,.05)!important;
}
</style>

Di atas kita hanya menambahkan sintaks HTML untuk menampilkan menu yang ada di
bagian sidebar. Kemudian ada fitu yang menarik di Nuxt.js, yaittu pada bagian style kita
menambahkan sebuah class CSS yang bernama a.nuxt-link-active, dimana CSS
tersebut akan di panggil secara otomatis ketika kita melakukan navigasi di dalam menu
atau route yang di pilih.

Langkah 3 - Membuat Layout Admin


Setelah berhasil membuat component Header dan juga Sidebar, sekarang kita lanjutkan
membuat layout untuk halaman admin. Dan kita juga akan memanggil kedua component
tersebut di layout ini.

Silahkan buat file baru dengan nama admin.vue di dalam folder layouts, setelah itu
masukkan kode berikut ini :

<template>
<div class="c-app">
<div class="c-sidebar c-sidebar-dark c-sidebar-fixed c-sidebar-lg-
show" id="sidebar">
<div class="c-sidebar-brand d-lg-down-none">
<img src="/images/xiaomi.png" class="bg-light rounded shadow-sm
p-2" width="35"> <span class="ml-2 font-weight-bold">MI STORE</span>
</div>

<!-- sidebar -->


<Sidebar />
<!-- end sidebar -->

</div>
<div class="c-wrapper c-fixed-components">
<!-- header -->
<Header />
<!-- end header -->

<div class="c-body">

<!-- content -->


<Nuxt />

587
<!-- end content -->

<footer class="c-footer">
<div><strong>MI STORE</strong> &copy; 2021 -
SantriKoding.com.</div>
<div class="ml-auto">Template by&nbsp;<a
href="https://coreui.io/">CoreUI</a></div>
</footer>
</div>
</div>
</div>
</template>

<script>

import Header from '@/components/admin/header.vue'


import Sidebar from '@/components/admin/sidebar.vue'

export default {

//middleware
middleware: 'isAdmin',

//register components
components: {
Header,
Sidebar
}

}
</script>

<style>
</style>

Di atas, pertama kita melakukan import 2 component yang sudah kita buat sebelumnya,
yaitu Header dan Footer.

import Header from '@/components/admin/header.vue'


import Sidebar from '@/components/admin/sidebar.vue'

Setelah itu, kita register kedua component tersebut agar dapat digunakan di dalam

588
template.

//register components
components: {
Header,
Sidebar
}

Dan di dalam template, untuk menampilkan component tersebut, kita cukup menggunakan
sintaks seperti berikut ini :

<!-- sidebar -->


<Sidebar />
<!-- end sidebar -->

<!-- header -->


<Header />
<!-- end header -->

<!-- content -->


<Nuxt />
<!-- end content -->

Untuk <Nuxt /> akan digunakan untuk merender sebuah view atau component yang
menggunakan layout ini nantinya.

Dan kita atur agar layout ini menggunakan middleware isAdmin, artinya hanya bisa
diakses jika user tersebut memiliki role atau peran sebagai admin.

//middleware
middleware: 'isAdmin',

589
Menampilkan Data Sliders

Setelah berhasil menambahkan beberapa konfigurasi di dalam module slider Vuex, maka
sekarang kita akan belajar bagaimana cara menampilkan data di dalam component / view.
Kita juga akan belajar membuat fitur pagination halaman.

Langkah 1 - Menampilkan Data Sliders


Silahkan buat folder baru dengan nama sliders di dalam folder pages/admin dan di
dalam folder sliders silahkan buat file baru dengan nama index.vue kemudian
masukkan kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
laptop"></i> SLIDERS</span>
</div>
<div class="card-body">

<nuxt-link :to="{name: 'admin-sliders-create'}"


class="btn btn-warning btn-sm mb-3 p-2">
<i class="fa fa-plus-circle"></i> ADD NEW</nuxt-
link>

<b-table striped bordered hover :items="sliders.data"


:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="200"
:src="data.item.image" />
</template>
</b-table>

</div>
</div>
</div>

590
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Sliders - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Link Slider',
key: 'link'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/slider/getSlidersData')
},

591
//computed
computed: {

//sliders
sliders() {
return this.$store.state.admin.slider.sliders
},
},

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

//meta
head() {
return {
title: 'Sliders - Administrator',
}
},

Kemudian di dalam data function, kita membuat state baru dengan nama fields, state
tersebut akan kita gunakan untuk membuat header dari sebuah table.

592
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Link Slider',
key: 'link'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

Dan di dalam hook asyncData, kita melakukan dispatch atau memanggil action yang ada di
dalam Vuex, yang bernama getSlidersData.

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/slider/getSlidersData')
},

Jika proses dari action di atas berhasil, maka kita tinggal mengambil datanya di dalam state
sliders yang ada di Vuex.

Disini kita menggunakan computed properti untuk mendapatkan datanya secara reactive.
Di dalam computed properti kita membuat method dengan nama sliders yang di
dalamnya memanggil sebuah state di dalam Vuex yang bernama sliders.

593
//computed
computed: {

//sliders
sliders() {
return this.$store.state.admin.slider.sliders
},
},

Dan untuk menampilkan di dalam template, kita menggunakan component dari Bootstrap
Vue yang bernama <b-table>.

<b-table striped bordered hover :items="sliders.data" :fields="fields"


show-empty>
//...
</b-table>

Di atas, untuk properti :items merupakan data yang akan di tampilkan di dalam table, dan
disini kita berikan value sliders yang merupakan nama method di dalam computed
properti.

Sedangkan untuk properti :fields akan digunakan untuk generate sebuah table header,
dan disini kita berikan value-nya dari state yang bernama fields yang sudah kita buat
sebelumnya di dalam data function.

Langkah 2 - Mengaktifkan Link Menu


Setelah berhasil membuat view atau component, maka secara otomatis Nuxt.js juga akan
membuatkan kita route dari file yang sudah kita buat di dalam halaman pages. Sekarang
kita akan mengaktifkan menu tersebut di dalam file component Sidebar.

Silahkan buka file components/admin/sidebar.vue, kemudian cari kode berikut ini :

594
<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
laptop"></use>
</svg> Sliders</a>
</li>

Dan ubahlah menjadi seperti berikut ini :

<li class="c-sidebar-nav-item">
<nuxt-link :to="{name: 'admin-sliders'}" class="c-sidebar-nav-link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
laptop"></use>
</svg> Sliders
</nuxt-link>
</li>

Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam admin-sliders.

Sekarang silahkan klik menu Sliders yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/admin/sliders, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :

595
Langkah 3 - Membuat Fitur Pagination
Sekarang kita lanjutkan untuk menambahkan fitur pagination, fitur ini digunakan untuk
menampilkan button navigasi ke halaman-halaman yang lain. Silahkan buka file
pages/admin/sliders/index.vue, kemudian ubah semua kode-nya menjadi seperti
berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
laptop"></i> SLIDERS</span>
</div>
<div class="card-body">

<nuxt-link :to="{name: 'admin-sliders-create'}"


class="btn btn-warning btn-sm mb-3 p-2">
<i class="fa fa-plus-circle"></i> ADD NEW</nuxt-
link>

596
<b-table striped bordered hover :items="sliders.data"
:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="200"
:src="data.item.image" />
</template>
</b-table>

<!-- pagination -->


<b-pagination align="right"
:value="sliders.current_page" :total-rows="sliders.total"
:per-page="sliders.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Sliders - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{

597
label: 'Link Slider',
key: 'link'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/slider/getSlidersData')
},

//computed
computed: {

//sliders
sliders() {
return this.$store.state.admin.slider.sliders
},
},

//method
methods: {

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/slider/SET_PAGE', page)

//dispatch on action "getSlidersData"


this.$store.dispatch('admin/slider/getSlidersData',
this.search)
},
}

}
</script>

<style>

598
</style>

Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state sliders yang ada di dalam Vuex.

<b-pagination align="right" :value="sliders.current_page" :total-


rows="sliders.total" :per-page="sliders.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

Kemudian kita membuat 1 method baru dengan nama changePage, method tersebut akan
di jalankan ketika kita melakukan navigasi di button pagination. kurang lebih seperti berikut
ini :

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/slider/SET_PAGE', page)

//dispatch on action "getSlidersData"


this.$store.dispatch('admin/slider/getSlidersData', this.search)
},

Method di atas akan dijalankan ketika kita melakukan klik pada navigasi pagination. Dan di
dalam method tersebut kita menjalankan 2 perintah.

Yang pertama, kita melakukan commit ke dalam mutation yang bernama SET_PAGE yang
berada di dalam store/admin/slider.js dan untuk parameternya berupa nomor
pagination yang di dapatkan dari component <b-paginate>.

//commit to mutation "SET_PAGE"


this.$store.commit('admin/slider/SET_PAGE', page)

Yang kedua, kita melakukan dispatch ke dalam action yang bernama getSlidersData,
tujuannya agar data yang ditampilkan sesuai dengan nomor pagination-nya. Dan kita
tambahkan parameter this.search apabila kita melakukan navigasi di dalam hasil
pencarian.

599
//dispatch on action "getSlidersData"
this.$store.dispatch('admin/slider/getSlidersData', this.search)

Sekarang kita bisa mencoba dengan melakukan refresh di halaman sliders atau bisa
melihatnya melalui link http://localhost:3000/admin/sliders dan jika berhasil maka kita akan
mendapatkan tampilan kurang lebih seperti berikut ini :

600
Membuat Proses Insert Data Slider

Sebelum membuat component / view untuk menampilkan form upload data slider, maka kita
perlu menambahkan 1 action lagi di dalam Vuex, action tersebut yang nantinya akan
digunakan untuk melakukan sending data ke dalam server melalui Rest API.

Langkah 1 - Menambahkan Action di Vuex Slider


Pertama, kita akan menambahkan 1 action baru di dalam Vuex slider, silahkan buka file
store/admin/slider.js kemudian ubah semua kode-nya menjadi seperti berikut ini :

//state
export const state = () => ({

//sliders
sliders: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {

//set value state "sliders"


state.sliders = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

//actions

601
export const actions = {

//get sliders data


getSlidersData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/sliders" with method "GET"


this.$axios.get(`/api/admin/sliders?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_SLIDERS_DATA"


commit('SET_SLIDERS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store slider
storeSlider({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/sliders" with method "POST"


this.$axios.post('/api/admin/sliders', payload)

//success
.then(() => {

//dispatch action "getSlidersData"


dispatch('getSlidersData')

//resolve promise
resolve()

})

602
//error
.catch(error => {
reject(error)
})

})
},

Dari penambahan kode di atas, kita tambahkan 1 action baru dengan nama storeSlider,
action tersebutlah yang nantinya akan digunakan untuk mengirim data ke dalam server
menggunakan Rest API.

//store slider
storeSlider({ dispatch, commit }, payload) {

//...
}

Di dalam action tersebut, kita melakukan sending data menggunakan Axios dengan
method POST ke dalam endpoint /api/admin/sliders dan datanya kita parsing dengan
nama payload. Data tersebut nantinya akan dikirim dari component / view.

//store to Rest API "/api/admin/sliders" with method "POST"


this.$axios.post('/api/admin/sliders', payload)

Jika proses sending data berhasil dilakuka, maka kita akan melakukan dispatch atau
memanggil action yang bernama getSlidersData, tujuannya agar melakukan fetching
data ulang dan mendapatkan data baru untuk ditampilkan.

//dispatch action "getSlidersData"


dispatch('getSlidersData')

Langkah 2 - Membuat View Tambah Slider


Disini kita akan membuat sebuah file view atau component baru yang akan digunakan untuk

603
menampilkan halaman tambah data dan sekaligus untuk proses insert data melalui Rest API.

Silahkan buat sebuah folder baru dengan nama create di dalam folder
pages/admin/sliders dan di dalam folder create silahkan buat file baru dengan nama
index.vue, kemudian masukkan kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> ADD NEW SLIDER</span>
</div>
<div class="card-body">

<form @submit.prevent="storeSlider">

<div class="form-group">
<label>GAMBAR</label>
<input type="file" @change="handleFileChange"
class="form-control">
<div v-if="validation.image" class="mt-2">
<b-alert show variant="danger">{{
validation.image[0] }}</b-alert>
</div>
</div>

<div class="form-group">
<label>LINK SLIDER</label>
<input type="text" v-model="slider.link"
placeholder="Masukkan Link Slider" class="form-control">
</div>

<button class="btn btn-info mr-1 btn-submit"


type="submit"><i class="fa fa-paper-plane"></i>
SAVE</button>
<button class="btn btn-warning btn-reset"
type="reset"><i class="fa fa-redo"></i>
RESET</button>

604
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Add New Slider - Administrator',
}
},

data() {
return {
//state slider
slider: {
image: '',
'link': ''
},
//state validation
validation: []
}
},

methods: {

//handle file upload


handleFileChange(e) {

//get image
let image = this.slider.image = e.target.files[0]

//check fileType
if (!image.type.match('image.*')) {

605
//if fileType not allowed, then clear value and set null
e.target.value = ''

//set state "slider.image" to null


this.slider.image = null

//show sweet alert


this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})
}

},

//method "storeSlider"
async storeSlider() {

//define formData
let formData = new FormData();

formData.append('image', this.slider.image)
formData.append('link', this.slider.link)

//sending data to action "storeSlider" vuex


await this.$store.dispatch('admin/slider/storeSlider', formData)
//success
.then(() => {

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

//redirect route "admin-sliders"


this.$router.push({
name: 'admin-sliders'
})

606
})

//error
.catch(error => {

//assign error to state "validation"


this.validation = error.response.data
})
}
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

//meta
head() {
return {
title: 'Add New Slider - Administrator',
}
},

Di dalam data function, kita membuat 2 state baru, yaitu slider dan validation.

607
//state slider
slider: {
image: '',
'link': ''
},

//state validation
validation: []

State slider akan digunakan untuk menyimpan data yang di ketik di dalam input form dan
di dalam state ini kita tambahkan 2 key di dalamnya, yaitu image dan link.

Sedangkan untuk state validation akan digunakan untuk menyimpan response error
validasi yang di dapatkan dari Rest API.

Kemudian di dalam method kita membuat 2 fungsi, yaitu :

handleFileChange
storeSlider

handleFileChange

Method ini akan dijalankan ketika input file di dalam template diubah, karena di dalam input
tersebut kita memberikan event @change yang mengarah ke dalam method ini.

<input type="file" @change="handleFileChange" class="form-control">

Di damana di dalam method handleFileChange pertama kita akan melakukan assign file
yang dipilih dari komputer ke dalam state slider.image.

//get image
let image = this.slider.image = e.target.files[0]

Setelah itu, kita membuat kondisi untuk memeriksa apakah file yang diupload tersebut
menggunakan ekstensi image atau bukan. Jika file yang di pilih buka format image, maka
kita akan melakukan beberapa aksi, yang pertama mengosongkan input file di formnya
terlebih dahulu.

608
//if fileType not allowed, then clear value and set null
e.target.value = ''

Setelah itu, state slider.image kita juga set ke null.

//set state "slider.image" to null


this.slider.image = null

Kemudian kita tampilkan sebuah alert atau pesan bahwa file yang akan di upload tidak
sesuai dengan yang di harapkan.

//show sweet alert


this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})

storeSlider

Method ini akan digunakan untuk menampung data dari form sebelum dikirimkan ke dalam
action Vuex. Disini pertama kita melakukan inisialisasi sebuah FormData.

//define formData
let formData = new FormData();

Setelah itu, kita append data yang diambil dari input form ke dalam formData. Tujuannya
agar lebih mudah dalam pengiriman data tersebut.

formData.append('image', this.slider.image)
formData.append('link', this.slider.link)

Dan sekarang kita akan kirimkan formData tersebut ke dalam action Vuex yang bernama

609
storeSlider.

//sending data to action "storeSlider" vuex


await this.$store.dispatch('admin/slider/storeSlider', formData)

Jika proses sending berhasil dilakukan di dalam Vuex, maka kita akan menampilkan alert
sukses menggunakan Sweet Alert2.

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Setelah itu, kita arahkan / redirect ke dalam route yang bernama admin-sliders.

//redirect route "admin-sliders"


this.$router.push({
name: 'admin-sliders'
})

Langkah 3 - Uji Coba Proses Insert Data Slider


Sekarang kita akan lakukan uji coba untuk proses insert data slider. Silahkaan klik button
TAMBAH yang berada di halaman index sliders atau bisa menggunakan link berikut ini
http://localhost:3000/admin/sliders/create jika berhasil maka kita akan mendapatkan hasil
seperti berikut ini :

610
Silahkan masukkan data gambar dan link (opsional), dan jika berhasil kita akan
mendapatkan hasil seperti berikut ini :

611
Membuat Proses Delete Data Slider

Sama seperti proses insert data slider, sebelum kita menambahkan fitur delete data slider,
maka kita perlu menambahkan action terlebih dahulu di dalam Vuex. Action tersebutlah
yang nanti bertugas untuk melakukan http request ke dalam server menggunakan Rest API
untuk menjalankan proses delete data.

Langkah 1 - Menambahkan Action di Vuex Slider


Silahkan buka file store/admin/slider.js kemudian ubah semua kode yang ada di
dalamnya menjadi seperti berikut ini :

//state
export const state = () => ({

//sliders
sliders: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {

//set value state "sliders"


state.sliders = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

612
//actions
export const actions = {

//get sliders data


getSlidersData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/sliders" with method "GET"


this.$axios.get(`/api/admin/sliders?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_SLIDERS_DATA"


commit('SET_SLIDERS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store slider
storeSlider({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/sliders" with method "POST"


this.$axios.post('/api/admin/sliders', payload)

//success
.then(() => {

//dispatch action "getSlidersData"


dispatch('getSlidersData')

//resolve promise
resolve()

613
})

//error
.catch(error => {
reject(error)
})

})
},

//destroy slider
destroySlider({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {
//delete to Rest API "/api/admin/sliders/:id" with method
"DELETE"
this.$axios.delete(`/api/admin/sliders/${payload}`)

//success
.then(() => {

//dispatch action "getSlidersData"


dispatch('getSlidersData')

//resolve promise
resolve()

})

})

Di atas, kita menambahkan 1 action baru dengan nama destroySlider, action tersebut
yang nanti akan digunakan untuk menangani proses delete data ke dalam server
menggunakan Rest API.

614
//destroy slider
destroySlider({ dispatch, commit }, payload) {

//...
}

Di dalam action tersebut, kita melakukan http request menggunakan Axios dengan method
DELETE ke dalam endpoint /api/admin/sliders/:id.

//delete to Rest API "/api/admin/sliders/:id" with method "DELETE"


this.$axios.delete(`/api/admin/sliders/${payload}`)

Jika proses delete data berhasil dilakukan, maka kita akan melakukan dispatch ke dalam
action yang bernama getSlidersData, tujuannya untuk melakukan fetching ulang agar
data yang ditampilkan bisa sesuai.

//dispatch action "getSlidersData"


dispatch('getSlidersData')

Langkah 2 - Menambahkan Button dan Fungsi Delete


Setelah berhasil menambahkan action di dalam Vuex, maka kita akan lanjutkan untuk
menampilkan button delete di dalam table dan membuat fungsi delete. Silahkan buka file
pages/admin/sliders/index.vue, kemudian ubah semua kode-nya menjadi seperti
berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
laptop"></i> SLIDERS</span>
</div>

615
<div class="card-body">

<nuxt-link :to="{name: 'admin-sliders-create'}"


class="btn btn-warning btn-sm mb-3 p-2">
<i class="fa fa-plus-circle"></i> ADD NEW</nuxt-
link>

<b-table striped bordered hover :items="sliders.data"


:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="200"
:src="data.item.image" />
</template>
<template v-slot:cell(actions)="row">
<b-button variant="danger" size="sm"
@click="destroySlider(row.item.id)">DELETE</b-button>
</template>
</b-table>

<!-- pagination -->


<b-pagination align="right"
:value="sliders.current_page" :total-rows="sliders.total"
:per-page="sliders.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Sliders - Administrator',
}
},

616
//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Link Slider',
key: 'link'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/slider/getSlidersData')
},

//computed
computed: {

//sliders
sliders() {
return this.$store.state.admin.slider.sliders
},
},

//method
methods: {

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/slider/SET_PAGE', page)

//dispatch on action "getSlidersData"

617
this.$store.dispatch('admin/slider/getSlidersData',
this.search)
},

//method "destroySlider"
destroySlider(id) {
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {

//dispatch to action "destroySlider" vuex


this.$store.dispatch('admin/slider/destroySlider', id)
.then(() => {

//feresh data
this.$nuxt.refresh()

//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

})
}
})
}
}

}
</script>

<style>

</style>

618
Di atas kita menambahkan 1 button baru, dimana button tersebut kita berikan event
@click yang mengarah ke dalam method yang bernama destroySlider dan di dalam
method tersebut kita berikan parameter berupa ID dari slider.

<b-button variant="danger" size="sm"


@click="destroySlider(row.item.id)">DELETE</b-button>

Dan di dalam method destroySlider pertama-tama kita menampilkan jendela konfirmasi


menggunakan Sweet Alert2.

this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
})

Jika kita klik button konfirmasi, maka akan melakukan dispatch ke dalam action yang
bernama destroySlider dengan parameter ID dari slider.

//dispatch to action "destroySlider" vuex


this.$store.dispatch('admin/slider/destroySlider', id)

Dan jika di dalam action Vuex proses delete data berhasil dilakukan, maka kita akan
melakukan refresh website-nya dengan kode berikut ini :

//feresh data
this.$nuxt.refresh()

Kode di atas digunakan untuk memperbarui data di dalam website tanpa perlu melakukan
reload halaman. Dan setelah itu kita menampilkan alert menggunakan Sweet Alert2.

619
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Langkah 3 - Uji Coba Proses Delete Data Slider


Sekarang kita bisa mencobanya dengan membuka halaman sliders atau bisa melalui link
berikut ini http://localhost:3000/admin/sliders, jika berhasil maka kita akan mendapatkan
tampilan kurang lebih seperti berikut ini :

Silahkan klik button DELETE di salah satu data yang kita miliki, jika berhasil maka akan
menampilkan halaman konfirmasi menggunakan Sweet Alert2.

620
Dan jika kita klik YA, HAPUS!, maka data akan terhapus dan kita mendapatkan alert sukses
menggunakan Sweet Alert2.

621
Konfigurasi Vuex Admin User

Pada materi kali ini kita akan membuat module Vuex untuk user dan disini kita akan
menambahkan state, mutations dan actions untuk kita gunakan mengambil data dari
server / Rest API ke dalam Nuxt.js.

Konfigurasi Vuex User


Silahkan buat file baru dengan nama user.js di dalam folder store/admin/ dan setelah
itu masukkan kode berikut ini di dalamnya.

//state
export const state = () => ({

//users
users: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_USERS_DATA"
SET_USERS_DATA(state, payload) {

//set value state "users"


state.users = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

//actions

622
export const actions = {

//get users data


getUsersData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/users" with method "GET"


this.$axios.get(`/api/admin/users?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_USERS_DATA"


commit('SET_USERS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Dari penambahan kode di atas, pertama kita membuat 2 state baru yaitu users dan page.

//users
users: [],

//page
page: 1,

State users akan digunakan untuk menyimpan data users yang di dapatkan dari Rest API
secara reactive. Sedangkan untuk state page akan digunakan untuk menyimpan nomor
pagination halaman.

Dan di dalam mutations kita membuat 2 method baru, yaitu :

623
SET_USERS_DATA
SET_PAGE

SET_USERS_DATA

Mutation ini akan digunakan untuk melakukan assign atau mengubah isi dari state users
dengan data berupa response dari Rest API yang dikirimkan melalui action.

//mutation "SET_USERS_DATA"
SET_USERS_DATA(state, payload) {

//set value state "users"


state.users = payload
},

SET_PAGE

Mutation ini akan digunakan untuk mengubah isi dari state page dengan data berupa nomor
pagination halaman yang nanti dikirim langsung dari component / view.

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

Dan di dalam actions, kita membuat 1 method baru dengan nama getUsersData, method
tersebut yang akan digunakan untuk melakukan http request ke dalam Rest API.

//get users data


getUsersData({ commit, state }, payload) {

//...
}

Di dalam action tersebut, pertama kita membuat sebuah variable dengan nama search,
yang mana isinya akan diambil dari payload yang nantinya datanya akan dikirim dari
component / view.

624
//search
let search = payload ? payload : ''

Setelah itu, kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/admin/users?q=""&page="". Untuk parameter q akan diisi dari
variable search dan untuk parameter page akan diambil dari state yang bernama page.

//fetching Rest API "/api/admin/users" with method "GET"


this.$axios.get(`/api/admin/users?q=${search}&page=${state.page}`)

Jika proses fetching http request berhasil dilakukan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_USERS_DATA dengan memberikan parameter berupa
response data yang didapatkan dari Rest API.

//commit ti mutation "SET_USERS_DATA"


commit('SET_USERS_DATA', response.data.data)

625
Menampilkan Data Users

Setelah menambahkan state, mutations dan actions, sekarang kita lanjutkan untuk
proses menampilkan data di dalam component / view. Alurnya nanti component / view akan
memanggil action di dalam Vuex untuk melakukan http request ke dalam server
menggunakan Rest API. Setelah itu data yang di dapatkan akan di commit ke dalam
mutation dan di dalam mutation akan melakukan assign atau update state dengan data
response tersebut.

Setelah data ada di dalam state Vuex, selanjutnya component / view memanggil state
tersebut agar mendapatkan datanya dan bisa ditampilkam ke layar browser.

Langkah 1 - Menampilkan Data User


Silahkan buat folder baru dengan nama users di dalam folder pages/admin dan di dalam
folder users silahkan buat file baru dengan nama index.vue kemudian masukkan kode
berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> USERS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-users-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control"
placeholder="cari berdasarkan nama user">
<div class="input-group-append">

626
<button class="btn btn-warning"><i class="fa
fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="users.data"


:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50"
:src="data.item.image" />
</template>
</b-table>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Users - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'User Name',
key: 'name'
},

627
{
label: 'Email Address',
key: 'email'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/user/getUsersData')
},

//computed
computed: {

//users
users() {
return this.$store.state.admin.user.users
},
},

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

628
//meta
head() {
return {
title: 'Users - Administrator',
}
},

Dan di dalam data function kita membuat state dengan nama fields, state tersebut
nantinya akan digenerate mernjadi header dari sebuah table.

//table header
fields: [{
label: 'User Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

kemudian kita melakukan dispatch ke dalam action Vuex yang bernama getUsersData di
dalam hook asyncData, tujuannya agar dijalankan secara SSR atau server side rendering.

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/user/getUsersData')
},

Setelah proses di dalam action tersebut berhasil, sekarang kita tinggal mengambil data
users yang ada di dalam state Vuex. Disini kita menggunakan computed properti untuk
memanggil data dari state Vuex. Kurang lebih seperti berikut ini :

629
//computed
computed: {

//users
users() {
return this.$store.state.admin.user.users
},
},

Di dalam computed propeti kita membuat method yang bernama users yang di dalamnya
akan memanggil sebuah state yang bernama users yang berada di dalam
store/admin/user.js.

Setelah data di ambil dan diletakkan di dalam computed properti, maka sekarang kita
tinggal menampilkannya di dalam component / view. Dan disini kita menggunakan
component dari Bootstrap Vue, yaitu <b-table>, kurang lebih seperti berikut ini :

<b-table striped bordered hover :items="users.data" :fields="fields"


show-empty>
//...
</b-table>

Di atas, untuk properti :items akan berisi data yang ada di dalam table dan kita akan
berikan value dengan nama method yang ada di dalam computed properti, yaitu users.

Sedangkan untuk properti :fields akan digunakan untuk menampilkan header dari table
dan disini kita berikan value dari state yang bernama fields yang sebelumnya sudah kita
buat di dalam data function.

Langkah 2 - Mengaktifkan Link Menu


Setelah berhasil membuat view atau component, maka secara otomatis Nuxt.js juga akan
membuatkan kita route dari file yang sudah kita buat di dalam halaman pages. Sekarang
kita akan mengaktifkan menu tersebut di dalam file component Sidebar.

Silahkan buka file components/admin/sidebar.vue, kemudian cari kode berikut ini :

630
<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
group"></use>
</svg> Users</a>
</li>

Dan ubahlah menjadi seperti berikut ini :

<li class="c-sidebar-nav-item">
<nuxt-link :to="{name: 'admin-users'}" class="c-sidebar-nav-link">
<svg class="c-sidebar-nav-icon">
<use
xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
group"></use>
</svg> Users
</nuxt-link>
</li>

Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam admin-users.

Sekarang silahkan klik menu Users yang ada di menu Sidebar atau bisa langsung ke URL
berikut ini http://localhost:3000/admin/users, maka jika berhasil kita akan mendapatkan
tampilan seperti berikut ini :

631
Langkah 3 - Membuat Fitur Pencarian
Sekarang kita lanjutkan untuk membuat fitur pencarian di dalam table, fitur ini akan
mempermudah kita jika ingin mencari data tertentu.

Silahkan buka pages/admin/users/index.vue kemudian ubah semua kode-nya menjadi


seperti berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> USERS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-users-

632
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama user">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="users.data"


:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50"
:src="data.item.image" />
</template>
</b-table>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Users - Administrator',
}
},

633
//data function
data() {
return {
//table header
fields: [{
label: 'User Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/user/getUsersData')
},

//computed
computed: {

//users
users() {
return this.$store.state.admin.user.users
},
},

//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/user/SET_PAGE', 1)

634
//dispatch on action "getUsersData"
this.$store.dispatch('admin/user/getUsersData', this.search)
},
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.

//state search
search: ''

Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :

<input type="text" class="form-control" v-model="search"


@keypress.enter="searchData" placeholder="cari berdasarkan nama user">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i class="fa fa-
search"></i>
SEARCH
</button>
</div>

Di atas pada bagian input kita menambahkan event @keypress.enter dimana di


dalamnya akan memanggil method yang bernama searchData, di dalam button juga sama
kita menambahkan event @click yang kita arahkan ke dalam method searcData.

Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :

635
//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/user/SET_PAGE', 1)

//dispatch on action "getUsersData"


this.$store.dispatch('admin/user/getUsersData', this.search)
},

Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.

Dan yang kedua melakukan dispatch ke dalam action Vuex yang bernama getUsersData
dan kita parsing sebuah parameter yaitu this.search yang isinya adalah kata kunci yang
di dapatkan dari input form.

Sekarang silahkan refresh / reload halaman users dan jika berhasil maka kita bisa mencoba
proses pencarian kurang lebih seperti berikut ini :

Langkah 4 - Membuat Fitur Pagination


Setelah berhasil membuat fitur pencarian, sekarang kita lanjutkan untuk menambahkan fitur
pagination, fitur ini digunakan untuk menampilkan button navigasi ke halaman-halaman
yang lain.

636
Silahkan buka file pages/admin/users/index.vue, kemudian ubah semua kode-nya
menjadi seperti berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> USERS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-users-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama user">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="users.data"


:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50"
:src="data.item.image" />
</template>
</b-table>

637
<!-- pagination -->
<b-pagination align="right" :value="users.current_page"
:total-rows="users.total"
:per-page="users.per_page" @change="changePage" aria-
controls="my-table"></b-pagination>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Users - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'User Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

638
//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/user/getUsersData')
},

//computed
computed: {

//users
users() {
return this.$store.state.admin.user.users
},
},

//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/user/SET_PAGE', 1)

//dispatch on action "getUsersData"


this.$store.dispatch('admin/user/getUsersData', this.search)
},
//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/user/SET_PAGE', page)

//dispatch on action "getUsersData"


this.$store.dispatch('admin/user/getUsersData', this.search)
},
}

}
</script>

<style>

639
</style>

Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state users yang ada di dalam Vuex.

<b-pagination align="right" :value="users.current_page" :total-


rows="users.total" :per-page="users.per_page" @change="changePage" aria-
controls="my-table"></b-pagination>

Kemudian kita membuat 1 method baru dengan nama changePage, method tersebut akan
di jalankan ketika kita melakukan navigasi di button pagination. kurang lebih seperti berikut
ini :

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/user/SET_PAGE', page)

//dispatch on action "getUsersData"


this.$store.dispatch('admin/user/getUsersData', this.search)
},

Method di atas akan dijalankan ketika kita melakukan klik pada navigasi pagination. Dan di
dalam method tersebut kita menjalankan 2 perintah.

Yang pertama, kita melakukan commit ke dalam mutation yang bernama SET_PAGE yang
berada di dalam store/admin/user.js dan untuk parameternya berupa nomor
pagination yang di dapatkan dari component <b-paginate>.

//commit to mutation "SET_PAGE"


this.$store.commit('admin/user/SET_PAGE', page)

Yang kedua, kita melakukan dispatch ke dalam action yang bernama getUsersData,
tujuannya agar data yang ditampilkan sesuai dengan nomor pagination-nya. Dan kita
tambahkan parameter this.search apabila kita melakukan navigasi di dalam hasil
pencarian.

640
//dispatch on action "getUsersData"
this.$store.dispatch('admin/user/getUsersData', this.search)

Sekarang kita bisa mencoba dengan melakukan refresh di halaman users atau bisa
melihatnya melalui link http://localhost:3000/admin/users dan jika berhasil maka kita akan
mendapatkan tampilan kurang lebih seperti berikut ini :

641
Membuat Proses Insert Data User

Sekarang kita akan lanjutkan belajar bagaimana cara membuat proses insert data user baru
ke dalam database di dalam Nuxt.js. Sebelum itu, kita akan menambahkan sebuah action
baru terlebih dahulu di dalam Vuex, action tersebutlah yang akan digunakan untuk
mengirim data ke dalam server untuk disimpan ke dalam database.

Langkah 1 - Menambahkan Action di Vuex User


Pertama, kita akan menambahkan 1 action baru di dalam Vuex user, silahkan buka file
store/admin/user.js kemudian ubah semua kode-nya menjadi seperti berikut ini :

//state
export const state = () => ({

//users
users: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_USERS_DATA"
SET_USERS_DATA(state, payload) {

//set value state "users"


state.users = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

642
//actions
export const actions = {

//get users data


getUsersData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/users" with method "GET"


this.$axios.get(`/api/admin/users?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_USERS_DATA"


commit('SET_USERS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store user
storeUser({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/users" with method "POST"


this.$axios.post('/api/admin/users', payload)

//success
.then(() => {

//dispatch action "getUsersData"


dispatch('getUsersData')

//resolve promise
resolve()

643
})

//error
.catch(error => {
reject(error)
})

})
},

Dari perubahan kode di atas, pertama kita menambahkan action baru dengan nama
storeUser.

//store user
storeUser({ dispatch, commit }, payload) {

//...
}

Di dalam action tersebut, kita melakukan sending data menggunakan Axios dengan
method POST ke dalam endpoint /api/admin/users dan untuk parameter-nya adalah
payload yang nanti akan berisi data user yang dikirim dari component / view.

//store to Rest API "/api/admin/users" with method "POST"


this.$axios.post('/api/admin/users', payload)

Jika proses sending data berhasil dilakukan, maka kita akan melakukan dispatch ke dalam
action yang bernama getUsersData, dengan tujuan agar melakukan fetching ulang
dengan data yang terbaru.

//dispatch action "getUsersData"


dispatch('getUsersData')

644
Langkah 2 - Membuat View Tambah User
Disini kita akan membuat sebuah file view atau component baru yang akan digunakan untuk
menampilkan halaman tambah data dan sekaligus untuk proses insert data melalui Rest API.

Silahkan buat sebuah folder baru dengan nama create di dalam folder
pages/admin/users dan di dalam folder create silahkan buat file baru dengan nama
index.vue, kemudian masukkan kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> ADD NEW USER</span>
</div>
<div class="card-body">

<form @submit.prevent="storeUser">

<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>FULL NAME</label>
<input type="text" v-model="user.name"
placeholder="Masukkan Nama User" class="form-control">
<div v-if="validation.name" class="mt-2">
<b-alert show variant="danger">{{
validation.name[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>EMAIL ADDRESS</label>
<input type="email" v-model="user.email"
placeholder="Masukkan Email Address"
class="form-control">
<div v-if="validation.email" class="mt-2">
<b-alert show variant="danger">{{

645
validation.email[0] }}</b-alert>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>PASSWORD</label>
<input type="password" v-model="user.password"
placeholder="Masukkan Password"
class="form-control">
<div v-if="validation.password" class="mt-2">
<b-alert show variant="danger">{{
validation.password[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>PASSWORD CONFIRMATION</label>
<input type="password" v-
model="user.password_confirmation"
placeholder="Masukkan Konfirmasi Password"
class="form-control">
</div>
</div>
</div>

<button class="btn btn-info mr-1 btn-submit"


type="submit"><i class="fa fa-paper-plane"></i>
SAVE</button>
<button class="btn btn-warning btn-reset"
type="reset"><i class="fa fa-redo"></i>
RESET</button>

</form>

</div>
</div>
</div>
</div>
</div>
</div>
</main>

646
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Add New User - Administrator',
}
},

data() {
return {
//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: '',
},
//state validation
validation: []
}
},

methods: {

//method "storeUser"
async storeUser() {

//define formData
let formData = new FormData();

formData.append('name', this.user.name)
formData.append('email', this.user.email)
formData.append('password', this.user.password)
formData.append('password_confirmation',
this.user.password_confirmation)

//sending data to action "storeUser" vuex


await this.$store.dispatch('admin/user/storeUser', formData)

647
//success
.then(() => {

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

//redirect route "admin-users"


this.$router.push({
name: 'admin-users'
})

})

//error
.catch(error => {

//assign error to state "validation"


this.validation = error.response.data
})
}
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

648
//meta
head() {
return {
title: 'Add New User - Administrator',
}
},

Kemudian di dalam data function kita menambahkan 2 state baru, yaitu user dan
validation. Untuk state user akan digunakan untuk menyimpan data yang dikirim dari
input form. Sedangkan state validation akan digunakan untuk menyimpan error response
validasi yang di dapatkan dari Rest API.

//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: '',
},

//state validation
validation: []

Dan kita membuat 1 method baru dengan nama storeUser, method tersebut akan
dijalankan ketika form di dalam template di submit.

<form @submit.prevent="storeUser">

//...
</form>

//method "storeUser"
async storeUser() {

//...
}

649
Di dalam method tersebut, pertama kita melakukan inisialisasi FormData, tujuannya untuk
memudahkan dalam mengelompokan data yang akan dikirim ke dalam action Vuex.

//define formData
let formData = new FormData();

Setelah itu, kita melakukan append formData yang berisi data yang didapatkan dari input
form.

formData.append('name', this.user.name)
formData.append('email', this.user.email)
formData.append('password', this.user.password)
formData.append('password_confirmation',
this.user.password_confirmation)

Kemudian kita kirim formData tersebut ke dalam action Vuex dengan nama storeUser.

//sending data to action "storeUser" vuex


await this.$store.dispatch('admin/user/storeUser', formData)

Jika proses sending data di dalam action Vuex sukses dilakukan, maka kita akan
menampilkam alert sukses menggunakan Sweet Alert2.

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Setelah itu, kita redirect / arahkan ke dalam route yang bernama admin-users.

650
//redirect route "admin-users"
this.$router.push({
name: 'admin-users'
})

Tapi jika data yang dikirim gagal disimpan, maka akan melakukan assign ke dalam state
validation dengan isi response error validasi.

//assign error to state "validation"


this.validation = error.response.data

Langkah 3 - Uji Coba Proses Insert Data User


Sekarang kita akan lakukan uji coba untuk proses insert data user. Silahkaan klik button
TAMBAH yang berada di halaman index users atau bisa menggunakan link berikut ini
http://localhost:3000/admin/users/create jika berhasil maka kita akan mendapatkan hasil
seperti berikut ini :

Silahkan masukkan data user baru dan klik SAVE, jika berhasil maka akan mendapatkan
alert dan akan diarahkan ke halaman index users.

651
652
Membuat Proses Edit dan Update Data User

Setelah berhasil membuat proses insert data user baru, sekarang kita akan lanjutkan belajar
bagaimana cara melakukan proses edit dan update data ke dalam database di dalam
Nuxt.js. Sebelum itu, tentu kita akan menambahkan state, mutation dan action terlebih
dahulu di dalam Vuex.

Langkah 1 - Menambahkan State, Mutation dan Action di Vuex


Pertama-tama kita akan menambahkan sebuah state, mutation dan action baru di
dalam Vuex Admin User. Silahkan buka file store/admin/user.js dan ubah semua kode-
nya menjadi seperti berikut ini :

//state
export const state = () => ({

//users
users: [],

//page
page: 1,

//user
user: {}

})

//mutations
export const mutations = {

//mutation "SET_USERS_DATA"
SET_USERS_DATA(state, payload) {

//set value state "users"


state.users = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload

653
},

//mutation "SET_USER_DATA"
SET_USER_DATA(state, payload) {

//set value state "user"


state.user = payload
},

//actions
export const actions = {

//get users data


getUsersData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/users" with method "GET"


this.$axios.get(`/api/admin/users?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_USERS_DATA"


commit('SET_USERS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store user
storeUser({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/users" with method "POST"

654
this.$axios.post('/api/admin/users', payload)

//success
.then(() => {

//dispatch action "getUsersData"


dispatch('getUsersData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//get detail user


getDetailUser({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//get to Rest API "/api/admin/users/:id" with method "GET"


this.$axios.get(`/api/admin/users/${payload}`)

//success
.then(response => {

//commit to mutation "SET_USER_DATA"


commit('SET_USER_DATA', response.data.data)

//resolve promise
resolve()

})

})

},

//update user

655
updateUser({ dispatch, commit }, { userId, payload }) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/users/:id" with method


"POST"
this.$axios.post(`/api/admin/users/${userId}`, payload)

//success
.then(() => {

//dispatch action "getUsersData"


dispatch('getUsersData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

Dari penambahan kode di atas, pertama kita menambahkan 1 state baru dengan nama
user. State tersebut akan digunakan untuk menyimpan detail data user yang didapatkan
dari Rest API.

//user
user: {}

Kemudian di dalam mutation kita menambahkan 1 method baru dengan nama


SET_USER_DATA. Mutation tersebut yang nanti akan digunakan untuk melakukan assign /
update isi dari state user dengan data response yang dikirimkan dari action.

656
//mutation "SET_USER_DATA"
SET_USER_DATA(state, payload) {

//set value state "user"


state.user = payload
},

Dan di dalam action kita menambahkan 2 method baru, yaitu :

getDetailUser
updateUser

getDetailUser

Action ini akan digunakan untuk mendapatkan detail data user yang akan di edit, dimana di
dalam action ini kita melakukan fetching menggunakan Axios dengan method GET ke
dalam endpoint /api/admin/users/:id.

//get to Rest API "/api/admin/users/:id" with method "GET"


this.$axios.get(`/api/admin/users/${payload}`)

Jika proses fetching data tersebut berhasil dilakukan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_USER_DATA dengan parameter berupa response data
dari Rest API.

//commit to mutation "SET_USER_DATA"


commit('SET_USER_DATA', response.data.data)

updateUser

Action ini akan digunakan untuk melakukan proses update data user ke dalam server
menggunakan Rest API. Disini kita melakukan sending data menggunakan Axios dengan
method POST ke dalam endpoint /api/admin/users/:id. Dan kita tambahkan parameter
berupa payload, dimana isinya adalah data user yang dikirimkan dari component / view.

657
//store to Rest API "/api/admin/users/:id" with method "POST"
this.$axios.post(`/api/admin/users/${userId}`, payload)

Jika proses sending data di atas berhasil dilakukan, maka kita akan menjalankan /
melakukan dispatch ke dalam action yang bernama getUsersData, dengan tujuan agar
melakukan fetching ulang dengan data yang terbaru.

//dispatch action "getUsersData"


dispatch('getUsersData')

Langkah 2 - Menambahkan Button Edit


Sekarang kita akan lanjutkan untuk menambahkan sebuah button untuk proses navigasi ke
dalam halaman edit data user. Silahkan buka file pages/admin/users/index.vue,
kemudian cari kode berikut ini :

<b-table striped bordered hover :items="users.data" :fields="fields"


show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50" :src="data.item.image" />
</template>
</b-table>

Dan ubahlah menjadi seperti berikut ini :

658
<b-table striped bordered hover :items="users.data" :fields="fields"
show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50" :src="data.item.image" />
</template>
<template v-slot:cell(actions)="row">
<b-button :to="{name: 'admin-users-edit-id', params: {id:
row.item.id}}" variant="info" size="sm">
EDIT
</b-button>
</template>
</b-table>

Di atas kita menambahkan 1 button baru untuk kita gunakan dalam proses edit data,
dimana di dalam button tersebut kita arahkan ke dalam route yang bernama admin-
users-edit-id dan kita tambahkan juga parameter berupa data ID params: {id:
row.item.id}.

Sekarang jika kita coba refresh halaman index users, maka kita akan mendapatkan kurang
lebih tampilannya seperti berikut ini :

Langkah 3 - Membuat View Edit User


Langkah selanjutnya, kita akan membuat sebuah component / view yang digunakan untuk
menampilkan data yang akan di edit dan sekaligus membuat proses update-nya.

659
Silahkan buat folder baru dengan nama edit di dalam folder pages/admin/users dan di
dalam folder edit silahkan buat file baru dengan nama _id.vue dan masukkan kode
berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> EDIT USER</span>
</div>
<div class="card-body">

<form @submit.prevent="updateUser">

<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>FULL NAME</label>
<input type="text" v-model="user.name"
placeholder="Masukkan Nama User" class="form-control">
<div v-if="validation.name" class="mt-2">
<b-alert show variant="danger">{{
validation.name[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>EMAIL ADDRESS</label>
<input type="email" v-model="user.email"
placeholder="Masukkan Email Address"
class="form-control">
<div v-if="validation.email" class="mt-2">
<b-alert show variant="danger">{{
validation.email[0] }}</b-alert>
</div>
</div>
</div>
</div>

660
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>PASSWORD</label>
<input type="password" v-model="user.password"
placeholder="Masukkan Password"
class="form-control">
<div v-if="validation.password" class="mt-2">
<b-alert show variant="danger">{{
validation.password[0] }}</b-alert>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>PASSWORD CONFIRMATION</label>
<input type="password" v-
model="user.password_confirmation"
placeholder="Masukkan Konfirmasi Password"
class="form-control">
</div>
</div>
</div>

<button class="btn btn-info mr-1 btn-submit"


type="submit"><i class="fa fa-paper-plane"></i>
UPDATE</button>
<button class="btn btn-warning btn-reset"
type="reset"><i class="fa fa-redo"></i>
RESET</button>

</form>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout

661
layout: 'admin',

//meta
head() {
return {
title: 'Edit User - Administrator',
}
},

data() {
return {
//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: '',
},
//state validation
validation: []
}
},

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/user/getDetailUser',
route.params.id)
},

//mounted
mounted() {
this.user.name = this.$store.state.admin.user.user.name
this.user.email = this.$store.state.admin.user.user.email
},

methods: {

//method "updateUser"
async updateUser() {

//define formData
let formData = new FormData();

formData.append('name', this.user.name)
formData.append('email', this.user.email)

662
formData.append('password', this.user.password)
formData.append('password_confirmation',
this.user.password_confirmation)
formData.append("_method", "PATCH")

//sending data to action "updateUser" vuex


await this.$store.dispatch('admin/user/updateUser', {
userId: this.$route.params.id,
payload: formData
})

//success
.then(() => {

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

//redirect route "admin-users"


this.$router.push({
name: 'admin-users'
})

})

//error
.catch(error => {

//assign error to state "validation"


this.validation = error.response.data
})
}
}

}
</script>

<style>

</style>

663
Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

//meta
head() {
return {
title: 'Edit User - Administrator',
}
},

Kemudian di dalam data function kita menambahkan 2 state baru, yaitu user dan
validation. Untuk state user akan digunakan untuk menyimpan data yang dikirim dari
input form. Sedangkan state validation akan digunakan untuk menyimpan error response
validasi yang di dapatkan dari Rest API.

//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: '',
},

//state validation
validation: []

Dan di dalam hook asyncData kita melakukan dispatch atau memanggil sebuah action
Vuex yang bernama getDetailUser dengan parameter ID user yang di dapatkan dari
URL.

664
//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/user/getDetailUser', route.params.id)
},

Jika proses di dalam Vuex di atas berhasil dilakukan, sekarang kita tinggal mengambil
datanya dari state yang ada di Vuex dan kita assign ke dalam state yang ada di dalam daata
function menggunakan mounted properti.

//mounted
mounted() {
this.user.name = this.$store.state.admin.user.user.name
this.user.email = this.$store.state.admin.user.user.email
},

kemudian kita membuat 1 method dengan nama updateUser, method tersebut akan
dijalankan ketika form di dalam template di submit.

<form @submit.prevent="updateUser">

//..
</form>

//method "updateUser"
async updateUser() {

//...
}

Di dalam method di atas, pertama kita melakukan inisialisasi sebuah FormData, fungsinya
untuk mempermudah dalam pengiriman sebuah data ke dalam Vuex.

//define formData
let formData = new FormData();

665
Setelah itu, kita melakukan append dari data yang di dapatkan dari input form ke dalam
formData.

formData.append('name', this.user.name)
formData.append('email', this.user.email)
formData.append('password', this.user.password)
formData.append('password_confirmation',
this.user.password_confirmation)
formData.append("_method", "PATCH")

Untuk proses update, jangan lupa menambahkan key _method dengan value PATCH.

Sekarang kita kirimkan formData tersebut ke dalam action Vuex yang bernama
updateUser.

//sending data to action "updateUser" vuex


await this.$store.dispatch('admin/user/updateUser', {
userId: this.$route.params.id,
payload: formData
})

Di atas, kita tidak hanya mengirimkan formData saja, melainkan ada parameter lain, yaitu
userID yang isinya adalah ID user yang didapatkan dari URL.

Jika proses update di dalam action Vuex berhasil dilakukan, maka kita akan menampilkan
alert sukses update menggunakan Sweet Alert2.

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Kemudian kita redirect / arahkan ke dalam route yang bernama admin-users.

666
//redirect route "admin-users"
this.$router.push({
name: 'admin-users'
})

Tapi, jika proses update gagal dilakukan, maka akan melakukan assign error response
validasi ke dalam state validation.

//assign error to state "validation"


this.validation = error.response.data

Langkah 4 - Uji Coba Proses Update Data User


Sekarang kita akan lakukan uji coba untuk proses edit dan update data user. Silahkan klik
button EDIT di salah satu data yang ada di halaman index users, jika berhasil maka kita
akan mendapatkan halaman seperti berikut ini :

Setelah itu, silahkan diubah isi sesuai dengan kebutuhan, setelah itu silahkan klik UPDATE
dan jika berhasil maka kita akan mendapatkan pesan berhasil update seperti berikut ini :

667
668
Membuat Proses Delete Data User

Setelah berhasil membuat proses edit dan update data user, sekarang kita akan lanjutkan
untuk membuat proses delete data user dari datanase. Sebelum itu, kita akan
menambahkan sebuah action terlebih dahulu di dalam Vuex untuk proses http request
delete data menggunakan Rest API.

Langkah 1 - Menambahkan Action di Vuex User


Pertama-tama kita akan menambahkan sebuah action terlebih dahulu di dalam Vuex User.
Silahkan buka file store/admin/user.js, kemudian ubah semua kode-nya menjadi
seperti berikut ini :

//state
export const state = () => ({

//users
users: [],

//page
page: 1,

//user
user: {}

})

//mutations
export const mutations = {

//mutation "SET_USERS_DATA"
SET_USERS_DATA(state, payload) {

//set value state "users"


state.users = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload

669
},

//mutation "SET_USER_DATA"
SET_USER_DATA(state, payload) {

//set value state "user"


state.user = payload
},

//actions
export const actions = {

//get users data


getUsersData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/users" with method "GET"


this.$axios.get(`/api/admin/users?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_USERS_DATA"


commit('SET_USERS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store user
storeUser({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/users" with method "POST"

670
this.$axios.post('/api/admin/users', payload)

//success
.then(() => {

//dispatch action "getUsersData"


dispatch('getUsersData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//get detail user


getDetailUser({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//get to Rest API "/api/admin/users/:id" with method "GET"


this.$axios.get(`/api/admin/users/${payload}`)

//success
.then(response => {

//commit to mutation "SET_USER_DATA"


commit('SET_USER_DATA', response.data.data)

//resolve promise
resolve()

})

})

},

//update user

671
updateUser({ dispatch, commit }, { userId, payload }) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/users/:id" with method


"POST"
this.$axios.post(`/api/admin/users/${userId}`, payload)

//success
.then(() => {

//dispatch action "getUsersData"


dispatch('getUsersData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//destroy user
destroyUser({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {
//delete to Rest API "/api/admin/users/:id" with method
"DELETE"
this.$axios.delete(`/api/admin/users/${payload}`)

//success
.then(() => {

//dispatch action "getUsersData"


dispatch('getUsersData')

//resolve promise
resolve()

672
})

})

Dari perubahan kode di atas, kita menambahkan 1 action baru dengan nama destroyUser.

//destroy user
destroyUser({ dispatch, commit }, payload) {

//...
}

Di dalam action tersebut, kita melakukan http request menggunakanAxios dengan method
DELETE ke dalam endpoint /api/admin/users/:id.

//delete to Rest API "/api/admin/users/:id" with method "DELETE"


this.$axios.delete(`/api/admin/users/${payload}`)

Jika dari proses http request delete data di atas berjalan dengan sukses, maka kita akan
melakukan dispatch ke dalam sebuah action yang bernama getUsersData, dengan tujuan
agar melakukan fetching ulang dengan data yang telah diperbarui.

//dispatch action "getUsersData"


dispatch('getUsersData')

Langkah 2 - Menambahkan Button dan Fungsi Delete


Setelah berhasil menambahkan action di dalam Vuex, maka kita akan lanjutkan untuk
menampilkan button delete di dalam table dan membuat fungsi delete. Silahkan buka file
pages/admin/users/index.vue, kemudian ubah semua kode-nya menjadi seperti
berikut ini :

673
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
users"></i> USERS</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-users-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama user">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="users.data"


:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50"
:src="data.item.image" />
</template>
<template v-slot:cell(actions)="row">
<b-button :to="{name: 'admin-users-edit-id', params:
{id: row.item.id}}" variant="info" size="sm">
EDIT
</b-button>

674
<b-button variant="danger" size="sm"
@click="destroyUser(row.item.id)">DELETE</b-button>
</template>
</b-table>

<!-- pagination -->


<b-pagination align="right" :value="users.current_page"
:total-rows="users.total"
:per-page="users.per_page" @change="changePage" aria-
controls="my-table"></b-pagination>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Users - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'User Name',
key: 'name'
},
{
label: 'Email Address',
key: 'email'
},
{

675
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/user/getUsersData')
},

//computed
computed: {

//users
users() {
return this.$store.state.admin.user.users
},
},

//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/user/SET_PAGE', 1)

//dispatch on action "getUsersData"


this.$store.dispatch('admin/user/getUsersData', this.search)
},
//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/user/SET_PAGE', page)

//dispatch on action "getUsersData"


this.$store.dispatch('admin/user/getUsersData', this.search)
},

676
//method "destroyUser"
destroyUser(id) {
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {

//dispatch to action "destroyUser" vuex


this.$store.dispatch('admin/user/destroyUser', id)
.then(() => {

//feresh data
this.$nuxt.refresh()

//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

})
}
})
}
}

}
</script>

<style>

</style>

Di atas kita menambahkan 1 button baru, dimana button tersebut kita berikan event
@click yang mengarah ke dalam method yang bernama destroyUser dan di dalam

677
method tersebut kita berikan parameter berupa ID dari user.

<b-button variant="danger" size="sm"


@click="destroyUser(row.item.id)">DELETE</b-button>

Dan di dalam method destroyUser pertama-tama kita menampilkan jendela konfirmasi


menggunakan Sweet Alert2.

this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
})

Jika kita klik button konfirmasi, maka akan melakukan dispatch ke dalam action yang
bernama destroyUser dengan parameter ID dari user.

//dispatch to action "destroyUser" vuex


this.$store.dispatch('admin/user/destroyUser', id)

Dan jika di dalam action Vuex proses delete data berhasil dilakukan, maka kita akan
melakukan refresh website-nya dengan kode berikut ini :

//feresh data
this.$nuxt.refresh()

Kode di atas digunakan untuk memperbarui data di dalam website tanpa perlu melakukan
reload halaman. Dan setelah itu kita menampilkan alert menggunakan Sweet Alert2.

678
//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Langkah 3 - Uji Coba Proses Delete Data User


Sekarang kita bisa mencobanya dengan membuka halaman users atau bisa melalui link
berikut ini http://localhost:3000/admin/users, jika berhasil maka kita akan mendapatkan
tampilan kurang lebih seperti berikut ini :

Silahkan klik button DELETE di salah satu data yang kita miliki, jika berhasil maka akan
menampilkan halaman konfirmasi menggunakan Sweet Alert2.

679
Dan jika kita klik YA, HAPUS!, maka data akan terhapus dan kita mendapatkan alert sukses
menggunakan Sweet Alert2.

680
Membuat Halaman Dashboard

Sekarang kita akan lanjutkan untuk membuat halaman dashboard untuk admin, halaman ini
yang akan pertama kali di tampilkan setelah berhasil melakukan proses otentikasi. Dan
nantinya di dalam halaman ini akan kita gunakan untuk menampilkan beberapa data,
seperti statistik dan pendapatan berupa chart.

Langkah 1 - Membuat View Dashboard


Silahkan buat folder baru dengan nama dashboard di dalam folder pages/admin dan di
dalam folder dashboard silahkan buat file baru dengan nama index.vue dan masukkan
kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">

<div class="col-6 col-lg-3">


<div class="card border-0 rounded shadow-sm overflow-
hidden">
<div class="card-body p-0 d-flex align-items-center">
<div class="bg-primary py-4 px-5 mfe-3">
<i class="fas fa-circle-notch fa-spin fa-2x"></i>
</div>
<div>
<div class="text-value text-primary">{{
statistic.pending }}</div>
<div class="text-muted text-uppercase font-weight-bold
small">PENDING</div>
</div>
</div>
</div>
</div>

<div class="col-6 col-lg-3">


<div class="card border-0 rounded shadow-sm overflow-
hidden">
<div class="card-body p-0 d-flex align-items-center">
<div class="bg-success py-4 px-5 mfe-3">
<i class="fas fa-check-circle fa-2x"></i>

681
</div>
<div>
<div class="text-value text-success">{{
statistic.success }}</div>
<div class="text-muted text-uppercase font-weight-bold
small">SUCCESS</div>
</div>
</div>
</div>
</div>

<div class="col-6 col-lg-3">


<div class="card border-0 rounded shadow-sm overflow-
hidden">
<div class="card-body p-0 d-flex align-items-center">
<div class="bg-warning py-4 px-5 mfe-3">
<i class="fas fa-exclamation-triangle fa-2x"></i>
</div>
<div>
<div class="text-value text-warning">{{
statistic.expired }}</div>
<div class="text-muted text-uppercase font-weight-bold
small">EXPIRED</div>
</div>
</div>
</div>
</div>

<div class="col-6 col-lg-3">


<div class="card border-0 rounded shadow-sm overflow-
hidden">
<div class="card-body p-0 d-flex align-items-center">
<div class="bg-danger py-4 px-5 mfe-3">
<i class="fas fa-times-circle fa-2x"></i>
</div>
<div>
<div class="text-value text-danger">{{
statistic.failed }}</div>
<div class="text-muted text-uppercase font-weight-bold
small">FAILED</div>
</div>
</div>
</div>
</div>

</div>

682
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-chart-
bar"></i> GRAFIK PENDAPATAN {{new Date().getFullYear()}}</span>
</div>
<div class="card-body">
<client-only>
<line-chart :data="chart.chartData"></line-chart>
</client-only>
</div>
</div>
</div>
</div>

</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Dashboard - Administrator',
}
},

async asyncData({ $axios }) {

//fetching dashboard
const dashboard = await $axios.$get('/api/admin/dashboard')

//statistic
const statistic = {
'pending': dashboard.data.count.pending,
'success': dashboard.data.count.success,
'expired': dashboard.data.count.expired,
'failed': dashboard.data.count.failed,

683
}

//cart
const chart = {
chartData: {
labels: dashboard.data.chart.month_name,
datasets: [
{
label: `STATISTIK PENDAPATAN : ${new
Date().getFullYear()}`,
backgroundColor: '#bccad8',
data: dashboard.data.chart.grand_total
},
]
}
}

return {
statistic,
chart
}
},

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk title dari websitenya, kurang lebih seperti berikut ini :

684
//meta
head() {
return {
title: 'Dashboard - Administrator',
}
},

Kemudian kita menggunakan hook asyncData untuk melakukan fething dengan methode
SSR atau Server Side Rendering, jadi halaman atau template tidak akan di tampilkan,
sebelum proses di dalam hook ini selesai.

Dimana di dalam hook asyncData kita melakukan fething ke dalam sebuah Rest API
dengan endpoint /api/admin/dashboard menggunakan Axios dengan method GET.

//fetching dashboard
const dashboard = await $axios.$get('/api/admin/dashboard')

Setelah itu, kita membuat 2 variable baru yang akan digunakan untuk menampung hasil
response dari Rest API, yaitu statistic dan chart.

//statistic
const statistic = {
'pending': dashboard.data.count.pending,
'success': dashboard.data.count.success,
'expired': dashboard.data.count.expired,
'failed': dashboard.data.count.failed,
}

Di atas kita membuat beberapa key yang isinya mengambil response dari Rest API berupa
statistik data invoice, yaitu jumlah pending, success, expired dan failed.

Kemudian untuk chart, kurang lebih seperti berikut ini :

685
//cart
const chart = {
chartData: {
labels: dashboard.data.chart.month_name,
datasets: [{
label: `STATISTIK PENDAPATAN : ${new Date().getFullYear()}`,
backgroundColor: '#bccad8',
data: dashboard.data.chart.grand_total
}, ]
}
}

Di atas, untuk labels akan berisi data response dari nama bulan yang di dapatkan dari
Rest API. Dan untuk data akan berupa data grand_total yang di dapatkan juga dari Rest
API.

Setelah itu, agar kedua variable di atas dapat digunakan dan di panggil di dalam template,
maka kita perlu melakukan return terlebih dahulu.

return {
statistic,
chart
}

Langkah 2 - Uji Coba Halaman Dashboard


Sekarang silahkan buka halaman dashboard di URL berikut ini
http://localhost:3000/admin/dashboard, jika berhasil maka kurang lebih hasilnya seperti
berikut ini :

686
687
Konfigurasi Vuex Admin Category

Sekarang, kita akan belajar membuat module baru untuk mengelola data category di dalam
project Nuxt.js. Ini bertujuan agar kode-kode store kita, tidak tercampur menjadi satu
dengan yang lain, dan dengan menerapkan module-module di dalam Vuex, kita bisa lebih
mudah dalam melakukan maintenance dan pengembangan.

Sebelum membuat konfigurasi Vuex, maka kita perlu tau dulu bagaimana cara Vuex ini
bekerja. Dan untuk gambaran alur kerjanya, kurang lebih seperti berikut ini :

Dari gambar di atas, pertama component / view melakukan dispatch ke dalam sebuah
action dan di dalam action akan melakukan http request ke dalam sebuah endpoint dan
hasil dari request tersebut akan di commit ke dalam mutation dan di dalam mutation
akan melakukan update sebuah state. Dan component / view tersebut baru bisa
menampilkan data dari state yang di dapatkan dari Rest API.

Membuat Konfigurasi Vuex Admin Category


Sekarang, kita akan membuat konfigurasi di dalam Vuex Admin Category, yaitu

688
menambahkan sebuah actions, mutation dan state.

Silahkan buat folder baru dengan nama admin di dalam folder store. Dan di dalam folder
admin silahkan buat file baru dengan nama category.js, kemudian masukkan kode
berikut ini :

//state
export const state = () => ({

//categories
categories: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {

//set value state "categories"


state.categories = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

//actions
export const actions = {

//get categories data


getCategoriesData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

689
//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/categories" with method


"GET"
this.$axios.get(`/api/admin/categories?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_CATEGORIES_DATA"


commit('SET_CATEGORIES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Di dalam konfigurasi Vuex di atas, kita membuat beberapa config, yaitu :

1. state.
2. mutations.
3. actions.

state

State digunakan untuk menyimpan sebuah data yang bersifat reactive/realtime dan
umumnya akan digunakan untuk menyimpan data dari hasil yang di dapatkan dari sebuah
response Rest API. Disini kita membuat 2 state baru, yaitu

categories - dengan default value array.


page - dengan default value angka 1.

Untuk state categories akan digunakan untuk menampung data response yang di
dapatkaan dari Rest API berupa list data categories. Dan nantinya di dalam component /
view kita tinggal memanggil state ini untuk menampilkan data-nya.

690
//categories
categories: [],

Dan untuk state page akan digunakan untuk menampung nilai dari pagination, dan untuk
default-nya kita berikan angka 1.

//page
page: 1,

mutations

Mutation digunakan untuk mengubah isi dari sebuah state, disini kita membuat 2 mutation,
yaitu

SET_CATEGORIES_DATA - digunakan untuk mengubah nilai state categories.


SET_PAGE - digunakan untuk mengubah nilai state page.

Untuk mutation SET_CATEGORIES_DATA akan digunakan untuk mengubah isi dari state
categories dan datanya akan di dapatkan dari response Rest API yang di kirim dari
actions.

//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {

//set value state "categories"


state.categories = payload
},

Dan untuk mutation SET_PAGE akan digunakan untuk mengubah isi dari state page, untuk
datanya nanti akan dinamis sesuai yang dikirimkan melalui component / view.

691
//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

actions

Action digunakan untuk melakukan http request ke dalam sebuah endpoint tertentu, dan
disini kita membuat sebuah action yang bernama getCategoriesData.

//get categories data


getCategoriesData({ commit, state }, payload) {

//...
}

Dan di dalamnya pertama kita membuat sebuah variable untuk menyimpan keyword
pencarian data.

//search
let search = payload ? payload : ''

Setelah itu, kita melakukan http request ke server menggunakan Axios dengan method
GET ke dalam endpoint /api/admin/categories?q=""&page="" dan di dalam endpoint
tersebut kita tambahkan 2 parameter yang bersifat opsional, yaitu q untuk pencarian data
dan page untuk pagination.

Untuk parameter q akan diisi dari variable search dan untuk parameter page akan diambil
dari state yang bernama page.

Jika proses request ke dalam endpoint tersebut berhasil, maka kita akan melakukan commit
ke dalam mutation yang bernama SET_CATEGORIES_DATA dan parameternya adalah
response data yang di dapatkan dari Rest API.

692
//commit ti mutation "SET_CATEGORIES_DATA"
commit('SET_CATEGORIES_DATA', response.data.data)

693
Menampilkan Data Categories

Pada materi kali ini kita semua akan belajar bagaimana cara menampilkan data categories
dari database di dalam Nuxt.js, Disini kita akan memanfaatkan Vuex untuk mendapatkan
data dari Rest API.

Langkah 1 - Menampilkan Data Categories


Silahkan buat folder baru dengan nama categories di dalam folder pages/admin dan di
dalam folder categories silahkan buat file baru dengan nama index.vue kemudian
masukkan kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> CATEGORIES</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-categories-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control"
placeholder="cari berdasarkan nama category">
<div class="input-group-append">
<button class="btn btn-warning"><i
class="fa fa-search"></i>SEARCH</button>
</div>
</div>
</div>

694
<b-table striped bordered hover :items="categories.data"
:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50"
:src="data.item.image" />
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Categories - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Category Name',
key: 'name'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'

695
}
],
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/category/getCategoriesData')
},

//computed
computed: {

//categories
categories() {
return this.$store.state.admin.category.categories
},
},

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga atur untuk meta title dari view atau component ini.

696
//meta
head() {
return {
title: 'Categories - Administrator',
}
},

Di dalam data function, kita menambahkan sebuah state yang bernama fields dan di
dalamnya berisi beberapa key array, state ini akan kita gunakan untuk membuat sebuah
header untuk table.

//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Category Name',
key: 'name'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

Setelah itu, kita menggunakan hook asyncData untuk melakukan dispatch ke dalam action
yang sudah kita buat sebelumnya di dalam Vuex.

await store.dispatch('admin/category/getCategoriesData')

Di atas kita melakukan dispatch ke dalam action yang bernama getCategoriesData yang
berada di dalam file /store/admin/category.js.

Jika proses dispatch berhasil, kita tinggal memanggil datanya yang ada di dalam state, disini
kita menggunakan properti computed dan di dalamnya akan memanggil state yang
bernama categories yang berada di dalam file /store/admin/category.js.

697
return this.$store.state.admin.category.categories

Setelah itu pada bagian template, coba perhatikan kode berikut ini :

<b-table striped bordered hover :items="categories.data"


:fields="fields" show-empty>

//...

</b-table>

Kode di atas merupakan component dari Bootstrap Vue yang bernama <b-table>, yang
akan kita gunakan untuk menampilkan sebuah table dengan lebih mudah. Dan di dalam
component tersebut kita memiliki properti :items yang mana isinya kita berikan nilai dari
properti computed yang sudah kita buat di atas sebelumnya, yaitu categories.

Kemudian untuk memberikan table header kita menggunakan properti :fields yang mana
isinya akan kita ambil dari data function yang bernama fields.

Langkah 2 - Mengaktifkan Link Menu


Setelah berhasil membuat view atau component, maka secara otomatis Nuxt.js juga akan
membuatkan kita route dari file yang sudah kita buat di dalam halaman pages. Sekarang
kita akan mengaktifkan menu tersebut di dalam file component Sidebar.

Silahkan buka file components/admin/sidebar.vue, kemudian cari kode berikut ini :

<li class="c-sidebar-nav-item"><a href="#" class="c-sidebar-nav-link">


<svg class="c-sidebar-nav-icon">
<use xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
folder"></use>
</svg> Categories</a>
</li>

Kemudian ubah menjadi seperti berikut ini :

698
<li class="c-sidebar-nav-item"><nuxt-link :to="{name: 'admin-
categories'}" class="c-sidebar-nav-link">
<svg class="c-sidebar-nav-icon">
<use xlink:href="@/node_modules/@coreui/icons/sprites/free.svg#cil-
folder"></use>
</svg> Categories</nuxt-link>
</li>

Di atas yang semula kita menggunakan <a href="#">, kita ubah menggunakan <nuxt-
link> dan route-nya kita arahkan ke dalam name yang bernama admin-categories.

Sekarang silahkan klik menu Categories yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/admin/categories, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :

Langkah 3 - Membuat Fitur Pencarian


Setelah berhasil menampilkan data, sekarang kita lanjutkan untuk membuat fitur pencarian
data. Disini kita akan melakukan perubahan lagi di dalam file di atas dan menambahkan
beberapa baris kode untuk menjalankan fitur pencarian.

Silahkan buka file pages/admin/categories/index.vue, kemudian ubah semua kode-


nya menjadi seperti berikut ini :

699
<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> CATEGORIES</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-categories-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama category">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>SEARCH</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="categories.data"


:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50"
:src="data.item.image" />
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>

700
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Categories - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Category Name',
key: 'name'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/category/getCategoriesData')
},

701
//computed
computed: {

//categories
categories() {
return this.$store.state.admin.category.categories
},
},

//method
methods: {
//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/category/SET_PAGE', 1)

//dispatch on action "getCategoriesData"


this.$store.dispatch('admin/category/getCategoriesData',
this.search)
},

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.

//state search
search: ''

Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :

702
<input type="text" class="form-control" v-model="search"
@keypress.enter="searchData" placeholder="cari berdasarkan nama
category">

<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i class="fa fa-
search"></i>SEARCH</button>
div>

Di atas pada bagian input kita menambahkan event @keypress.enter dimana di


dalamnya akan memanggil method yang bernama searchData, di dalam button juga sama
kita menambahkan event @click yang kita arahkan ke dalam method searcData.

Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/category/SET_PAGE', 1)

//dispatch on action "getCategoriesData"


this.$store.dispatch('admin/category/getCategoriesData',
this.search)
},

Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.

Dan yang kedua melakukan dispatch ke dalam action Vuex yang bernama
getCategoriesData dan kita parsing sebuah parameter yaitu this.search yang isinya
adalah kata kunci yang di dapatkan dari input form.

Sekarang silahkan refresh / reload halaman categories dan jika berhasil maka kita bisa
mencoba proses pencarian kurang lebih seperti berikut ini :

703
Langkah 4 - Membuat Fitur Pagination
Sekarang kita lanjutkan untuk membuat fitur pagination, itur ini akan memberikan kita
navigasi di bagian bawah untuk berpindah dari halaman 1 ke halaman yang lain.

Silahkan buka file pages/admin/categories/index.vue, kemudian ubah semua kode-


nya menjadi seperti berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> CATEGORIES</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-categories-

704
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama category">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>SEARCH</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="categories.data"


:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50"
:src="data.item.image" />
</template>
</b-table>

<!-- pagination -->


<b-pagination align="right"
:value="categories.current_page" :total-rows="categories.total"
:per-page="categories.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {

705
title: 'Categories - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Category Name',
key: 'name'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/category/getCategoriesData')
},

//computed
computed: {

//categories
categories() {
return this.$store.state.admin.category.categories
},
},

//method
methods: {
//method "searchData"

706
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/category/SET_PAGE', 1)

//dispatch on action "getCategoriesData"


this.$store.dispatch('admin/category/getCategoriesData',
this.search)
},

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/category/SET_PAGE', page)

//dispatch on action "getCategoriesData"


this.$store.dispatch('admin/category/getCategoriesData',
this.search)
},

}
</script>

<style>

</style>

Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state categories yang ada di dalam Vuex.

<!-- pagination -->


<b-pagination align="right" :value="categories.current_page" :total-
rows="categories.total" :per-page="categories.per_page"
@change="changePage" aria-controls="my-table"></b-pagination>

Dan ketika navigasi pagination di klik, maka akan menjalankan event @change, yang
mengarah ke dalam method changePage.

Di dalam method changePage kita menjalankan 2 perintah, yang pertaman melakukan

707
commit ke dalam mutation yang bernama SET_PAGE dengan parameter yang diambil dari
component pagination di atas.

//commit to mutation "SET_PAGE"


this.$store.commit('admin/category/SET_PAGE', page)

Dan yang kedua menjalankan dispatch ke dalam action getCategoriesData dan


menambahkan parameter this.seach, karena bisa jadi kita melakukan navigasi
pagination dari hasil pencarian data.

//dispatch on action "getCategoriesData"


this.$store.dispatch('admin/category/getCategoriesData', this.search)

Sekarang kita bisa mencoba dengan melakukan refresh di halaman categories atau bisa
melihatnya melalui link http://localhost:3000/admin/categories dan jika berhasil maka kita
akan mendapatkan tampilan kurang lebih seperti berikut ini :

708
Membuat Proses Insert Data Category

Sebelumnya kita sudah belajar bagaimana cara menampilkan data dari database melalui
Rest API dan Vuex dan juga membuat fitur pencarian beserta pagination. Sekarang kita akan
lanjutkan untuk membuat proses insert data category ke dalam database menggunakan
Rest API dan Vuex di dalam Nuxt.js

Langkah 1 - Menambahkan Action di Vuex


Pertama kita akan menambahkan sebuah action terlebih dahulu di dalam Vuex, fungsinya
yang akan melakukan sending data ke dalam server menggunakan Rest API.

Silahkan buka file store/admin/category.js kemudian ubah semua kode-nya menjadi


seperti berikut ini :

//state
export const state = () => ({

//categories
categories: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {

//set value state "categories"


state.categories = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

709
}

//actions
export const actions = {

//get categories data


getCategoriesData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/categories" with method


"GET"
this.$axios.get(`/api/admin/categories?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_CATEGORIES_DATA"


commit('SET_CATEGORIES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store category
storeCategory({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/categories" with method


"POST"
this.$axios.post('/api/admin/categories', payload)

//success
.then(() => {

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

710
//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

Dari penambahan kode di atas, kita menambahkan 1 action baru dengan nama
storeCategory, di dalamnya kita akan melakukan sending data ke dalam server.

//store category
storeCategory({ dispatch, commit }, payload) {

//...
}

Di dalam action di atas, kita melakukan http request menggunakan Axios dengan method
POST ke dalam endpoint /api/admin/categories dan kita tambahkan parameter
payload yang mana isinya akan di kirimkan melalui component / view.

//store to Rest API "/api/admin/categories" with method "POST"


this.$axios.post('/api/admin/categories', payload)

Jika proses sending data berhasil dilakukan, maka kita akan menjalankan dispatch ke
dalam action getCategoriesData. Tujuannya agar kita melakukan fetching ulang dengan
data yang baru.

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

711
Langkah 2 - Membuat View Tambah Category
Disini kita akan membuat sebuah file view atau component baru yang akan digunakan untuk
menampilkan halaman tambah data dan sekaligus untuk proses insert data melalui Rest API.

Silahkan buat sebuah folder baru dengan nama create di dalam folder
pages/admin/categories dan di dalam folder create silahkan buat file baru dengan
nama index.vue, kemudian masukkan kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> ADD NEW CATEGORY</span>
</div>
<div class="card-body">

<form @submit.prevent="storeCategory">

<div class="form-group">
<label>GAMBAR</label>
<input type="file" @change="handleFileChange"
class="form-control">
<div v-if="validation.image" class="mt-2">
<b-alert show variant="danger">{{
validation.image[0] }}</b-alert>
</div>
</div>

<div class="form-group">
<label>NAMA CATEGORY</label>
<input type="text" v-model="category.name"
placeholder="Masukkan Nama Category" class="form-control">
<div v-if="validation.name" class="mt-2">
<b-alert show variant="danger">{{
validation.name[0] }}</b-alert>
</div>
</div>

712
<button class="btn btn-info mr-1 btn-submit"
type="submit"><i class="fa fa-paper-plane"></i>
SAVE</button>
<button class="btn btn-warning btn-reset"
type="reset"><i class="fa fa-redo"></i>
RESET</button>

</form>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Add New Category - Administrator',
}
},

data() {
return {
//state category
category: {
image: '',
name: ''
},
//state validation
validation: []
}
},

methods: {

//handle file upload


handleFileChange(e) {

713
//get image
let image = this.category.image = e.target.files[0]

//check fileType
if (!image.type.match('image.*')) {

//if fileType not allowed, then clear value and set null
e.target.value = ''

//set state "category.image" to null


this.category.image = null

//show sweet alert


this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})
}

},

//method "storeCategory"
async storeCategory() {

//define formData
let formData = new FormData();

formData.append('image', this.category.image)
formData.append('name', this.category.name)

//sending data to action "storeCategory" vuex


await this.$store.dispatch('admin/category/storeCategory',
formData)
//success
.then(() => {

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000

714
})

//redirect route "admin-categories"


this.$router.push({
name: 'admin-categories'
})

})

//error
.catch(error => {

//assign error to state "validation"


this.validation = error.response.data
})
}
}

}
</script>

<style>

</style>

Dari penambahan kode di atas pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga mengatur untuk meta title yang ada di dalam view atau component ini.

//meta
head() {
return {
title: 'Add New Category - Administrator',
}
},

715
Dan di dalam data function kita membuat 2 state, yaitu category dan validation. Untuk
state category akan memiliki key lagi di dalamnya, state ini akan digunakan untuk
menampung data yang diinputkan dari form. Dan untuk state validation akan digunakan
untuk menampung response error validasi yang di dapatkan dari Rest API.

return {
//state category
category: {
image: '',
name: ''
},
//state validation
validation: []
}

Kemudian di dalam method kita membuat 2 fungsi, yaitu :

handleFileChange
storeCategory

handleFileChange

Method ini akan dijalankan ketika input file di dalam template diubah, karena di dalam input
tersebut kita memberikan event @change yang mengarah ke dalam method ini.

<input type="file" @change="handleFileChange" class="form-control">

Di damana di dalam method handleFileChange pertama kita akan melakukan assign file
yang dipilih dari komputer ke dalam state category.image.

//get image
let image = this.category.image = e.target.files[0]

Setelah itu, kita membuat kondisi untuk memeriksa apakah file yang diupload tersebut
menggunakan ekstensi image atau bukan. Jika file yang di pilih buka format image, maka
kita akan melakukan beberapa aksi, yang pertama mengosongkan input file di formnya
terlebih dahulu.

716
//if fileType not allowed, then clear value and set null
e.target.value = ''

Setelah itu, state category.image kita juga set ke null.

//set state "category.image" to null


this.category.image = null

Kemudian kita tampilkan sebuah alert atau pesan bahwa file yang akan di upload tidak
sesuai dengan yang di harapkan.

//show sweet alert


this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})

storeCategory

Method ini akan digunakan untuk menampung data yang di dapatkan dari form sebelum
dikirimkan ke action Vuex. Disini pertama kita melakukan inisialisasi FormData.

//define formData
let formData = new FormData();

Setelah itu, kita melakukan append FormData dari data yang di dapatkan dari input yang
disimpan di dalam state category.image dan category.name.

formData.append('image', this.category.image)
formData.append('name', this.category.name)

Kemudian kita melakukan dispatch atau memanggil action Vuex yang bernama

717
storeCategory yang berada di dalam file store/admin/category.js dan kita berikan
parameter formData.

Jika proses insert data menggunakan action Vuex berhasil, maka akan menampilkan sebuah
alert menggunakan Sweet Alert2.

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Setelah itu, kita akan di arahkan ke dalam route yang bernama admin-categories.

//redirect route "admin-categories"


this.$router.push({
name: 'admin-categories'
})

Tapi, jika proses insert data gagal dilakukan, maka akan melakukan assign error response
validasi ke dalam state validation.

//assign error to state "validation"


this.validation = error.response.data

Di dalam template, untuk form action kita arahkan ke dalam method yang sudah kita buat di
atas, yaitu storeCategory.

<form @submit.prevent="storeCategory">

//...
</form>

718
Langkah 3 - Uji Coba Proses Insert Data Category
Sekarang kita akan lakukan uji coba untuk proses insert data category. Silahkaan klik button
TAMBAH yang berada di halaman index cateegory atau bisa menggunakan link berikut ini
http://localhost:3000/admin/categories/create jika berhasil maka kita akan mendapatkan
hasil seperti berikut ini :

719
Membuat Proses Edit dan Update Data Category

Setelah berhasil membuat proses insert data ke dalam database menggunakan Rest API dan
Vuex, maka sekarang kita lanjutkan untuk membuat proses edit dan update data.

Konsepnya kita akan menampilkan datanya terlebih dahulu di dalam form edit dan setelah
data berhasil di tampilkan, kita bisa sesuaikan datanya, setelah itu kita membuat proses
update agar data yang telah disesuaikan tersebut dapat berubah juga di dalam database.

Langkah 1 - Menambahkan State, Mutation dan Action di Vuex


Sekarang kita akan menambahakn sebuah state, mutation dan action baru untuk
kebutuhan edit dan update data category. Silahkan buka file store/admin/category.js
kemudian ubah semua kode-nya menjadi seperti berikut ini :

//state
export const state = () => ({

//categories
categories: [],

//page
page: 1,

//category
category: {}

})

//mutations
export const mutations = {

//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {

//set value state "categories"


state.categories = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

720
//set value state "page"
state.page = payload
},

//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {

//set value state "category"


state.category = payload
},

//actions
export const actions = {

//get categories data


getCategoriesData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/categories" with method


"GET"
this.$axios.get(`/api/admin/categories?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_CATEGORIES_DATA"


commit('SET_CATEGORIES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store category
storeCategory({ dispatch, commit }, payload) {

//set promise

721
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/categories" with method


"POST"
this.$axios.post('/api/admin/categories', payload)

//success
.then(() => {

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//get detail category


getDetailCategory({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//get to Rest API "/api/admin/categories/:id" with method


"GET"
this.$axios.get(`/api/admin/categories/${payload}`)

//success
.then(response => {

//commit to mutation "SET_CATEGORY_DATA"


commit('SET_CATEGORY_DATA', response.data.data)

//resolve promise
resolve()

})

722
})

},

//update category
updateCategory({ dispatch, commit }, { categoryId, payload }) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/categories/:id" with method


"POST"
this.$axios.post(`/api/admin/categories/${categoryId}`,
payload)

//success
.then(() => {

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

Dari perubahan kode Vuex di atas, pertama kita menambahkan 1 state baru dengan nama
category.

//category
category: {}

723
State tersebut akan digunakan untuk menyimpan detail data category nanti-nya.

Setelah itu, kita menambahkan 1 mutation yang nanti akan digunakan untuk mengupdate
nilai dari state category di atas dari hasil response Rest API yang dikirim oleh action.

//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {

//set value state "category"


state.category = payload
},

Dan kita menambahkan 2 action baru, yaitu :

getDetailCategory - digunakan untuk mendapatkan detail category.


updateCategory - digunakan untuk melakukan proses update category.

getDetailCategory

Action ini akan digunakan untuk mendapatkan detail data category dari database
menggunakan Rest API. Disini kita memanggil sebuah endpoint
/api/admin/categories/:id menggunakan Axios dengan method GET.

//get to Rest API "/api/admin/categories/:id" with method "GET"


this.$axios.get(`/api/admin/categories/${payload}`)

Jika data berhasil di dapatkan, maka kita akan melakukan commit ke dalam mutation yang
bernama SET_CATEGORY_DATA dan kita berikan parameter data response dari Rest API.

//commit to mutation "SET_CATEGORY_DATA"


commit('SET_CATEGORY_DATA', response.data.data)

updateCategory

Action ini akan digunakan untuk melakukan proses update data ke dalam database. Disini
kita mengirim data ke dalam sebuah endpoint /api/admin/categories/:id dengan
sebuah data yang di kirimkan dari component / view.

724
//store to Rest API "/api/admin/categories/:id" with method "POST"
this.$axios.post(`/api/admin/categories/${categoryId}`, payload)

Jika proses http request berhasil, maka kita akan menjalankan dispatch ke dalam action
getCategoriesData dengan tujuan agar melakukan proses fetching ulang.

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

Langkah 2 - Menambahkan Button Edit


Setalah berhasil menambahkan state, mutation dan action di dalam Vuex, sekarang
kita akan belajar untuk menampilkan button EDIT di dalam table.

Silahkan buka file pages/admin/categories/index.vue, kemudian cari kode berikut ini


:

<b-table striped bordered hover :items="categories.data"


:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50" :src="data.item.image" />
</template>
</b-table>

Dan ubah menjadi seperti berikut ini :

725
<b-table striped bordered hover :items="categories.data"
:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50" :src="data.item.image" />
</template>
<template v-slot:cell(actions)="row">
<b-button :to="{name: 'admin-categories-edit-id', params: {id:
row.item.id}}" variant="info" size="sm">
EDIT
</b-button>
</template>
</b-table>

Di atas kita menambahkan 1 button baru untuk kita gunakan dalam proses edit data,
dimana di dalam button tersebut kita arahkan ke dalam route yang bernama admin-
categories-edit-id dan kita tambahkan juga parameter berupa data ID params: {id:
row.item.id}.

Sekarang jika kita coba refresh halaman index categories, maka kita akan mendapatkan
kurang lebih tampilannya seperti berikut ini :

Langkah 3 - Membuat View Edit Category


Langkah selanjutnya, kita akan membuat sebuah component / view yang digunakan untuk
menampilkan data yang akan di edit dan sekaligus membuat proses update-nya.

726
Silahkan buat folder baru dengan nama edit di dalam folder pages/admin/categories
dan di dalam folder edit silahkan buat file baru dengan nama _id.vue dan masukkan
kode berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> EDIT CATEGORY</span>
</div>
<div class="card-body">

<form @submit.prevent="updateCategory">

<div class="form-group">
<label>GAMBAR</label>
<input type="file" @change="handleFileChange"
class="form-control">
</div>

<div class="form-group">
<label>NAMA CATEGORY</label>
<input type="text" v-model="category.name"
placeholder="Masukkan Nama Category" class="form-control">
<div v-if="validation.name" class="mt-2">
<b-alert show variant="danger">{{
validation.name[0] }}</b-alert>
</div>
</div>

<button class="btn btn-info mr-1 btn-submit"


type="submit"><i class="fa fa-paper-plane"></i>
UPDATE</button>
<button class="btn btn-warning btn-reset"
type="reset"><i class="fa fa-redo"></i>
RESET</button>

</form>
</div>

727
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Edit Category - Administrator',
}
},

data() {
return {
//state category
category: {
image: '',
name: ''
},
//state validation
validation: []
}
},

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/category/getDetailCategory',
route.params.id)
},

//mounted
mounted() {
this.category.name =
this.$store.state.admin.category.category.name
},

728
//method
methods: {

//handle file upload


handleFileChange(e) {

//get image
let image = this.category.image = e.target.files[0]

//check fileType
if (!image.type.match('image.*')) {

//if fileType not allowed, then clear value and set null
e.target.value = ''

//set state "category.image" to null


this.category.image = null

//show sweet alert


this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})
}

},

//method "updateCategory"
async updateCategory() {

//define formData
let formData = new FormData();

formData.append('image', this.category.image)
formData.append('name', this.category.name)
formData.append("_method", "PATCH")

//sending data to action "updateCategory" vuex


await this.$store.dispatch('admin/category/updateCategory', {
categoryId: this.$route.params.id,
payload: formData
})
//success

729
.then(() => {

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

//redirect route "admin-categories"


this.$router.push({
name: 'admin-categories'
})

})

//error
.catch(error => {

//assign error to state "validation"


this.validation = error.response.data
})
}
}

}
</script>

<style>

</style>

Dari penambahan kode di atas pertama kita atur agar view atau component ini
menggunakan induk layout dari admin.

//layout
layout: 'admin',

Setelah itu, kita juga mengatur untuk meta title yang ada di dalam view atau component ini.

730
//meta
head() {
return {
title: 'Edit Category - Administrator',
}
},

Dan di dalam data function kita membuat 2 state baru, yairu category dan validation.
Untuk state category akan di isi data dari Vuex untuk di tampilkan di halaman input form.

//state category
category: {
image: '',
name: ''
},

Dan untuk state validation akan digunakan untuk menyimpan data response error
validasi yang di dapatkan dari Rest API.

//state validation
validation: []

Setelah itu, di dalam hook asyncData kita memanggil atau menjalankan dispatch ke dalam
action yang ada di dalam Vuex dengan bernama getDetailCategory dan memberikan
parameter ID yang di dapatkan dari URL.

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('admin/category/getDetailCategory',
route.params.id)
},

Selanjutnya, saat proses mounted, kita melakukan assign state category.name dengan
data yang di dapatkan dari state yang ada di dalam Vuex, yaitu nama category.

731
//mounted
mounted() {
this.category.name = this.$store.state.admin.category.category.name
},

Kemudian di dalam method kita membuat 2 fungsi, yaitu :

handleFileChange
updateCategory

handleFileChange

Method ini akan dijalankan ketika input file di dalam template diubah, karena di dalam input
tersebut kita memberikan event @change yang mengarah ke dalam method ini.

<input type="file" @change="handleFileChange" class="form-control">

Di damana di dalam method handleFileChange pertama kita akan melakukan assign file
yang dipilih dari komputer ke dalam state category.image.

//get image
let image = this.category.image = e.target.files[0]

Setelah itu, kita membuat kondisi untuk memeriksa apakah file yang diupload tersebut
menggunakan ekstensi image atau bukan. Jika file yang di pilih buka format image, maka
kita akan melakukan beberapa aksi, yang pertama mengosongkan input file di formnya
terlebih dahulu.

//if fileType not allowed, then clear value and set null
e.target.value = ''

Setelah itu, state category.image kita juga set ke null.

732
//set state "category.image" to null
this.category.image = null

Kemudian kita tampilkan sebuah alert atau pesan bahwa file yang akan di upload tidak
sesuai dengan yang di harapkan.

//show sweet alert


this.$swal.fire({
title: 'OOPS!',
text: "Format File Tidak Didukung!",
icon: 'error',
showConfirmButton: false,
timer: 2000
})

updateCategory

Method ini digunakan untuk menampung data yang dikirim dari form sebelum di kirimkan ke
dalam Vuex untuk proses update data dengan Rest API.

Disini pertama-tama kita melakukan inisialisasi sebuah FormData, tujuannya adalah untuk
menampung data menjadi satu agar mudah di kirim ke dalam server.

//define formData
let formData = new FormData();

Setelah itu, kita buat beberapa append dari formData yang berisi data category dan
termasuk method untuk update, yaitu PATCH.

formData.append('image', this.category.image)
formData.append('name', this.category.name)
formData.append("_method", "PATCH")

Kemudian kita kirimkan formData tersebut ke dalam sebuah action Vuex dengan nama
updateCategory.

733
//sending data to action "updateCategory" vuex
await this.$store.dispatch('admin/category/updateCategory', {
categoryId: this.$route.params.id,
payload: formData
})

Di atas kita mengirim 2 data ke dalam Vuex, yaitu category ID yang di dapatkan dari URL
dan payload yang berisi formData.

Jika proses update data berhasil, maka akan menampilkan sebuah alert menggunakan
Sweet Alert2. Kurang lebih seperti berikut ini :

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Diupdate!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Setelah itu, kita redirect / arahkan ke dalam route yang bernama admin-categories.

Tapi jika proses update data gagal, maka akan melakukan assign sebuah response error
validasi ke dalam state yang bernama validation.

//assign error to state "validation"


this.validation = error.response.data

Langkah 4 - Uji Coba Proses Update Data Category


Sekarang kita akan lakukan uji coba untuk proses edit dan update data categories. Silahkan
klik button EDIT di salah satu data yang ada di halaman index categories, jika berhasil
maka kita akan mendapatkan halaman seperti berikut ini :

734
Setelah itu, silahkan diubah isi sesuai dengan kebutuhan dan untuk gambar bisa kita isi atau
kosongkan, setelah itu silahkan klik UPDATE dan jika berhasil maka kita akan mendapatkan
pesan berhasil update seperti berikut ini :

735
Membuat Proses Delete Data Category

Di materi kali ini kita akan belajar bagaimana cara membuat fitur delete data category dari
database. Dan tentu saja kita tetap menggunakan Vuex untuk melakukan proses delete
data menggunakan Rest API. Nanti kita juga akan kombinasikan menggunakan Sweet
Alert2 untuk menampilkan jendela konfirmasi sebelum data benar-benar di delete.

Langkah 1 - Menambahkan Action di Vuex


Sekarang kita akan menambahkan sebuah action baru di dalam Vuex untuk proses delete
data. SIlahkan buka file store/admin/category.js kemudian ubah kode-nya menjadi
seperti berikut ini :

//state
export const state = () => ({

//categories
categories: [],

//page
page: 1,

//category
category: {}

})

//mutations
export const mutations = {

//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {

//set value state "categories"


state.categories = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload

736
},

//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {

//set value state "category"


state.category = payload
},

//actions
export const actions = {

//get categories data


getCategoriesData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/categories" with method


"GET"
this.$axios.get(`/api/admin/categories?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_CATEGORIES_DATA"


commit('SET_CATEGORIES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//store category
storeCategory({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

737
//store to Rest API "/api/admin/categories" with method
"POST"
this.$axios.post('/api/admin/categories', payload)

//success
.then(() => {

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//get detail category


getDetailCategory({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//get to Rest API "/api/admin/categories/:id" with method


"GET"
this.$axios.get(`/api/admin/categories/${payload}`)

//success
.then(response => {

//commit to mutation "SET_CATEGORY_DATA"


commit('SET_CATEGORY_DATA', response.data.data)

//resolve promise
resolve()

})

})

738
},

//update category
updateCategory({ dispatch, commit }, { categoryId, payload }) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/admin/categories/:id" with method


"POST"
this.$axios.post(`/api/admin/categories/${categoryId}`,
payload)

//success
.then(() => {

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//destroy category
destroyCategory({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {
//delete to Rest API "/api/admin/categories/:id" with method
"DELETE"
this.$axios.delete(`/api/admin/categories/${payload}`)

//success
.then(() => {

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

739
//resolve promise
resolve()

})

})

},

Dari penambahan kode di atas, kita menambahkan 1 action baru dengan nama
destroyCategory, dimana di dalamnya kita melakukan http request menggunakan Axios
ke dalam endpoint /api/admin/categories/:id dengan method DELETE.

//delete to Rest API "/api/admin/categories/:id" with method "DELETE"


this.$axios.delete(`/api/admin/categories/${payload}`)

Jika proses delete berhasil, maka kita akan menjalankan dispatch ke dalam action yang
bernama getCategoriesData, dengan tujuan melakukan fetching ulang.

//dispatch action "getCategoriesData"


dispatch('getCategoriesData')

Langkah 2 - Menambahkan Button dan Fungsi Delete


Setelah berhasil menambahkan action di dalam Vuex, langkah selanjutnya kita akan
menampilkan button delete di dalam table dan membuat method untuk proses delete.
Silahkan buka file pages/admin/categories/index.vue, kemudian ubah kode-nya
menjadi seperti berikut ini :

<template>
<main class="c-main">
<div class="container-fluid">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<div class="card border-0 rounded shadow-sm border-top-

740
orange">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-
folder"></i> CATEGORIES</span>
</div>
<div class="card-body">

<div class="form-group">
<div class="input-group mb-3">
<div class="input-group-prepend">
<nuxt-link :to="{name: 'admin-categories-
create'}" class="btn btn-warning btn-sm" style="padding-top: 10px;">
<i class="fa fa-plus-circle"></i> ADD
NEW</nuxt-link>
</div>
<input type="text" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="cari
berdasarkan nama category">
<div class="input-group-append">
<button @click="searchData" class="btn btn-
warning"><i class="fa fa-search"></i>SEARCH</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="categories.data"


:fields="fields" show-empty>
<template v-slot:cell(image)="data">
<img class="img-fluid" width="50"
:src="data.item.image" />
</template>
<template v-slot:cell(actions)="row">
<b-button :to="{name: 'admin-categories-edit-id',
params: {id: row.item.id}}" variant="info" size="sm">
EDIT
</b-button>
<b-button variant="danger" size="sm"
@click="destroyCategory(row.item.id)">DELETE</b-button>
</template>
</b-table>

<!-- pagination -->


<b-pagination align="right"
:value="categories.current_page" :total-rows="categories.total"
:per-page="categories.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

741
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</template>

<script>
export default {

//layout
layout: 'admin',

//meta
head() {
return {
title: 'Categories - Administrator',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'Image',
key: 'image',
tdClass: 'text-center'
},
{
label: 'Category Name',
key: 'name'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

//state search
search: ''
}
},

742
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('admin/category/getCategoriesData')
},

//computed
computed: {

//categories
categories() {
return this.$store.state.admin.category.categories
},
},

//method
methods: {
//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/category/SET_PAGE', 1)

//dispatch on action "getCategoriesData"


this.$store.dispatch('admin/category/getCategoriesData',
this.search)
},

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('admin/category/SET_PAGE', page)

//dispatch on action "getCategoriesData"


this.$store.dispatch('admin/category/getCategoriesData',
this.search)
},

//method "destroyCategory"
destroyCategory(id) {
this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',

743
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {

//dispatch to action "deleteCategory" vuex


this.$store.dispatch('admin/category/destroyCategory', id)
.then(() => {

//feresh data
this.$nuxt.refresh()

//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

})
}
})
}

}
</script>

<style>

</style>

Dari perubahan kode di atas, pertama kita menambahkan 1 button batu untuk proses delete
data.

<b-button variant="danger" size="sm"


@click="destroyCategory(row.item.id)">DELETE</b-button>

744
Di atas kita menambahkan event @click yang di arahkan ke dalam method yang bernama
destroyCategory dan di dalamnya kita berikan parameter dari ID category.

Setelah itu, kita menambahkan 1 method baru dengan nama destroyCategory, dimana
jika dijalankan maka akan menampilkan sebuah jendela konfirmasi menggunakan Sweet
Alert2.

this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
})

Jika button konfirmasi di klik, maka akan menjalankan / memanggil dispatch ke dalam action
Vuex yang bernama destroyCategory dan kita tambahkan parameter ID yang di
dapatkan dari button delete.

Jika proses delete data berhasil, maka pertama kita melakukan refresh project Nuxt.js,
dengan tujuan agar data yang ditampilkan bisa terupdate.

//feresh data
this.$nuxt.refresh()

Setelah itu, kita akan menampilkan alert lagi menggunakan Sweet Alert2 dari status berhasil
melakukan proses delete data.

//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Data Berhasil Dihapus!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

745
Langkah 3 - Uji Coba Proses Delete Data Category
Sekarang kita bisa mencobanya dengan membuka halaman categories atau bisa melalui link
berikut ini http://localhost:3000/admin/categories, jika berhasil maka kita akan
mendapatkan tampilan kurang lebih seperti berikut ini :

Jika kita klik button DELETE di salah satu data yang ada, maka akan menampilkan sebuah
konfirmasi kurang lebih seperti berikut ini :

Dan jika kita klik YA, HAPUS!, maka data akan berhasil dihapus dan kita mendapatkan

746
hasil seperti berikut ini :

747
Konfigurasi Vuex Admin Product

Pada materi kali ini kita akan belajar membuat konfigurasi Vuex Admin Product. Disini kita
akan menambahkan beberapa store, yaitu state, mutations dan actions.

Konfigurasi Vuex Admin Product


Silahkan buat file baru dengan nama product.js di dalam folder store/admin/ dan
setelah itu masukkan kode berikut ini di dalamnya.

//state
export const state = () => ({

//products
products: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {

//set value state "products"


state.products = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

//actions
export const actions = {

748
//get products data
getProductsData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/admin/products" with method "GET"


this.$axios.get(`/api/admin/products?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_PRODUCTS_DATA"


commit('SET_PRODUCTS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Dari penambahan kode store Vuex di atas, pertama kita menambahkan 2 state baru, yaitu
products dan page. Untuk state products akan digunakan untuk menyimpan response
data yang di dapatkan dari Rest API. Sedangkan untuk state page akan digunakan untuk
menyimpan nomor halaman untuk pagination.

//products
products: [],

//page
page: 1,

Dan kita juga menambahkan 2 mutation baru yaitu :

SET_PRODUCTS_DATA - digunakan untuk mengubah nilai dari state products


dengan data yang dikirimkan oleh action yaitu berupa data response dari Rest API.

749
//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {

//set value state "products"


state.products = payload
},

SET_PAGE - digunakan untuk mengubah nilai dari state page dengan data yang
dikirmkan dari component / view yaitu berupa nomor halaman untuk pagination.

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

Kemudian di dalam action kita menambahkan 1 method baru yang bernama


getProductsData.

//get products data


getProductsData({ commit, state }, payload) {

//...
}

Dimana di dalamnya pertama kita membuat sebuah variable dengan nama search, dengan
isi yang di dapatkan dari parameter.

//search
let search = payload ? payload : ''

Variable tersebut akan kita sertakan saat proses http request ke dalam endpoint Rest API
sebagai parameter untuk pencarian.

Selanjutnya untuk proses http request kita menggunakan Axios dengan method GET dan

750
untuk endpoint-nya adalah /api/admin/products?q=""&page="". Untuk parameter q
akan diisi dari variable search dan untuk parameter page akan diambil dari state yang
bernama page.

//fetching Rest API "/api/admin/products" with method "GET"


this.$axios.get(`/api/admin/products?q=${search}&page=${state.page}`)

Jika proses fetching http request berhasil, maka akan melakukan commit ke dalam mutation
yang bernama SET_PRODUCTS_DATA dengan memberikan parameter berupa response data
dari Rest API.

//commit ti mutation "SET_PRODUCTS_DATA"


commit('SET_PRODUCTS_DATA', response.data.data)

751
Membuat Component Hader dan Footer

Sekarang kita akan belajar membuat component Header dan Footer yang nanti akan
digunakan untuk halaman depan website. Kedua component ini akan sering kita gunakan di
semua view yang ada.

Langkah 1 - Membuat Component Header

Silahkan buat folder baru dengan nama web di dalam folder components dan di dalam
folder web tersebut, silahkan buat file baru dengan nama header.vue, kemudian
masukkan kode berikut ini di dalamnya :

<template>
<header class="section-header fixed-top">
<section class="header-main border-bottom">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-lg-3 col-sm-4 col-md-4 col-5">
<nuxt-link to="/" class="brand-wrap" data-abc="true">
<img src="/images/xiaomi.png" width="35" class="bg-light
p-2 rounded">
<span class="logo">MI STORE</span>
</nuxt-link>
</div>
<div class="col-lg-4 col-xl-5 col-sm-8 col-md-4 d-none d-md-
block">
<div class="search-wrap">
<div class="input-group w-100">
<input type="text" class="form-control search-form"
style="width:55%;" placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button class="btn btn-primary search-button"> <i
class="fa fa-search"></i> </button>
</div>
</div>
</div>
</div>
<div class="col-lg-5 col-xl-4 col-sm-8 col-md-4 col-7">
<div class="d-flex justify-content-end">
<a href="#" class="btn search-button btn-md d-md-block
ml-4"><i class="fa fa-shopping-cart"></i> <span class="ml-2">0</span> |
Rp. 0</a>

752
</div>
</div>
</div>
</div>
</section>
<nav class="navbar navbar-expand-md navbar-main border-bottom p-2">
<div class="container-fluid">
<div class="d-md-none my-2">
<div class="input-group">
<input type="search" name="search" class="form-control"
placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button class="btn btn-warning"> <i class="fa fa-
search"></i></button>
</div>
</div>
</div>
<button class="navbar-toggler collapsed" type="button" data-
toggle="collapse" data-target="#dropdown6"
aria-expanded="false"> <span class="navbar-toggler-
icon"></span> </button>
<div class="navbar-collapse collapse" id="dropdown6">
<ul class="navbar-nav mr-auto">
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-list-ul"></i> KATEGORI</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-shopping-bag"></i> SEMUA PRODUK</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-info-circle"></i> TENTANG</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-comments"></i> KONTAK</a> </li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown" v-if="!$auth.loggedIn">
<nuxt-link :to="{name: 'customer-login'}" class="nav-link"
href="#" role="button" aria-expanded="false"> <i class="fa fa-user-
circle"></i>
ACCOUNT</nuxt-link>
</li>
<li class="nav-item dropdown" v-if="$auth.loggedIn">
<nuxt-link :to="{name: 'customer-dashboard'}" class="nav-
link" href="#" role="button" aria-expanded="false"> <i class="fa fa-
tachometer-alt"></i>
DASHBOARD</nuxt-link>
</li>
</ul>

753
</div>
</div>
</nav>
</header>
</template>

<script>
export default {

}
</script>

<style scoped>
.btn {
font-size: initial;
}
</style>

Di atas, kita tambahkan component Header untuk menampilkan navigasi atau navbar dari
project Nuxt.js kita. Template di atas masih bersifat statis semua dan belum ada data
apapun yang akan di tampilkan disini.

Langkah 2 - Membuat Component Footer

Selanjutnya, kita akan membuat component lagi untuk Footer. Silahkan buat file baru
dengan nama footer.vue di dalam folder components/web, kemudian masukkan kode
berikut ini di dalamnya :

<template>
<footer class="pt-5" style="background: rgb(255, 255, 255); border-
top: 5px solid rgb(230 74 26);">
<div class="container-fluid">
<div class="row">
<div class="col-md-5 mb-4">
<h4 class="font-weight-bold">TENTANG</h4>
<hr style="border-top: 3px solid rgb(226, 232, 240); border-
radius: 0.5rem;">
<p> Mi Store Official Terpercaya di Indonesia. Jual Beli Aman
&amp; Harga Termurah! Belanja
sekarang. </p>
</div>
<div class="col-md-3 mb-4">

754
<h4 class="font-weight-bold">METODE PEMBAYARAN</h4>
<hr style="border-top: 3px solid rgb(226, 232, 240); border-
radius: 0.5rem;">
<div class="row">
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/BCA.png" style="width:
50px;"></div>
</div>
</div>
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/BNI.png" style="width:
45px;"></div>
</div>
</div>
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/BRI.png" style="width:
60px;"></div>
</div>
</div>
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/GOPAY.png" style="width:
60px;"></div>
</div>
</div>
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/indomaret-logo.png"
style="width: 60px;">
</div>
</div>
</div>
<div class="col-md-4 col-4 mb-3">
<div class="card rounded">
<div class="card-body p-2 text-center"><img
src="/images/payment/atm-bersama.jpg" style="width:
40px;"></div>
</div>

755
</div>
</div>
</div>
<div class="col-md-4 mb-4">
<h4 class="font-weight-bold">JAM OPERASIONAL</h4>
<hr style="border-top: 3px solid rgb(226, 232, 240); border-
radius: 0.5rem;">
<p><i class="fa fa-clock"></i> Toko Buka Setiap Hari :
<br><br> <strong>Senin - Jum'at</strong> ( 07.00 s/d
19.00 ) <br> <strong>Sabtu - Minggu</strong> ( 07.00 s/d
16.00 ) </p>
</div>
</div>
<div class="row text-center mt-3 pb-3">
<div class="col-md-12">
<hr> © <strong>MI STORE</strong> 2021 • Hak Cipta Dilindungi
</div>
</div>
</div>
</footer>
</template>

<script>
export default {

}
</script>

<style>

</style>

Di atas kita memanggil beberapa gambar yang ada di dalam component Footer dari folder
/static/images/. Dimana gambar-gambar tersebut sudah kita tambahkan sebelumnya.

756
Membuat Fitur Rating dan Review

Fitur ini bisa digunakan jika status invoice bernilai success, karena masih di localhost,
maka untuk status invoice nanti belum bisa otomatis diupdate menjadi success saat
pembayaran berhasil dilakukan. Jadi nanti kita akan manual mengganti status invoice di
dalam database untuk menguji fitur ini.

Sebelum itu, kita akan membuat module Vuex terlebih dahulu yang nanti digunakan untuk
menangani proses insert data rating dan review di dalam fitur ini.

Langkah 1 - Membuat Vuex Review


Silahkan buat file baru dengan nama review.js di dalam folder store/customer/ dan di
dalam file tersebut dilahkan masukkan kode berikut ini :

757
//actions
export const actions = {

//store review
storeReview({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/customer/reviews" with method


"POST"
this.$axios.post('/api/customer/reviews', payload)

//success
.then(response => {

//resolve promise
resolve(response.data)

})

//error
.catch(error => {
reject(error)
})

})
},

Di atas, kita hanya menambahkan 1 action baru dengan nama storeReview. Di dalam
action tersebut kita melakukan sending data menggunakan Axios dengan method POST ke
dalam endpoint /api/customer/reviews dan untuk parameter payload akan berisi data
rating dan review yang nanti akan dikirmkan oleh component / view.

//store to Rest API "/api/customer/reviews" with method "POST"


this.$axios.post('/api/customer/reviews', payload)

758
Langkah 2 - Menambahkan Button dan Modal untuk Review
Sekarang kita akan menambahkan button dan modal yang nanti digunakan untuk
mengirimkan rating dan review dari product yang di beli.

Silahkan buka file pages/customer/invoices/show/_snap_token.vue, kemudian


ubah semua kode-nya menjadi seperti berikut ini :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> DETAIL ORDER</h5>
<hr>
<table class="table table-bordered">
<client-only>
<tr>
<td style="width: 25%">
NO. INVOICE
</td>
<td style="width: 1%">:</td>
<td>
{{ invoice.invoice }}
</td>
</tr>
<tr>
<td>
FULL NAME
</td>
<td>:</td>
<td>
{{ invoice.name }}
</td>
</tr>
<tr>

759
<td>
PHONE
</td>
<td>:</td>
<td>
{{ invoice.phone }}
</td>
</tr>
<tr>
<td>
COURIER / SERVICE / COST
</td>
<td>:</td>
<td>
{{ invoice.courier }} / {{ invoice.courier_service
}} / Rp.
{{ formatPrice(invoice.courier_cost) }}
</td>
</tr>
<tr>
<td>
CITY
</td>
<td>:</td>
<td>
{{ invoice.city.name }}
</td>
</tr>
<tr>
<td>
PROVINCE
</td>
<td>:</td>
<td>
{{ invoice.province.name }}
</td>
</tr>
<tr>
<td>
ADDRESS
</td>
<td>:</td>
<td>
{{ invoice.address }}
</td>
</tr>

760
<tr>
<td>
GRAND TOTAL
</td>
<td>:</td>
<td>
Rp. {{ formatPrice(invoice.grand_total) }}
</td>
</tr>
<tr>
<td>
STATUS
</td>
<td>:</td>
<td>
<button @click="payment(invoice.snap_token)" v-
if="invoice.status == 'pending'"
class="btn btn-info">BAYAR SEKARANG</button>
<button v-else-if="invoice.status == 'success'"
class="btn btn-success"><i class="fa fa-check-
circle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'expired'"
class="btn btn-warning-2"><i class="fa fa-
exclamation-triangle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'failed'"
class="btn btn-danger"><i class="fa fa-times-
circle"></i> {{ invoice.status }}</button>
</td>
</tr>
</client-only>
</table>

</div>
</div>

<div class="card border-0 rounded shadow-sm border-top-orange


mt-4">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-
cart"></i> DETAIL ITEMS</span>
</div>
<div class="card-body">
<table class="table" style="border-style: solid
!important;border-color: rgb(198, 206, 214) !important;">
<tbody>
<client-only>

761
<tr v-for="order in invoice.orders" :key="order.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="order.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ order.product.title }}</b></h5>
<table class="table-borderless" style="font-
size: 14px">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ order.qty
}}</b></td>
</tr>
</table>
<!-- modal button -->
<button v-if="invoice.status == 'success'"
type="button" class="btn btn-warning-2 mt-4" data-toggle="modal"
:data-target="'#modal-'+order.id">
BERIKAN ULASAN
</button>

<!-- Modal -->


<div class="modal fade" ref="modal" :id="'modal-
'+order.id" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-
centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"
id="exampleModalLabel">ULASAN PRODUK</h5>
<button type="button" class="close"
data-dismiss="modal" aria-label="Close">
<span aria-
hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row justify-content-center">
<div class="col-md-7">
<vue-star-rating v-
model="rating.star" :show-rating="false">

762
</vue-star-rating>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="form-group">
<label class="font-weight-
bold">ULASAN</label>
<textarea class="form-control"
id="alamat" rows="3" placeholder="Masukkan Ulasan Produk"
v-
model="rating.review"></textarea>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-
secondary" data-dismiss="modal">TUTUP</button>
<button v-if="rating.star &&
rating.review" @click.prevent="storeReview(order.id, order.product.id)"
type="button"
class="btn btn-warning" data-
dismiss="modal">KIRIM</button>
</div>
</div>
</div>
</div>
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(order.price) }}</p>
</td>
</tr>
</client-only>
</tbody>
</table>
</div>
</div>

</div>
</div>
</div>
</div>
</template>

763
<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'

export default {

//middleware
middleware: 'isCustomer',

//layout
layout: 'default',

//register components
components: {
Sidebar
},

//meta
head() {
return {
title: 'Detail Order - Customer',
}
},

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('customer/invoice/getDetailInvoice',
route.params.snap_token)
},

//computed
computed: {
invoice() {
return this.$store.state.customer.invoice.invoice
}
},

//data function
data() {
return {
//state rating
rating: {
star: 0,
review: ''
},
}

764
},

//method
methods: {

//method "payment"
payment(snap_token) {
window.snap.pay(snap_token, {

onSuccess: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
},
onPending: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
},
onError: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
}
})
},

//method "storeReview"
async storeReview(orderId, productId) {

//define formData
let formData = new FormData();

formData.append('rating', this.rating.star)
formData.append('review', this.rating.review)
formData.append('order_id', orderId)
formData.append('product_id', productId)

765
//sending data to action "storeReview" vuex
await this.$store.dispatch('customer/review/storeReview',
formData)
//success
.then(() => {

//feresh data
this.$nuxt.refresh()

//clear state
this.rating.star = 0
this.rating.review = ''

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Ulasan Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 3000
})

//redirect route same page


this.$router.push({ path: this.$route.path });

})

.catch(() => {
//sweet alert
this.$swal.fire({
title: 'GAGAL!',
text: "Anda sudah membuat ulasan untuk produk ini!",
icon: 'error',
showConfirmButton: false,
timer: 3000
})

})

}
}

}
</script>

<style>

766
</style>

Dari perubahan kode diatas, pertama kita menambahkan button BERIKAN ULASAN di dalam
data item order. Dan button tersebut akan muncul jika status dari invoice success.

<!-- modal button -->


<button v-if="invoice.status == 'success'" type="button" class="btn btn-
warning-2 mt-4" data-toggle="modal" :data-target="'#modal-'+order.id">
BERIKAN ULASAN
</button>

Kemudian, kita juga menambahkan modal untuk menampilkan input rating dan review di
dalamnya.

<div class="modal fade" ref="modal" :id="'modal-'+order.id"


tabindex="-1" aria-hidden="true">

//...

</div>

Dan kita juga menambahkan state di dalam data function, state tersebut yang nanti akan
digunakan untuk menampung nilai dari data yang diinputkan.

//state rating
rating: {
star: 0,
review: ''
},

Setelah itu, kita juga membuat method yang bernama storeReview, method tersebut akan
dijalankan ketika button KIRIM di dalam modal di klik.

767
<button v-if="rating.star && rating.review"
@click.prevent="storeReview(order.id, order.product.id)" type="button"
class="btn btn-warning" data-dismiss="modal">KIRIM</button>

//method "storeReview"
async storeReview(orderId, productId) {

//...
}

Di dalam method di atas, pertama kita melakukan inisialisasi sebuah FormData, tujuannya
agar mempermudah dalam mengelompokan data yang akan dikirim ke dalam action Vuex.

//define formData
let formData = new FormData();

Setelah itu, kita buat append formData dengan key dan value yang di dapatkan dari state,
yang mana isinya akan diambilkan dari input form.

formData.append('rating', this.rating.star)
formData.append('review', this.rating.review)
formData.append('order_id', orderId)
formData.append('product_id', productId)

Dan kita akan kirimkan formData tersebut ke dalam action yang bernama storeReview.

//sending data to action "storeReview" vuex


await this.$store.dispatch('customer/review/storeReview', formData)

Jika data rating dan review berhasil dikirim, maka kita akan melakukan refresh Nuxt.js
menggunakan kode berikut ini :

768
//feresh data
this.$nuxt.refresh()

Dan kita set state rating ke dalam bentuk semula atau kosong.

//clear state
this.rating.star = 0
this.rating.review = ''

Dan kita akan menampilkan alert sukses menggunakan Sweet Alert2.

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Ulasan Berhasil Disimpan!",
icon: 'success',
showConfirmButton: false,
timer: 3000
})

Dan akan di redirect ke halaman yang sama.

//redirect route same page


this.$router.push({ path: this.$route.path });

Tapi, jika sudah ada data rating dan ulasan di dalam database, maka kita akan menculkan
error menggunakan Sweet Alert2.

769
//sweet alert
this.$swal.fire({
title: 'GAGAL!',
text: "Anda sudah membuat ulasan untuk produk ini!",
icon: 'error',
showConfirmButton: false,
timer: 3000
})

Langkah 3 - Uji Coba Fitur Rating dan Review


INFORMASI : Untuk sekarang kamu belum bisa melihat hasilnya, karena belum memiliki
data invoice apapun di dalam database.

ketika status di invoice sudah success, maka akan menampilkan button di dalam item
product yang nantinya bisa digunakan untuk mengirim sebuah rating dan review. Kurang
lebih seperti berikut ini :

770
Jika kita klik button BERIKAN ULASAN, maka akan menampilkan popup seperti berikut ini :

771
Kita bisa memberitan rating dan ulasan / review sesuai dengan product yang kita beli dan
jika kita klik KIRIM, maka akan mendapatkaan hasil seperti berikut ini :

772
Membuat Layout Default

Setelah berhasil membuat component Header dan juga Footer, maka kita akan lanjutkan
untuk membuat layout induk dari website yang kita buat.

Langkah 1 - Membuat Layout Default


Silahkan buat file baru dengan nama default.vue di dalam folder layouts, setelah itu
masukkan kode berikut ini di dalamnya.

773
<template>
<div>
<!-- header -->
<Header />
<!-- end header -->

<!-- content -->


<Nuxt />
<!-- end content -->

<!-- footer -->


<Footer />
<!-- end footer -->
</div>
</template>

<script>

import Header from '@/components/web/header.vue'


import Footer from '@/components/web/footer.vue'

export default {

//register components
components: {
Header,
Footer
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita melakukan import kedua component yang
sudah kita buat sebeleumnya, yaitu Header dan Footer.

import Header from '@/components/web/header.vue'


import Footer from '@/components/web/footer.vue'

774
Setelah itu, agar kedua component tersebut dapat digunakan di dalam template, maka kita
perlu melakukan register terlebih dahulu.

//register components
components: {
Header,
Footer
}

Dan untuk memanggil component tersebut di dalam template, kita bisa menggunakan kode
seperti berikut ini :

<!-- header -->


<Header />
<!-- end header -->

<!-- content -->


<Nuxt />
<!-- end content -->

<!-- footer -->


<Footer />
<!-- end footer -->

Langkah 2 - Kustomisasi Halaman Index


Selanjutnya, untuk melihat component yang sudah kita buat maka kita perlu melakukan
sedikit kustomisasi di file index page. Silahkan buka file pages/index.vue kemudian ubah
semua kode-nya menjadi seperti berikut ini :

775
<template>
<div class="mt-custom mb-5">
<div class="fade-in">
<h1 class="text-center">SANTRI KODING</h1>
</div>
</div>
</template>

<script>
export default {}
</script>

Sekarang, silahkan reload / refresh halaman depan website atau bisa buka di URL berikut ini
http://localhost:3000, jika berhasil maka kita akan mendapatkan hasil kurang lebih seperti
berikut ini :

776
Membuat Proses Register Customer

Setelah berhasil menampilkan component Header dan juga Footer, sekarang kita akan
lanjutkan belajar membuat proses register untuk customer di website toko online yang
sedang kita kembangkan. Sebelum itu, kita akan membuat action di Vuex untuk menangani
proses register menggunakan Rest API.

Langkah 1 - Membuat Action di Vuex


Silahkan buat folder baru dengan nama customer di dalam folder store. Dan di dalam
folder customer tersebut, silahkan buat file baru dengan nama customer.js dan
masukkan kode berikut ini di dalamnya.

777
//actions
export const actions = {

//store register
storeRegister({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/customer/register" with method


"POST"
this.$axios.post('/api/customer/register', payload)

//success
.then(() => {

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

Di atas, kita hanya menambahkan sebuah action baru dengan nama storeRegister,
action tersebut yang nanti akan digunakan untuk menangai proses register menggunakan
Rest API.

//store register
storeRegister({ commit }, payload) {

//...
}

Di dalam action tersebut, kita melakukan http request untuk mengirim data menggunakan

778
Axios dengan method POST ke dalam endpoint /api/customer/register. Dan kita
berikan parameter payload yang nantinya akan berisi data customer yang dikirimkan dari
component / view.

//store to Rest API "/api/customer/register" with method "POST"


this.$axios.post('/api/customer/register', payload)

Agar di dalam component / view dapat menerima callback promise, maka kita perlu
melakukan resolve.

//resolve promise
resolve()

Langkah 2 - Membuat View dan Fungsi Register


Sekarang kita lanjutkan untuk membuat halaman view untuk menampilkan form register
dan sekaligus membuat fungsi untuk proses registernya. Dan untuk fungsi register nanti
akan kita lempar ke dalam action Vuex yang sudah kita buat diatas.

Silahkan buat folder baru dengan nama customer di dalam folder pages. Dan di dalam
folder customer, silahkan buat file baru dengan nama register.vue, kemudian
masukkan kode berikut ini di dalamnya.

<template>
<div class="container mt-custom mb-3">
<div class="fade-in">
<div class="row justify-content-center">
<div class="col-md-7">
<div class="card-group">
<div class="card border-top-orange border-0 shadow-sm
rounded">
<div class="card-body">
<h3>REGISTER</h3>
<hr>
<div v-if="validation.message" class="mt-2">
<b-alert show variant="danger">{{ validation.message
}}</b-alert>
</div>
<form @submit.prevent="register">

779
<div class="row">
<div class="col-md-6">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-user"></i>
</span>
</div>
<input class="form-control" v-model="user.name"
type="text" placeholder="Nama Lengkap">
</div>
<div v-if="validation.name" class="mt-2">
<b-alert show variant="danger">{{
validation.name[0] }}</b-alert>
</div>
</div>
<div class="col-md-6">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-envelope"></i>
</span>
</div>
<input class="form-control" v-model="user.email"
type="email" placeholder="Email Address">
</div>
<div v-if="validation.email" class="mt-2">
<b-alert show variant="danger">{{
validation.email[0] }}</b-alert>
</div>
</div>
</div>

<div class="row">
<div class="col-md-6">
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" v-
model="user.password" type="password" placeholder="Password">
</div>
<div v-if="validation.password" class="mt-2">
<b-alert show variant="danger">{{

780
validation.password[0] }}</b-alert>
</div>
</div>
<div class="col-md-6">
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" v-
model="user.password_confirmation" type="password"
placeholder="Password Confirmation">
</div>
</div>
</div>

<div class="row">
<div class="col-12">
<button class="btn btn-info shadow-sm rounded-sm
px-4" type="submit">REGISTER</button>
<button class="btn btn-warning shadow-sm rounded-sm
px-4" type="reset">RESET</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="text-center mt-3">
Sudah punya akun? <nuxt-link :to="{name: 'customer-login'}"
class="font-weight-bold"> Login Disini
</nuxt-link>
</div>
</div>
</div>
</template>

<script>
export default {

//middleware
middleware: 'authenticated',

781
//layout
layout: 'default',

//meta
head() {
return {
title: 'Register - Customer',
}
},

//data function
data() {
return {
//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: ''
},
//validation
validation: []
}
},

//method
methods: {

//method "register"
async register() {

//dispatch to action "storeRegister"


await this.$store.dispatch('customer/customer/storeRegister', {
name: this.user.name,
email: this.user.email,
password: this.user.password,
password_confirmation: this.user.password_confirmation
})

.then(() => {

//sweet alert
this.$swal.fire({
title: 'REGISTER BERHASIL!',
text: "Proses Register Berhasil!",
icon: 'success',

782
showConfirmButton: false,
timer: 2000
})

//redirect
this.$router.push({
name: 'customer-login'
})

})

.catch(error => {
//assign validation
this.validation = error.response.data
})

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan middleware authenticated.

//middleware
middleware: 'authenticated',

Setelah itu, kita atur layout induk yang digunakan, disini kita akan atur dengan layout yang
sebelumnya sudah kita buat, yaitu default.

//layout
layout: 'default',

Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk

783
mengubah title yang ada di website kita.

//meta
head() {
return {
title: 'Register - Customer',
}
},

Dan di dalam data function, kita membuat 2 state baru, yaitu user dan validation. Untuk
state user akan digunakan untuk menampung data yang diinputkan di dalam form.
Sedangkan untuk state validation akan digunakan untuk menampung error response
validasi yang di dapatkan dari Rest API.

//state user
user: {
name: '',
email: '',
password: '',
password_confirmation: ''
},

//validation
validation: []

Dan di dalam method, kita menambahkan 1 function baru dengan nama register.

//method "register"
async register() {

//...
}

Di dalam method tersebut, kita melakukan dispatch atau memanggil sebuah action yang
bernama storeRegister yang berada di dalam file store/customer/customer.js dan
kita tambahkan beberapa parameter di dalamnya.

784
//dispatch to action "storeRegister"
await this.$store.dispatch('customer/customer/storeRegister', {
name: this.user.name,
email: this.user.email,
password: this.user.password,
password_confirmation: this.user.password_confirmation
})

Di atas kita menambahkan 4 parameter data, yaitu name, email, password dan
confirmation_password. Ke empat parameter tersebut yang nanti akan dikirim action ke
dalam endpoint Rest API untuk proses register.

Jika proses register berhasil dilakukan, maka kita akan menampilkan alert sukses
menggunakan Sweet Alert2.

//sweet alert
this.$swal.fire({
title: 'REGISTER BERHASIL!',
text: "Proses Register Berhasil!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Setelah itu, kita arahkan ke dalam route yang bernama customer-login.

//redirect
this.$router.push({
name: 'customer-login'
})

Tapi, jika proses register di atas gagal dilakukan, maka akan melakukan assign error
response validasi ke dalam state yaang bernama validation.

//assign validation
this.validation = error.response.data

785
Dan di dalam template, untuk form action akan kita arahkan ke dalam method yang sudah
kita buat di atas, yaitu register.

<form @submit.prevent="register">

//...
</form>

Langkah 3 - Uji Coba Proses Register Customer


Sekarang, silahkan buat link berikut ini http://localhost:3000/customer/register dan jika
berhasil, maka akan mendapatkan hasil seperti berikut ini :

Silahkan klik REGISTER tanpa mengisi data apapun dan jika berhasil, maka kita akan
mendapatkan error validasi yang dikirimkan oleh Laravel, kurang lebih seperti berikut ini :

786
Dan sekarang, silahkan coba masukkan data yang dibutuhkan dan klik REGISTER, jika
berhasil maka kita akan mendapatkan hasil seperti berikut ini :

787
Membuat Proses Login Customer

Setelah berhasil membuat proses register, maka sekarang kita lanjutkan untuk membuat
proses login untuk customer. Dimana kita akan melakukan proses login menggunakan data
yang kita masukkan saat register tadi. Karena menggunakan module Nuxt Auth, maka
untuk proses login kita tidak perlu membuat sebuah Vuex.

Langkah 1 - Membuat View dan Proses Login


Silahkan buat file baru dengan nama login.vue di dalam folder pages/customer,
kemudian masukkan semua kode berikut ini di dalam file tersebut.

<template>
<div class="container mt-custom mb-3">
<div class="fade-in">
<div class="row justify-content-center">
<div class="col-md-4">
<div class="card-group">
<div class="card border-top-orange border-0 shadow-sm
rounded">
<div class="card-body">
<h3>LOGIN</h3>
<hr>
<div v-if="validation.message" class="mt-2">
<b-alert show variant="danger">{{ validation.message
}}</b-alert>
</div>
<form @submit.prevent="login">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-envelope"></i>
</span>
</div>
<input class="form-control" v-model="user.email"
type="email" placeholder="Email Address">
</div>
<div v-if="validation.email" class="mt-2">
<b-alert show variant="danger">{{ validation.email[0]
}}</b-alert>
</div>
<div class="input-group mb-4">

788
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" v-model="user.password"
type="password" placeholder="Password">
</div>
<div v-if="validation.password" class="mt-2">
<b-alert show variant="danger">{{
validation.password[0] }}</b-alert>
</div>
<div class="row">
<div class="col-12">
<button class="btn btn-warning shadow-sm rounded-sm
px-4 w-100" type="submit">LOGIN</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="text-center mt-3">
Belum punya akun? <nuxt-link :to="{name: 'customer-register'}"
class="font-weight-bold">Daftar Sekarang</nuxt-link>
</div>
</div>
</div>
</template>

<script>
export default {

//middleware
middleware: 'authenticated',

//layout
layout: 'default',

//meta
head() {
return {
title: 'Login - Customer',
}

789
},

data() {
return {
//state user
user: {
email: '',
password: '',
},
//validation
validation: []
}
},

methods: {
async login() {

await this.$auth.loginWith('customer', {
data: {
email: this.user.email,
password: this.user.password
}
})

.then(() => {

//redirect
this.$router.push({
name: 'customer-dashboard'
})

})

.catch(error => {
//assign validation
this.validation = error.response.data
})
}

}
</script>

<style>

790
</style>

Dari penambahan kode di atas, pertama kita atur agar view atau component ini
menggunakan middleware authenticated.

//middleware
middleware: 'authenticated',

Setelah itu, kita atur layout induk yang digunakan, disini kita akan atur dengan layout yang
sebelumnya sudah kita buat, yaitu default.

//layout
layout: 'default',

Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
mengubah title yang ada di website kita.

//meta
head() {
return {
title: 'Login - Customer',
}
},

Di dalam data function, kita menambahkan 2 state baru, yaitu user dan validation.
Untuk state user akan digunakan untuk menampung data yang dikirimkan dari input form.
Sedangkan untuk state validation akan digunakan untuk menampung error response
validasi yang di dapatkan dari Rest API.

791
//state user
user: {
email: '',
password: '',
},

//validation
validation: []
}

Dan di dalam method, kita menambahkan 1 action baru yang bernama login, method
tersebut akan dijalankan ketika form di submit.

<form @submit.prevent="login">

//...
</form>

async login() {

//...
}

Di dalam method tersebut, kita melakukan proses login dengan memanggil konfigurasi auth
yang sudah kita buat di dalam file nuxt.config.js. Kurang lebih seperti berikut ini :

await this.$auth.loginWith('customer', {
data: {
email: this.user.email,
password: this.user.password
}
})

Di atas, kita melakukan proses otentikasi dengan memanggil nama strategy customer
dengan mengirimkan 2 data parameter, yaitu email dan password.

Jika proses otentikasi di atas berhasil, maka kita akan diarahkan / redirect ke dalam route

792
yang bernama customer-dashboard.

//redirect
this.$router.push({
name: 'customer-dashboard'
})

Tapi, jika proses otentikasi gagal dilakukan, maka akan melakukan assign data response
error validasi ke dalam state yang bernama validation.

Dan untuk menampilkan validasi di dalam template, kurang lebih seperti berikut ini :

<div v-if="validation.email" class="mt-2">


<b-alert show variant="danger">{{ validation.email[0] }}</b-alert>
</div>

<div v-if="validation.password" class="mt-2">


<b-alert show variant="danger">{{ validation.password[0] }}</b-
alert>
</div>

Langkah 2 - Uji Coba Proses Login


Seakarang silahkan buka URL berikut ini http://localhost:3000/customer/login dan jika
berhasil, maka akan mendapatkan hasil kurang lebih seperti berikut ini :

793
Silahkan klik LOGIN tanpa mengisi data apapun di dalam form, maka kita akan
mendapatkan response kurang lebih seperti berikut ini :

Sekarang, kita coba memasukkan data email dan password customer yang belum ada di
dalam database, maka kita akan mendapatkan error login failed seperti berikut ini :

794
Dan terakhir, silahkan coba masukkan email dan password customer yang sebelumnya
kita masukkan lewat proses register, dan jika berhasil maka akan mendapatkan hasil seperti
berikut ini :

Di atas, sebenernya kita sudah berhasil melakukan proses otentikasi, halaman error
tersebut muncul karena kita belum memiliki route yang bernama customer-dashboard.

795
Membuat Halaman Dashboard Customer

Setelah berhasil membuat proses otentikasi / login, maka sekarang kita tinggal membuat
halaman dashboard untuk customer. Dimana halaman ini yang pertama kali ditampilkan
ketika customer berhasil melakukan proses otentikasi dan di halaman ini kita juga akan
menampilkan statistik data invoice yang pernah dibuat oleh si customer tersebut.

Langkah 1 - Membuat Component Sidebar Menu Customer


Pertama, kita akan membuat sebuah component terlebih dahulu, dimana component
tersebut akan digunakan untuk menampilkan sidebar menu untuk customer.

Silahkan buat file baru dengan nama sidebar.vue di dalam folder component/web,
kemudian masukkan kode berikut ini di dalamnya.

<template>
<div class="card border-0 rounded shadow-sm border-top-orange">
<div class="card-body">
<h5>MAIN MENU</h5>
<hr>
<ul class="list-group">
<nuxt-link :to="{name: 'customer-dashboard'}"
class="list-group-item text-decoration-none text-dark text-
uppercase"><i
class="fa fa-tachometer-alt"></i> Dashboard
</nuxt-link>

<a href="#" class="list-group-item text-decoration-none text-


dark text-uppercase"><i
class="fa fa-shopping-cart"></i> My Orders
</a>

<a @click="logout" class="list-group-item text-decoration-none


text-dark text-uppercase"
style="cursor: pointer;"><i class="fa fa-sign-out-alt"></i>
Logout
</a>
</ul>
</div>
</div>
</template>

796
<script>
export default {

//method
methods: {

//method "logout"
async logout() {

//logout auth
await this.$auth.logout()

//redirect route customer login


this.$router.push({
name: 'customer-login'
})

}
</script>

<style scoped>
a.nuxt-link-active {
background: rgba(255, 222, 212, .05) !important;
}
</style>

Di atas, kita juga menambahkan fungsi untuk proses logout, di dalam button kita
menambahkan event @click yang kita arahkan ke dalam method yang bernama logout.

<a @click="logout" class="list-group-item text-decoration-none text-dark


text-uppercase" style="cursor: pointer;"><i class="fa fa-sign-out-
alt"></i> Logout </a>

Dan untuk method logout, di dalamnya kita melakukan proses logout dengan kode seperti
berikut ini :

797
//logout auth
await this.$auth.logout()

Jika proses logout berhasil, maka akan di arahkan / redirect ke dalam route yang bernama
customer-login.

//redirect route customer login


this.$router.push({
name: 'customer-login'
})

Langkah 2 - Membuat View Dashbaord Customer


Silahkan buat folder baru dengan nama dashboard di dalam folder pages/customer. Dan
di dalam folder dashboard tersebut silahkan buat file baru dengan nama index.vue,
kemudian masukkan kode berikut ini di dalamnya.

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-orange">
<div class="card-body">
<h5><i class="fa fa-tachometer-alt"></i> DASHBOARD</h5>
<hr>

<div class="row">
<div class="col-md-12">
<div class="alert alert-success" role="alert">
Selamat Datang <strong>{{ $auth.user.name }}</strong>
</div>
</div>
</div>

798
<div class="row">

<div class="col-6 col-lg-3">


<div class="card rounded shadow-sm overflow-hidden">
<div class="card-body p-0 d-flex align-items-center">
<div class="bg-primary py-4 px-5 mfe-3">
<i class="fas fa-circle-notch fa-spin fa-2x"></i>
</div>
<div>
<div class="text-value text-primary">{{ pending
}}</div>
<div class="text-muted text-uppercase font-weight-
bold small">PENDING</div>
</div>
</div>
</div>
</div>

<div class="col-6 col-lg-3">


<div class="card rounded shadow-sm overflow-hidden">
<div class="card-body p-0 d-flex align-items-center">
<div class="bg-success py-4 px-5 mfe-3">
<i class="fas fa-check-circle fa-2x"></i>
</div>
<div>
<div class="text-value text-success">{{ success
}}</div>
<div class="text-muted text-uppercase font-weight-
bold small">SUCCESS</div>
</div>
</div>
</div>
</div>

<div class="col-6 col-lg-3">


<div class="card rounded shadow-sm overflow-hidden">
<div class="card-body p-0 d-flex align-items-center">
<div class="bg-warning py-4 px-5 mfe-3">
<i class="fas fa-exclamation-triangle fa-2x"></i>
</div>
<div>
<div class="text-value text-warning">{{ expired
}}</div>
<div class="text-muted text-uppercase font-weight-
bold small">EXPIRED</div>
</div>

799
</div>
</div>
</div>

<div class="col-6 col-lg-3">


<div class="card rounded shadow-sm overflow-hidden">
<div class="card-body p-0 d-flex align-items-center">
<div class="bg-danger py-4 px-5 mfe-3">
<i class="fas fa-times-circle fa-2x"></i>
</div>
<div>
<div class="text-value text-danger">{{ failed
}}</div>
<div class="text-muted text-uppercase font-weight-
bold small">FAILED</div>
</div>
</div>
</div>
</div>

</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'

export default {

//middleware
middleware: 'isCustomer',

//layout
layout: 'default',

//register components
components: {
Sidebar
},

800
//meta
head() {
return {
title: 'Dashboard - Customer',
}
},

async asyncData({ $axios }) {

//fetching dashboard
const dashboard = await $axios.$get('/api/customer/dashboard')

return {
//count statistik
'pending': dashboard.data.count.pending,
'success': dashboard.data.count.success,
'expired': dashboard.data.count.expired,
'failed': dashboard.data.count.failed,
}
},

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita melakukam import component sidebar
terlebih dahulu.

//import sidebar
import Sidebar from '@/components/web/sidebar.vue'

Dan agar component tersebut dapat digunakan di dalam template, maka kita perlu
melakukan register.

801
//register components
components: {
Sidebar
},

Dan untuk memanggil component tersebut di dalam template, kita bisa menggunakan kode
seperti berikut ini :

<!-- sidebar -->


<Sidebar />
<!-- end sidebar -->

Kemudian, kita atur agar view ini menggunakan middleware isCustomer, artinya hanya
bisa diakses jika si user memiliki level customer dan sudah melakukan proses otentikasi.

//middleware
middleware: 'isCustomer',

Dan kita atur, agar view ini menggunakan layout default.

//layout
layout: 'default',

Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
mengubah title yang ada di website kita.

//meta
head() {
return {
title: 'Dashboard - Customer',
}
},

Dan kita juga melakukan fetching ke dalam endpoint Rest API menggunakan hook
asyncData, tujuannya agar proses fetching dilakukan secara SSR atau server side

802
rendering.

async asyncData({ $axios }) {

//...
}

Dan untuk endpoint-nya disini adalah /api/customer/dashboard. Dimana endpoint


tersebut akan berisi statistik data invoice sesuai dengan customer yang sedang login.

//fetching dashboard
const dashboard = await $axios.$get('/api/customer/dashboard')

kemudian, agar response dari hook asyncData dapat dipanggil di dalam template, maka
kita perlu melakukan return terlebih dahulu.

return {
//count statistik
'pending': dashboard.data.count.pending,
'success': dashboard.data.count.success,
'expired': dashboard.data.count.expired,
'failed': dashboard.data.count.failed,
}

Dan untuk menampilkan statistik di dalam template, kurang lebih seperti berikut ini :

{{ pending }}

{{ success }}

{{ expired }}

{{ failed }}

803
Langkah 3 - Uji Coba Halaman Dashboard Customer
Silahkan reload / refresh halaman-nya atau bisa buka di URL berikut ini
http://localhost:3000/customer/dashboard, jika berhasil maka kita akan mendapatkan hasil
seperti berikut ini :

804
Konfigurasi Vuex Customer Invoice

Sekarang kita akan belajar membuat state, mutations dan actions yang nantinya akan
kita gunakan untuk proses menampilkan data invoice berdasarkan customer yang sedang
login.

Membuat Konfigurasi Vuex Customer Invoice


Sekarang, kita akan membuat konfigurasi di dalam Vuex Customer Invoice, yaitu
menambahkan sebuah actions, mutation dan state.

Silahkan buat file baru dengan nama invoice.js di dalam folder store/customer,
kemudian masukkan kode berikut ini di dalamnya.

//state
export const state = () => ({

//invoices
invoices: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {

//set value state "invoices"


state.invoices = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

805
}

//actions
export const actions = {

//get invoices data


getInvoicesData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/customer/invoices" with method


"GET"
this.$axios.get(`/api/customer/invoices?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_INVOICES_DATA"


commit('SET_INVOICES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Dari penambahan kode di atas, pertama kita membuat 2 state baru, yaitu invoices dan
page.

//invoices
invoices: [],

//page
page: 1,

806
Untuk state invoices akan digunakan untuk menampung data invoice yang di dapatkan
dari Rest API dan untuk ditampilkan di component / view. Sedangkan untuk state page akan
digunakan untuk menyimpan nomor pagination halaman.

Dan di dalam mutation, kita membuat 2 method baru, yaitu :

SET_INVOICES_DATA
SET_PAGE

SET_INVOICES_DATA

Mutation ini akan digunakan untuk mengubah nilai state invoices dengan data yang
dikirimkan dari action, data tersebut berupa response dari Rest API.

//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {

//set value state "invoices"


state.invoices = payload
},

SET_PAGE

Mutation ini akan digunakan untuk mengubah isi dari state page dan isinya nanti akan
dikirimkan langsung melalui component / view, yaitu berupa data nomor pagination
halaman.

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

Kemudian di dalam actions kita menambahkan 1 method baru yang bernama


getInvoicesData.

807
//get invoices data
getInvoicesData({ commit, state }, payload) {

//...
}

Di dalam action tersebut, pertama kita membuat sebuah variable baru dengan nama
search dan isinya akan diambil dari parameter yang bernama payload dan isinya nanti
akan di dapatkan dari component / view.

//search
let search = payload ? payload : ''

Setelah itu, kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/customer/invoices?q=""&page="". Untuk parameter q akan
diisi dari variable search dan untuk parameter page akan diambil dari state yang bernama
page.

//fetching Rest API "/api/customer/invoices" with method "GET"


this.$axios.get(`/api/admin/customer?q=${search}&page=${state.page}`)

Jika proses fetching http request berhasil dilakukan, maka kita akan lakukan commit ke
dalam mutation yang bernama SET_INVOICES_DATA dengan menambahkan parameter
berupa data response yang di dapatkan dari Rest API.

//commit ti mutation "SET_INVOICES_DATA"


commit('SET_INVOICES_DATA', response.data.data)

808
Menampilkan Data Invoice Customer

Setelah berhasil membuat konfigurasi di dalam Vuex, maka sekarang kita akan lanjutkan
untuk menampilkan data di dalam component / view. Kita juga akan belajar membuat fitur
pencarian dan pagination.

Langkah 1 - Menampilkan Data Invoice


Silahkan buat folder baru dengan nama invoices di dalam folder pages/customer. Dan
di dalam folder invoices tersebut silahkan buat file baru dengan nama index.vue,
kemudian masukkan kode berikut ini di dalamnya.

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> MY ORDERS</h5>
<hr>

<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control"
placeholder="cari berdasarkan no. invoice">
<div class="input-group-append">
<button class="btn btn-warning"><i class="fa fa-
search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="invoices.data"


:fields="fields" show-empty>

809
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'" class="btn
btn-sm btn-primary"><i class="fa fa-circle-notch fa-spin"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'success'" class="btn
btn-sm btn-success"><i class="fa fa-check-circle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'expired'" class="btn
btn-sm btn-warning-2"><i class="fa fa-exclamation-triangle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'failed'" class="btn
btn-sm btn-danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'

export default {

//middleware
middleware: 'isCustomer',

//layout
layout: 'default',

//register components
components: {
Sidebar
},

//meta
head() {
return {

810
title: 'Invoices - Customer',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('customer/invoice/getInvoicesData')
},

//computed
computed: {

//invoices
invoices() {
return this.$store.state.customer.invoice.invoices
},
},

}
</script>

811
<style>

</style>

Di atas, pertama kita melakukan import component sidebar terlebih dahulu.

//import sidebar
import Sidebar from '@/components/web/sidebar.vue'

Setelah itu, agar component tersebut dapat digunakan di dalam template, maka kita perlu
melakukan register.

//register components
components: {
Sidebar
},

Dan untuk memanggil component tersebut di dalam template, kita bisa seperti berikut ini :

<!-- sidebar -->


<Sidebar />
<!-- end sidebar -->

Kemudian, kita atur agar view ini menggunakan middleware isCustomer, artinya hanya
bisa diakses jika si user memiliki level customer dan sudah melakukan proses otentikasi.

//middleware
middleware: 'isCustomer',

Dan kita atur, agar view ini menggunakan layout default.

//layout
layout: 'default',

812
Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
mengubah title yang ada di website kita.

//meta
head() {
return {
title: 'Invoices - Customer',
}
},

Di dalam data function, kita membuat state baru dengan nama field, state tersebut nanti
akan digenerate menjadi header dari table.

//table header
fields: [{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

Dan kita menggunakan hook asyncData untuk melakukan dispatch atau memanggil action
di dalam Vuex yang bernama getInvoicesData.

813
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('customer/invoice/getInvoicesData')
},

Jika dari proses di dalam Vuex berhasil dilakukan, maka kita akan mengambil datanya dari
state yang ada di Vuex. Disini kita menggunakan computed properti untuk mengambil data
tersebut.

//computed
computed: {

//invoices
invoices() {
return this.$store.state.customer.invoice.invoices
},
},

Dan untuk menampilkan data tersebut di dalam template, kita akan menggunakan
component dari Bootstrap Vue, yaitu <b-table>. Kurang lebih seperti berikut ini :

<b-table striped bordered hover :items="invoices.data" :fields="fields"


show-empty>

//...
</b-table>

Di atas, untuk properti :items akan diisi dengan data yang didapatkan dari state, dan disini
kita berikan value invoices yang kita buat di computed properti.

Sedangkan untuk properti :fields akan digunakan untuk mengenerate sebuah table
header dan disini kita berikan value state yang bernama fields, yang kita buat di dalam
data function.

Langkah 2 - Mengaktifkan Link Menu


Setelah berhasil membuat view atau component, maka secara otomatis Nuxt.js juga akan

814
membuatkan kita route dari file yang sudah kita buat di dalam halaman pages. Sekarang
kita akan mengaktifkan menu tersebut di dalam file component Sidebar.

Silahkan buka file components/web/sidebar.vue, kemudian cari kode berikut ini :

<a href="#" class="list-group-item text-decoration-none text-dark text-


uppercase"><i class="fa fa-shopping-cart"></i> My Orders</a>

Dan ubahlah menjadi seperti berikut ini :

<nuxt-link :to="{name: 'customer-invoices'}" class="list-group-item


text-decoration-none text-dark text-uppercase"><i class="fa fa-shopping-
cart"></i> My Orders
</nuxt-link>

Di atas yang semula masih menggunakan a href="#" kita ubah menjadi <nuxt-link dan
untuk route-nya kita arahkan ke dalam customer-invoices.

Sekarang silahkan klik menu My Orders yang ada di menu Sidebar atau bisa langsung ke
URL berikut ini http://localhost:3000/customer/invoices, maka jika berhasil kita akan
mendapatkan tampilan seperti berikut ini :

815
Langkah 3 - Membuat Fitur Pencarian
Sekarang kita lanjutkan untuk membuat fitur pencarian di dalam table, fitur ini akan
mempermudah kita jika ingin mencari data tertentu.

Silahkan buka pages/customer/invoices/index.vue kemudian ubah semua kode-nya


menjadi seperti berikut ini :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> MY ORDERS</h5>
<hr>

<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-model="search"
@keypress.enter="searchData" placeholder="cari berdasarkan no. invoice">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i
class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="invoices.data"


:fields="fields" show-empty>
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'" class="btn
btn-sm btn-primary"><i class="fa fa-circle-notch fa-spin"></i> {{

816
row.item.status }}</button>
<button v-if="row.item.status == 'success'" class="btn
btn-sm btn-success"><i class="fa fa-check-circle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'expired'" class="btn
btn-sm btn-warning-2"><i class="fa fa-exclamation-triangle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'failed'" class="btn
btn-sm btn-danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>
</b-table>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'

export default {

//middleware
middleware: 'isCustomer',

//layout
layout: 'default',

//register components
components: {
Sidebar
},

//meta
head() {
return {
title: 'Invoices - Customer',
}
},

//data function
data() {

817
return {
//table header
fields: [{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',
tdClass: 'text-center'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('customer/invoice/getInvoicesData')
},

//computed
computed: {

//invoices
invoices() {
return this.$store.state.customer.invoice.invoices
},
},

//method
methods: {

//method "searchData"
searchData() {

818
//commit to mutation "SET_PAGE"
this.$store.commit('customer/invoice/SET_PAGE', 1)

//dispatch on action "getInvoicesData"


this.$store.dispatch('customer/invoice/getInvoicesData',
this.search)
}
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.

//state search
search: ''

Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :

<input type="text" class="form-control" v-model="search"


@keypress.enter="searchData" placeholder="cari berdasarkan no. invoice">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i class="fa fa-
search"></i>
SEARCH
</button>
</div>

Di atas pada bagian input kita menambahkan event @keypress.enter dimana di


dalamnya akan memanggil method yang bernama searchData, di dalam button juga sama
kita menambahkan event @click yang kita arahkan ke dalam method searcData.

Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :

819
//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('customer/invoice/SET_PAGE', 1)

//dispatch on action "getInvoicesData"


this.$store.dispatch('customer/invoice/getInvoicesData',
this.search)
}

Di atas kita menjalankan 2 perintah, yang pertama melakukan commit langsung ke dalam
mutation SET_PAGE dengan paremeter / value 1.

Dan yang kedua melakukan dispatch ke dalam action Vuex yang bernama
getInvoicesData dan kita parsing sebuah parameter yaitu this.search yang isinya
adalah kata kunci yang di dapatkan dari input form.

Langkah 4 - Membuat Fitur Pagination


Setelah berhasil membuat fitur pencarian, sekarang kita lanjutkan untuk menambahkan fitur
pagination, fitur ini digunakan untuk menampilkan button navigasi ke halaman-halaman
yang lain.

Silahkan buka file pages/customer/invoices/index.vue, kemudian ubah semua kode-


nya menjadi seperti berikut ini :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> MY ORDERS</h5>
<hr>

820
<div class="form-group">
<div class="input-group mb-3">
<input type="text" class="form-control" v-model="search"
@keypress.enter="searchData" placeholder="cari berdasarkan no. invoice">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"><i
class="fa fa-search"></i>
SEARCH
</button>
</div>
</div>
</div>

<b-table striped bordered hover :items="invoices.data"


:fields="fields" show-empty>
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'" class="btn
btn-sm btn-primary"><i class="fa fa-circle-notch fa-spin"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'success'" class="btn
btn-sm btn-success"><i class="fa fa-check-circle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'expired'" class="btn
btn-sm btn-warning-2"><i class="fa fa-exclamation-triangle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'failed'" class="btn
btn-sm btn-danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>
</b-table>

<!-- pagination -->


<b-pagination align="right" :value="invoices.current_page"
:total-rows="invoices.total"
:per-page="invoices.per_page" @change="changePage" aria-
controls="my-table"></b-pagination>

</div>
</div>
</div>
</div>
</div>
</div>

821
</template>

<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'

export default {

//middleware
middleware: 'isCustomer',

//layout
layout: 'default',

//register components
components: {
Sidebar
},

//meta
head() {
return {
title: 'Invoices - Customer',
}
},

//data function
data() {
return {
//table header
fields: [{
label: 'No. Invoice',
key: 'invoice'
},
{
label: 'Grand Total',
key: 'grand_total'
},
{
label: 'Status Payment',
key: 'status',
tdClass: 'text-center'
},
{
label: 'Actions',
key: 'actions',

822
tdClass: 'text-center'
}
],

//state search
search: ''
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('customer/invoice/getInvoicesData')
},

//computed
computed: {

//invoices
invoices() {
return this.$store.state.customer.invoice.invoices
},
},

//method
methods: {

//method "searchData"
searchData() {

//commit to mutation "SET_PAGE"


this.$store.commit('customer/invoice/SET_PAGE', 1)

//dispatch on action "getInvoicesData"


this.$store.dispatch('customer/invoice/getInvoicesData',
this.search)
},

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('customer/invoice/SET_PAGE', page)

//dispatch on action "getInvoicesData"


this.$store.dispatch('customer/invoice/getInvoicesData',
this.search)

823
},
}

}
</script>

<style>

</style>

Dari perubahan kode di atas, kita menambahkan component dari Bootstrap Vue yang
bernama <b-paginate> dan isinya akan diambil dari properti computed yang kita buat
sebelumnya, dimana datanya di ambil dari state users yang ada di dalam Vuex.

<b-pagination align="right" :value="invoices.current_page" :total-


rows="invoices.total" :per-page="invoices.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

Kemudian kita membuat 1 method baru dengan nama changePage, method tersebut akan
di jalankan ketika kita melakukan navigasi di button pagination. kurang lebih seperti berikut
ini :

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('customer/invoice/SET_PAGE', page)

//dispatch on action "getInvoicesData"


this.$store.dispatch('customer/invoice/getInvoicesData',
this.search)
},

Method di atas akan dijalankan ketika kita melakukan klik pada navigasi pagination. Dan di
dalam method tersebut kita menjalankan 2 perintah.

Yang pertama, kita melakukan commit ke dalam mutation yang bernama SET_PAGE yang
berada di dalam store/customer/invoice.js dan untuk parameternya berupa nomor
pagination yang di dapatkan dari component <b-paginate>.

824
//commit to mutation "SET_PAGE"
this.$store.commit('customer/invoice/SET_PAGE', page)

Yang kedua, kita melakukan dispatch ke dalam action yang bernama getInvoicesData,
tujuannya agar data yang ditampilkan sesuai dengan nomor pagination-nya. Dan kita
tambahkan parameter this.search apabila kita melakukan navigasi di dalam hasil
pencarian.

//dispatch on action "getInvoicesData"


this.$store.dispatch('customer/invoice/getInvoicesData', this.search)

Sekarang kita bisa mencoba dengan melakukan refresh di halaman invoices atau bisa
melihatnya melalui link http://localhost:3000/customer/invoices dan jika berhasil maka kita
akan mendapatkan tampilan kurang lebih seperti berikut ini :

825
Menampilkan Detail Data Invoice Customer

Pada materi kali ini kita akan belajar bagaimana cara menampilkan detail data invoice dan
data item order berdasarkan invoice tersebut. Sebelum itu, kita akan menambahkan state,
mutation dan action terlebih dahulu di dalam Vuex. Action yang nantinya akan
digunakan untuk melakukan http request ke dalam server untuk mendapatan detail data
invoice-nya dan data tersebut akan di parsing ke dalam mutation untuk di update ke dalam
state.

Langkah 1 - Menambahkan State, Mutation dan Action di Vuex


Pertama-tama, kita akan menambahkan state, mutation dan action baru di dalam Vuex
Invoice Customer terlebih dahulu. Silahkan buka file store/customer/invoice.js dan
kemudian ubah semua kode-nya menjadi seperti berikut ini :

//state
export const state = () => ({

//invoices
invoices: [],

//page
page: 1,

//invoice
invoice: {}

})

//mutations
export const mutations = {

//mutation "SET_INVOICES_DATA"
SET_INVOICES_DATA(state, payload) {

//set value state "invoices"


state.invoices = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

826
//set value state "page"
state.page = payload
},

//mutation "SET_INVOICE_DATA"
SET_INVOICE_DATA(state, payload) {

//set value state "invoice"


state.invoice = payload
},

//actions
export const actions = {

//get invoices data


getInvoicesData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/customer/invoices" with method


"GET"
this.$axios.get(`/api/customer/invoices?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_INVOICES_DATA"


commit('SET_INVOICES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//get detail invoice


getDetailInvoice({ commit }, payload) {

//set promise

827
return new Promise((resolve, reject) => {

//get to Rest API "/api/customer/invoices/:snap_token" with


method "GET"
this.$axios.get(`/api/customer/invoices/${payload}`)

//success
.then(response => {

//commit to mutation "SET_INVOICE_DATA"


commit('SET_INVOICE_DATA', response.data.data)

//resolve promise
resolve()

})

})

},

Dari penambahan kode di atas, pertama kita menambahkan 1 state baru.

//invoice
invoice: {}

State tersebut akan digunakan untuk menyimpan detail data invoice yang di dapatkan dari
Rest API.

Kemudian di dalam mutation, kita menambahkan juga 1 method baru dengan nama
SET_INVOICE_DATA, mutation tersebut nantinya akan digunakan untuk mengubah isi dari
state invoice dengan data response yang dikirimkan oleh action.

828
//mutation "SET_INVOICE_DATA"
SET_INVOICE_DATA(state, payload) {

//set value state "invoice"


state.invoice = payload
},

Dan di dalam action, kita menambahkan 1 method baru dengan nama getDetailInvoice.

//get detail invoice


getDetailInvoice({ commit }, payload) {

//...
}

Di dalam action tersebut kita melakukan http request menggunakan Axios dengan method
GET ke dalam endpoint /api/customer/invoices/:snap_token. Untuk parameter
snap_token nanti akan dikirimkan oleh component / view.

//get to Rest API "/api/customer/invoices/:snap_token" with method "GET"


this.$axios.get(`/api/customer/invoices/${payload}`)

Jika proses fetching http request berhasil dilakukan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_INVOICE_DATA dengan parameter berupa response
data yang di dapatkan dari Rest API.

//commit to mutation "SET_INVOICE_DATA"


commit('SET_INVOICE_DATA', response.data.data)

Langkah 2 - Menambahkan Button Detail


Sekarang, kita akan lanjutkan untuk menambahkan button yang nanti digunakan untuk
melakukan navigasi ke dalam detail data invoice.

Silahkan buka file pages/customer/invoices/index.vue, kemudian cari kode berikut


ini :

829
<b-table striped bordered hover :items="invoices.data" :fields="fields"
show-empty>
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'" class="btn btn-sm
btn-primary"><i class="fa fa-circle-notch fa-spin"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'success'" class="btn btn-sm
btn-success"><i class="fa fa-check-circle"></i> {{ row.item.status
}}</button>
<button v-if="row.item.status == 'expired'" class="btn btn-sm
btn-warning-2"><i class="fa fa-exclamation-triangle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'failed'" class="btn btn-sm
btn-danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>
</b-table>

Dan ubahlah menjadi seperti berikut ini :

830
<b-table striped bordered hover :items="invoices.data" :fields="fields"
show-empty>
<template v-slot:cell(grand_total)="row">
Rp. {{ formatPrice(row.item.grand_total) }}
</template>
<template v-slot:cell(status)="row">
<button v-if="row.item.status == 'pending'" class="btn btn-sm
btn-primary"><i class="fa fa-circle-notch fa-spin"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'success'" class="btn btn-sm
btn-success"><i class="fa fa-check-circle"></i> {{ row.item.status
}}</button>
<button v-if="row.item.status == 'expired'" class="btn btn-sm
btn-warning-2"><i class="fa fa-exclamation-triangle"></i> {{
row.item.status }}</button>
<button v-if="row.item.status == 'failed'" class="btn btn-sm
btn-danger"><i class="fa fa-times-circle"></i> {{ row.item.status
}}</button>
</template>
<template v-slot:cell(actions)="row">
<b-button :to="{name: 'customer-invoices-show-snap_token',
params: {snap_token: row.item.snap_token}}" variant="info" size="sm">
DETAIL
</b-button>
</template>
</b-table>

Di atas, kita menambahkan 1 button yaitu DETAIL, dan di dalamnya kita arahkan ke dalam
route yang bernama customer-invoice-show-snap_token dan kita tambahkan
paremeter juga dengan data snap_token.

INFORMASI : Untuk sekarang kamu belum bisa melihat hasilnya, karena belum memiliki
data invoice apapun di dalam database.

831
Langkah 2 - Membuat View Detail Invoice
Sekarang kita lanjutkan untuk membuat view untuk menampilkan halaman detail data
invoice beserta item order-nya. Silahkan buat folder baru dengan nama show di dalam
folder pages/customer/invoices dan di dalam folder show silahkan buat file baru
dengan nama _snap_token.vue dan masukkan kode berikut ini di dalamnya :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> DETAIL ORDER</h5>
<hr>
<table class="table table-bordered">
<client-only>

832
<tr>
<td style="width: 25%">
NO. INVOICE
</td>
<td style="width: 1%">:</td>
<td>
{{ invoice.invoice }}
</td>
</tr>
<tr>
<td>
FULL NAME
</td>
<td>:</td>
<td>
{{ invoice.name }}
</td>
</tr>
<tr>
<td>
PHONE
</td>
<td>:</td>
<td>
{{ invoice.phone }}
</td>
</tr>
<tr>
<td>
COURIER / SERVICE / COST
</td>
<td>:</td>
<td>
{{ invoice.courier }} / {{ invoice.courier_service
}} / Rp.
{{ formatPrice(invoice.courier_cost) }}
</td>
</tr>
<tr>
<td>
CITY
</td>
<td>:</td>
<td>
{{ invoice.city.name }}
</td>

833
</tr>
<tr>
<td>
PROVINCE
</td>
<td>:</td>
<td>
{{ invoice.province.name }}
</td>
</tr>
<tr>
<td>
ADDRESS
</td>
<td>:</td>
<td>
{{ invoice.address }}
</td>
</tr>
<tr>
<td>
GRAND TOTAL
</td>
<td>:</td>
<td>
Rp. {{ formatPrice(invoice.grand_total) }}
</td>
</tr>
<tr>
<td>
STATUS
</td>
<td>:</td>
<td>
<button v-if="invoice.status == 'pending'"
class="btn btn-info">BAYAR SEKARANG</button>
<button v-else-if="invoice.status == 'success'"
class="btn btn-success"><i class="fa fa-check-
circle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'expired'"
class="btn btn-warning-2"><i class="fa fa-
exclamation-triangle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'failed'"
class="btn btn-danger"><i class="fa fa-times-
circle"></i> {{ invoice.status }}</button>
</td>

834
</tr>
</client-only>
</table>

</div>
</div>

<div class="card border-0 rounded shadow-sm border-top-orange


mt-4">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-
cart"></i> DETAIL ITEMS</span>
</div>
<div class="card-body">
<table class="table" style="border-style: solid
!important;border-color: rgb(198, 206, 214) !important;">
<tbody>
<client-only>
<tr v-for="order in invoice.orders" :key="order.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="order.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ order.product.title }}</b></h5>
<table class="table-borderless" style="font-
size: 14px">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ order.qty
}}</b></td>
</tr>
</table>
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(order.price) }}</p>
</td>
</tr>
</client-only>
</tbody>
</table>

835
</div>
</div>

</div>
</div>
</div>
</div>
</template>

<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'

export default {

//middleware
middleware: 'isCustomer',

//layout
layout: 'default',

//register components
components: {
Sidebar
},

//meta
head() {
return {
title: 'Detail Order - Customer',
}
},

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('customer/invoice/getDetailInvoice',
route.params.snap_token)
},

//computed
computed: {
invoice() {
return this.$store.state.customer.invoice.invoice
}
},

836
}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita melakukan import component sidebar,
karena kita akan menampilkan component tersebut di halaman ini.

//import sidebar
import Sidebar from '@/components/web/sidebar.vue'

Agar component dapat digunakan di dalam template, maka kita perlu melakukan register
terlebih dahulu.

//register components
components: {
Sidebar
},

Dan untuk memanggil component sidebar di dalam template, kurang lebih seperti berikut
ini :

<!-- sidebar -->


<Sidebar />
<!-- end sidebar -->

Kemudian, kita atur agar view ini menggunakan middleware isCustomer, artinya hanya
bisa diakses jika si user memiliki level customer dan sudah melakukan proses otentikasi.

//middleware
middleware: 'isCustomer',

Dan kita atur, agar view ini menggunakan layout default.

837
//layout
layout: 'default',

Setelah itu, kita juga menambahkan konfigurasi untuk meta tag title. Fungsinya untuk
mengubah title yang ada di website kita.

//meta
head() {
return {
title: 'Detail Order - Customer',
}
},

Di dalam hook asyncData kita melakukan dispatch atau memanggil ke dalam sebuah
action di Vuex dengan nama getDetailInvoice dan kita berikan parameter snap_token
yang diambil dari URL browser.

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('customer/invoice/getDetailInvoice',
route.params.snap_token)
},

Jika proses di dalam Vuex sukses dilakukan, maka kita tinggal mengambil data response-nya
di dalam state yang ada di Vuex. Dan disini kita akan menggunakan computed properti
untuk mengambilnya.

//computed
computed: {
invoice() {
return this.$store.state.customer.invoice.invoice
}
},

Di dalam computed properti di atas kita membuat method dengan nama invoice yang
mana di dalamnya memanggil sebuah state yang bernama invoice yang berada di dalam
file store/customer/invoice.js.

838
return this.$store.state.customer.invoice.invoice

Dan sekarang kita bisa memanggil detail data invoice di dalam template, kurang lebih
seperti ini contohnya.

<tr>
<td>
FULL NAME
</td>
<td>:</td>
<td>
{{ invoice.name }}
</td>
</tr>

Dan untuk data item orders dari invoice, akan ditampilkan menggunakan perulangan v-
for, karena datanya bisa lebih dari 1.

<tr v-for="order in invoice.orders" :key="order.id" style="background:


#edf2f7;">
//...
</tr>

Di atas, melakukan perulangan data yang diambil dari invoice.orders, dimana data
orders tersebut berada.

INFORMASI : Untuk sekarang kamu belum bisa melihat hasilnya, karena belum memiliki
data invoice apapun di dalam database.

839
840
Menampilkan Snap Payment Midtrans

Setelah berhasil menampilkan detail data invoice dan item order, maka sekarang kita akan
belajar untuk menampilkan popup pembayaran atau yang biasa disebut dengan SNAP
PAYMENT dari Midtrans.

Langkah 1 - Menambahkan Snap Payment


Silahkan buka file pages/customer/invoices/show/_snap_token.vue kemudian ubah
semua kode-nya menjadi seperti berikut ini :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3">
<!-- sidebar -->
<Sidebar />
<!-- end sidebar -->
</div>
<div class="col-md-9">
<div class="card border-0 rounded shadow-sm border-top-
orange">
<div class="card-body">
<h5><i class="fa fa-shopping-cart"></i> DETAIL ORDER</h5>
<hr>
<table class="table table-bordered">
<client-only>
<tr>
<td style="width: 25%">
NO. INVOICE
</td>
<td style="width: 1%">:</td>
<td>
{{ invoice.invoice }}
</td>
</tr>
<tr>
<td>
FULL NAME
</td>
<td>:</td>

841
<td>
{{ invoice.name }}
</td>
</tr>
<tr>
<td>
PHONE
</td>
<td>:</td>
<td>
{{ invoice.phone }}
</td>
</tr>
<tr>
<td>
COURIER / SERVICE / COST
</td>
<td>:</td>
<td>
{{ invoice.courier }} / {{ invoice.courier_service
}} / Rp.
{{ formatPrice(invoice.courier_cost) }}
</td>
</tr>
<tr>
<td>
CITY
</td>
<td>:</td>
<td>
{{ invoice.city.name }}
</td>
</tr>
<tr>
<td>
PROVINCE
</td>
<td>:</td>
<td>
{{ invoice.province.name }}
</td>
</tr>
<tr>
<td>
ADDRESS
</td>

842
<td>:</td>
<td>
{{ invoice.address }}
</td>
</tr>
<tr>
<td>
GRAND TOTAL
</td>
<td>:</td>
<td>
Rp. {{ formatPrice(invoice.grand_total) }}
</td>
</tr>
<tr>
<td>
STATUS
</td>
<td>:</td>
<td>
<button @click="payment(invoice.snap_token)" v-
if="invoice.status == 'pending'"
class="btn btn-info">BAYAR SEKARANG</button>
<button v-else-if="invoice.status == 'success'"
class="btn btn-success"><i class="fa fa-check-
circle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'expired'"
class="btn btn-warning-2"><i class="fa fa-
exclamation-triangle"></i> {{ invoice.status }}</button>
<button v-else-if="invoice.status == 'failed'"
class="btn btn-danger"><i class="fa fa-times-
circle"></i> {{ invoice.status }}</button>
</td>
</tr>
</client-only>
</table>

</div>
</div>

<div class="card border-0 rounded shadow-sm border-top-orange


mt-4">
<div class="card-header">
<span class="font-weight-bold"><i class="fa fa-shopping-
cart"></i> DETAIL ITEMS</span>
</div>

843
<div class="card-body">
<table class="table" style="border-style: solid
!important;border-color: rgb(198, 206, 214) !important;">
<tbody>
<client-only>
<tr v-for="order in invoice.orders" :key="order.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="order.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ order.product.title }}</b></h5>
<table class="table-borderless" style="font-
size: 14px">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ order.qty
}}</b></td>
</tr>
</table>
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(order.price) }}</p>
</td>
</tr>
</client-only>
</tbody>
</table>
</div>
</div>

</div>
</div>
</div>
</div>
</template>

<script>
//import sidebar
import Sidebar from '@/components/web/sidebar.vue'

844
export default {

//middleware
middleware: 'isCustomer',

//layout
layout: 'default',

//register components
components: {
Sidebar
},

//meta
head() {
return {
title: 'Detail Order - Customer',
}
},

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('customer/invoice/getDetailInvoice',
route.params.snap_token)
},

//computed
computed: {
invoice() {
return this.$store.state.customer.invoice.invoice
}
},

//method
methods: {

//method "payment"
payment(snap_token) {
window.snap.pay(snap_token, {

onSuccess: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}

845
})
},
onPending: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
},
onError: function () {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
}
})
},
}

}
</script>

<style>

</style>

Dari perubahan yang dilakukan, pertama kita menambahkan event @clik di dalam button
BAYAR SEKARANG, yang kita arahkan ke dalam method yang bernama payment dan di
dalamnya kita berikan parameter berupa snap_token.

<button @click="payment(invoice.snap_token)" v-if="invoice.status ==


'pending'" class="btn btn-info">BAYAR SEKARANG</button>

Setelah itu, kita membuat method baru dengan nama payment dan di dalamnya kurang
lebih seperti berikut ini :

846
//method "payment"
payment(snap_token) {
window.snap.pay(snap_token, {

onSuccess: function() {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
},
onPending: function() {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
},
onError: function() {
router.push({
name: 'invoices-show-snap_token',
params: {
snap_token: snap_token
}
})
}
})
},

Di atas, di dalam function payment, akan memanggil function snap.pay dari Midtrans,
dan di dalamnya kita berikan value snap_token.

Jika pembayaran succes, maka yang akan di eksekusi adalah onSuccess, jika pending
adalah onPending dan jika pembayaran gagal maka kode ini yang di eksekusi onError.

Di mana di dalam kode status di atas, kita arahkan ke dalam route yang bernama
invoices-show-snap_token.

847
Langkah 2 - Uji Coba Snap Payment
INFORMASI : Untuk sekarang kamu belum bisa melihat hasilnya, karena belum memiliki
data invoice apapun di dalam database.

Jika button BAYAR SEKARANG di klik, maka akan menampilkan popup pembayaran atau
SNAP PAYMENT dari Midtrans, kurang lebih seperti berikut ini :

848
Installasi dan Konfigurasi PWA

PWA adalah singkatan dari Progressive Web App, sebuah aplikasi yang dibangun dengan
melakukan optimasi pada sebuah website. Optimasi yang dilakukan tidak hanya akan
membuat website menjadi lebih cepat namun juga mampu memberikan pengalaman
layaknya menggunakan aplikasi mobile [1].

Di dalam Nuxt.js untuk membuat PWA akan sangat mudah sekali, karena sudah disediakan
library atau package secara official dengan tag line Zero Config. Artinya tanpa perlu
melakukan konfigurasi yang banyak kita sudah bisa menggunakan manfaat PWA di dalam
Nuxt.js, dibandingkan jika kita membuat PWA dari 0.

Langkah 1 - Installasi PWA


Silahkan jalankan perintah berikut ini di dalam project Nuxt.js kita untuk melakukan proses
installasi PWA.

npm i --save-dev @nuxtjs/pwa@3.3.5

Silahkan tunggu proses installasi library atau package tersebut sampai selesai dan pastikan
terhubung dengan internet.

Langkah 2 - Konfigurasi PWA


Sekarang kita akan lakukan sedikit konfigurasi saja di dalam project Nuxt.js. Silahkan buka
file nuxt.config.js, kemudian cari kode berikut ini :

buildModules: [
],

Dan ubahlah menjadi seperti berikut ini :

849
buildModules: [
'@nuxtjs/pwa', // <-- register PWA
],

pwa: {
meta: {
title: 'MI STORE - Distributor Xiaomi Indonesia Resmi',
author: 'Xiaomi Indonesia'
},
manifest: {
name: 'Xiaomi',
short_name: 'xiaomi',
description: 'Official Toko Online Penjualan Produk
Xiaomi',
lang: 'en'
},
icon: {
fileName: 'images/logo.png',
sizes: [64, 120, 144, 152, 192, 384, 512]
}
},

Dari perubahan di atas, pertama kita lakukan register library PWA di dalam config
buildModules. Dan kita juga menambahkan beberapa konfigurasi PWA untuk meta dan
kustom icon.

Jika file nuxt.config.js ditulis dengan lengkap, kurang lebih seperti berikut ini :

export default {

// Target Deployment
target: 'server',

//rendering mode SSR


ssr: true,

loading: {
color: 'white', // <-- color
height: '5px' // <-- height
},

// Global page headers: https://go.nuxtjs.dev/config-head


head: {

850
title: 'nuxt-ecommerce',
htmlAttrs: {
lang: 'en'
},
meta: [{
charset: 'utf-8'
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: ''
}
],
link: [{
rel: 'icon',
type: 'image/x-icon',
href: 'images/logo.png'
},
{
rel: 'stylesheet',
href:
'https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600
;700&display=swap'
},
{
rel: 'stylesheet',
href:
'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.
css'
},
],
script: [
{ src: '/js/coreui.bundle.min.js' },
{ src: 'https://app.sandbox.midtrans.com/snap/snap.js', 'data-
client-key': 'SB-Mid-client-bWcHM3-QSyGV2hhw' },
]
},

// Global CSS: https://go.nuxtjs.dev/config-css


css: [
'@/assets/css/style.min.css',
'@/assets/css/custom.css',

851
],

// Plugins to run before rendering page:


https://go.nuxtjs.dev/config-plugins
plugins: [
{ src: '~/plugins/vue-star-rating.js', mode: 'client' },
{ src: '~/plugins/chart.js', mode: 'client' },
{ src: '~/plugins/mixins.js' },
],

// Auto import components: https://go.nuxtjs.dev/config-components


components: true,

// Modules for dev and build (recommended):


https://go.nuxtjs.dev/config-modules
buildModules: [
'@nuxtjs/pwa', // <-- register PWA
],
pwa: {
meta: {
title: 'MI STORE - Distributor Xiaomi Indonesia Resmi',
author: 'Xiaomi Indonesia'
},
manifest: {
name: 'Xiaomi',
short_name: 'xiaomi',
description: 'Official Toko Online Penjualan Produk Xiaomi',
lang: 'en'
},
icon: {
fileName: 'images/logo.png',
sizes: [64, 120, 144, 152, 192, 384, 512]
}
},

// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
//https://dev.auth.nuxtjs.org/
'@nuxtjs/auth-next',
//https://github.com/avil13/vue-sweetalert2
'vue-sweetalert2/nuxt',
],

852
auth: {
strategies: {
//strategy "admin"
admin: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/admin/login',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/admin/logout',
method: 'post'
},
user: {
url: '/api/admin/user',
method: 'get',
propertyName: 'user'
}
},
},
//strategy "customer"
customer: {
scheme: 'local',
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
// autoFetch: true
},
endpoints: {
login: {
url: '/api/customer/login',

853
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/customer/logout',
method: 'post'
},
user: {
url: '/api/customer/user',
method: 'get',
propertyName: 'user'
}
},
},
},
},

// Axios module configuration: https://go.nuxtjs.dev/config-axios


axios: {
baseURL: 'https://ecommerce.appdev.my.id'
},

// Build Configuration: https://go.nuxtjs.dev/config-build


build: {
}
}

CATATAN : fitur ini akan aktif ketika kita melakukan deployment di production.

[1] :
https://psti.unisayogya.ac.id/2020/02/05/berkenalan-dengan-progressive-web-apps-pwa/

854
Membuat Vuex Web Category

Sebelum menampilkan data di dalam component / view tentu saja kita akan membuat sebua
module Vuex terlebih dahulu, dimana isinya adalah state, mutation dan action.
Tujuannya agar data yang digunakan bisa global dan lebih mudah dipanggil di semua
component yang membutuhkan.

Membuat Konfigurasi Vuex Web Category


Silahkan buat folder baru dengan nama web di dalam folder store. Dan di dalam folder web
tersebut silahkan buat file baru dengan nama category.js, kemudian masukkan kode
berikut ini di dalamnya.

//state
export const state = () => ({

//categories
categories: [],

})

//mutations
export const mutations = {

//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {

//set value state "categories"


state.categories = payload
},

//actions
export const actions = {

//get categories data


getCategoriesData({ commit }) {

//set promise
return new Promise((resolve, reject) => {

855
//fetching Rest API "/api/web/categories" with method "GET"
this.$axios.get('/api/web/categories')
//success
.then((response) => {

//commit ti mutation "SET_CATEGORIES_DATA"


commit('SET_CATEGORIES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Dari perubahan kode diatas, kita menambahkan 1 state baru dengan nama categories,
state tersebut akan digunakan untuk menampung data response yang di dapatkan dari Rest
API.

//categories
categories: [],

Dan kita juga menambahkan mutation baru dengan nama SET_CATEGORIES_DATA,


mutation tersebut akan digunakan untuk melakukan update isi dari state categories
dengan data yang dikirimkan dari action, yaitu berupa data response dari Rest API.

//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {

//set value state "categories"


state.categories = payload
},

Kemudian di dalam action, kita juga menambahkan 1 method baru, yaitu


getCategoriesData.

856
//get categories data
getCategoriesData({ commit }) {

//...
}

Di dalam action tersebut kita melakukan http request menggunakan Axios dengan method
GET ke dalam endpoint /api/web/categories.

//fetching Rest API "/api/web/categories" with method "GET"


this.$axios.get('/api/web/categories')

Jika proses fetching di atas berhasil, maka kita akan melakukan commit ke dalam mutation
yang bernama SET_CATEGORIES_DATA dengan parameter data response yang di dapatkan
dari Rest API.

//commit ti mutation "SET_CATEGORIES_DATA"


commit('SET_CATEGORIES_DATA', response.data.data)

857
Menampilkan Detail Data Product

Pada materi kali ini kita akan belajar bagaimana cara membuat halaman detail data product,
dimana di dalam halaman ini kita nanti akan menampilkan beberapa data, seperti gambar
product, harga, deskripsi, rating, dan lain-lain. Sebelum itu, kita akan menambahkan sebuah
state, mutation dan action terlebih dahulu di dalam module Vuex.

Langkah 1 - Menambahkan State, Mutation dan Action.

Silahkan buka file store/web/product.js, kemudian ubah semua kode yang ada di
dalamnya dengan ini :

//state
export const state = () => ({

//products
products: [],

//page
page: 1,

//product
product: {}

})

//mutations
export const mutations = {

//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {

//set value state "products"


state.products = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

858
//mutation "SET_PRODUCT_DATA"
SET_PRODUCT_DATA(state, payload) {

//set value state "product"


state.product = payload
},

//actions
export const actions = {

//get products data


getProductsData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/products" with method "GET"


this.$axios.get(`/api/web/products?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_PRODUCTS_DATA"


commit('SET_PRODUCTS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//get detail product


getDetailProduct({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//get to Rest API "/api/web/products/:slug" with method


"GET"
this.$axios.get(`/api/web/products/${payload}`)

859
//success
.then(response => {

//commit to mutation "SET_PRODUCT_DATA"


commit('SET_PRODUCT_DATA', response.data.data)

//resolve promise
resolve()

})

})

},

Dari penambahan kode di atas, pertama kita membuat 1 state baru dengan nama product,
state tersebut yang nanti akan digunakan untuk menyimpan detail data product.

//product
product: {}

Dan di dalam mutation, kita juga menambahkan 1 method baru dengan nama
SET_PRODUCT_DATA, mutation tersebut akan digunakan untuk mengubah isi dari state
product dengan data response yang dikirim oleh action.

//mutation "SET_PRODUCT_DATA"
SET_PRODUCT_DATA(state, payload) {

//set value state "product"


state.product = payload
},

Kemudian di dalam action kita membuat 1 method baru dengan nama getDetailProduct.

860
//get detail product
getDetailProduct({ commit }, payload) {

//...
}

Di dalam method tersebut, kita melakukan http request menggunakan Axios dengan
method GET ke dalam endpoint /api/web/products/:slug.

//get to Rest API "/api/web/products/:slug" with method "GET"


this.$axios.get(`/api/web/products/${payload}`)

Jika proses http request di atas berhasil dijalankan, maka kita akan melakukan commit ke
dalam mutation yang bernama SET_PRODUCT_DATA dengan parameter data response yang
di dapatkan dari Rest API.

//commit to mutation "SET_PRODUCT_DATA"


commit('SET_PRODUCT_DATA', response.data.data)

Langkah 2 - Membuat View Detail Product


Kita lanjutkan membuat component / view yang nantinya digunakan untuk menampilkan
halaman detaiil product. Silahkan buat file baru dengan nama _slug.vue di dalam folder
pages/products dan di dalam file tersebut silahkan masukkan kode berikut ini :

<template>
<div class="container mt-custom mb-5">
<div class="fade-in">
<div class="row">

<div class="col-md-4 mb-4">


<div class="card border-0 rounded shadow-sm">
<div class="card-body">
<img :src="product.image" class="w-100 rounded">
</div>
</div>
</div>

861
<div class="col-md-8">
<div class="card border-0 rounded shadow-sm">
<div class="card-body">
<h4>{{ product.title }}</h4>
<hr>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> /
<strong>{{ product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}
</h5>
<div class="mt-3">
<div v-html="product.description"></div>
</div>
<div class="table-responsive">
<table class="table table-sm table-borderless mb-0">
<tbody>
<client-only>
<tr>
<th class="pl-0 w-25"
scope="row"><strong>BERAT</strong></th>
<td><strong>{{ product.weight }}</strong>
gram</td>
</tr>
<tr>
<th class="pl-0 w-25"
scope="row"><strong>STOK</strong></th>
<td><strong>{{ product.stock }}</strong></td>
</tr>
</client-only>
</tbody>
</table>
</div>
<hr>
<button class="btn btn-lg btn-warning border-0 shadow-
sm"><i class="fa fa-shopping-cart"></i> TAMBAH KE
KERANJANG</button>
</div>
</div>
</div>

</div>

<div class="row mt-4">


<div class="col-md-12">
<div class="card border-0 rounded shadow-sm">

862
<div class="card-body">
<h5><i class="fa fa-comments"></i> ULASAN PRODUK (
<strong>{{ product.reviews_count }}</strong> ulasan )</h5>
<hr>
<div class="card bg-light shadow-sm rounded" v-for="review
in product.reviews" :key="review.id">
<div class="card-body">
<div class="row">
<div class="col-md-1">
<div class="review-avatar avatar-sm">
<img
:src="`https://ui-
avatars.com/api/?name=${review.customer.name}&amp;background=4e73df&amp;
color=ffffff&amp;size=100`">
</div>
</div>
<div class="col-md-11">
<client-only>
<vue-star-rating class="mb-2"
:rating="review.rating" :star-size="20" :read-only="true" :show-
rating="false">
</vue-star-rating>
</client-only>
<strong>
<span class="text-dark">{{ review.customer.name
}}</span>
</strong>
<div class="description mt-2">
<span style="color: rgb(119, 118, 118);font-
size:15px;font-style:italic">
{{ review.review }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>

863
export default {

//meta
head() {
return {
title: `${this.product.title} - MI STORE - Distributor Xiaomi
Indonesia Resmi`,
meta: [{
hid: 'og:title',
name: 'og:title',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:image',
name: 'og:image',
content: this.product.image
},
{
hid: 'description',
name: 'description',
content: `${this.product.title.substr(0, 30)}...`,
},
]
}
},

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('web/product/getDetailProduct',
route.params.slug)
},

//computed
computed: {

//product
product() {
return this.$store.state.web.product.product
},

864
},
}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama-tama kita atur untuk meta tag agar dinamis diambil
dari database. Dan ini sangat bagus untuk kebutuhan SEO di dalam website.

//meta
head() {
return {
title: `${this.product.title} - MI STORE - Distributor Xiaomi
Indonesia Resmi`,
meta: [{
hid: 'og:title',
name: 'og:title',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:image',
name: 'og:image',
content: this.product.image
},
{
hid: 'description',
name: 'description',
content: `${this.product.title.substr(0, 30)}...`,
},
]
}
},

865
Setelah itu, kita melakukan dispatch ke dalam action yang bernama getDetailProduct
dan kita tambahkan parameter berupa slug yang diambil dari URL browser. Dan proses
dispatch tersebut dilakukan di dalam hook asyncData, agar proses tersebut dijalankan
secara SSR atau server side rendering.

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('web/product/getDetailProduct',
route.params.slug)
},

Jika dari proses dispatch di atas berhasil, sekarang kita akan ambil data yang ada di dalam
state product di Vuex menggunakan computed properti.

//computed
computed: {

//product
product() {
return this.$store.state.web.product.product
},
},

Di dalam computed properti di atas, kita membuat method dengan nama product yang
mana di dalamnya akan melakukan pengambilan data dari state product yang ada di
dalam Vuex.

Dan untuk menampilkan data product di template, kurang lebih contohnya seperti berikut ini
:

<img :src="product.image" class="w-100 rounded">

Kode di atas, digunakan untuk menampilkan gambar product. Dan untuk menampilkan data
rating dan ulasan, maka kita perlu menggunakan directive v-for, karena data yang
ditampilkan akan lebih dari 1.

866
<div class="card bg-light shadow-sm rounded" v-for="review in
product.reviews" :key="review.id">

//...
</div>

Langkah 3 - Uji Coba Detail Product


Silahkan klik salah satu data product yang kamu miliki, dan jika berhasil maka kita akan
mendapatkan hasil kurang lebih seperti berikut ini :

867
Membuat Fitur Pencarian Product

Setelah berhasil menampilkan data products dan detail datanya, maka sekarang kita akan
belajar bagaimana cara membuat proses pencarian di dalam website ini. Disini kita tidak
perlu membuat sebuah Vuex lagi, karena kita akan manfaatkan dari module Vuex product.

Langkah 1 - Menambahkan Fungsi Pencarian di Header


Pertama-tama kita akan lakukan konfigurasi di dalam header / navbar untuk menambahkan
fungsi pencarian, dimana fungsi tersebut nantinya akan mengarahkan kita ke dalam sebuah
halaman pencarian.

Silahkan buka file components/web/header.vue, kemudian ubah semua kode yang ada
di dalamnya menjadi seperti berikut ini :

<template>
<header class="section-header fixed-top">
<section class="header-main border-bottom">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-lg-3 col-sm-4 col-md-4 col-5">
<nuxt-link to="/" class="brand-wrap" data-abc="true">
<img src="/images/xiaomi.png" width="35" class="bg-light
p-2 rounded">
<span class="logo">MI STORE</span>
</nuxt-link>
</div>
<div class="col-lg-4 col-xl-5 col-sm-8 col-md-4 d-none d-md-
block">
<div class="search-wrap">
<div class="input-group w-100">
<input type="text" class="form-control search-form" v-
model="search" @keypress.enter="searchData" style="width:55%;"
placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button @click="searchData" class="btn btn-primary
search-button"> <i class="fa fa-search"></i> </button>
</div>
</div>
</div>
</div>
<div class="col-lg-5 col-xl-4 col-sm-8 col-md-4 col-7">

868
<div class="d-flex justify-content-end">
<a href="#" class="btn search-button btn-md d-md-block
ml-4"><i class="fa fa-shopping-cart"></i> <span class="ml-2">0</span> |
Rp. 0</a>
</div>
</div>
</div>
</div>
</section>
<nav class="navbar navbar-expand-md navbar-main border-bottom p-2">
<div class="container-fluid">
<div class="d-md-none my-2">
<div class="input-group">
<input type="search" name="search" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="mau belanja apa
hari ini ?">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"> <i
class="fa fa-search"></i></button>
</div>
</div>
</div>
<button class="navbar-toggler collapsed" type="button" data-
toggle="collapse" data-target="#dropdown6"
aria-expanded="false"> <span class="navbar-toggler-
icon"></span> </button>
<div class="navbar-collapse collapse" id="dropdown6">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown"> <a class="nav-link dropdown-
toggle" href="#" data-toggle="dropdown"
data-abc="true" aria-expanded="false"><i class="fa fa-
list-ul"></i> KATEGORI</a>
<div class="dropdown-menu">
<nuxt-link :to="{name: 'categories-slug', params: {slug:
category.slug}}" class="dropdown-item" v-for="category in categories"
:key="category.id">
<img :src="category.image" width="50"> {{
category.name }}
</nuxt-link>
<div class="dropdown-divider"></div>
<nuxt-link :to="{name: 'categories'}" class="dropdown-
item active text-center" href="" data-abc="true">
LIHAT SEMUA KATEGORI <i class="fa fa-long-arrow-alt-
right"></i>
</nuxt-link>
</div>

869
</li>
<li class="nav-item"> <nuxt-link :to="{name: 'products'}"
class="nav-link" data-abc="true"><i class="fa fa-shopping-bag"></i>
SEMUA PRODUK</nuxt-link> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-info-circle"></i> TENTANG</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-comments"></i> KONTAK</a> </li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown" v-if="!$auth.loggedIn">
<nuxt-link :to="{name: 'customer-login'}" class="nav-link"
href="#" role="button" aria-expanded="false"> <i class="fa fa-user-
circle"></i>
ACCOUNT</nuxt-link>
</li>
<li class="nav-item dropdown" v-if="$auth.loggedIn">
<nuxt-link :to="{name: 'customer-dashboard'}" class="nav-
link" href="#" role="button" aria-expanded="false"> <i class="fa fa-
tachometer-alt"></i>
DASHBOARD</nuxt-link>
</li>
</ul>
</div>
</div>
</nav>
</header>
</template>

<script>
export default {

//hook "fetch"
async fetch() {

//fething sliders on Rest API


await this.$store.dispatch('web/category/getCategoriesData')
},

//computed
computed: {
//categories
categories() {
return this.$store.state.web.category.categories
},
},

870
//data function
data() {
return {

//state search
search: ''
}
},

//method
methods: {
searchData() {
this.$router.push({
name: 'search',
query: {
q: this.search
}
});
}
}

}
</script>

<style scoped>
.btn {
font-size: initial;
}
</style>

Dari penambahan kode di atas, pertama kita menambahkan state di dalam data function,
yang bertujuan untuk menampung input form pencarian.

//state search
search: ''

Setelah itu, di dalam template kita melakukan sedikit perubahan di dalam form pencarian,
kurang lebih seperti berikut ini :

871
<input type="search" name="search" class="form-control" v-model="search"
@keypress.enter="searchData" placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"> <i class="fa
fa-search"></i></button>
</div>

Di atas pada bagian input kita menambahkan event @keypress.enter dimana di


dalamnya akan memanggil method yang bernama searchData, di dalam button juga sama
kita menambahkan event @click yang kita arahkan ke dalam method searcData.

Setelah itu, kita membuat method searchData, kurang lebih seperti berikut ini :

searchData() {
this.$router.push({
name: 'search',
query: {
q: this.search
}
});
}

Di dalam method searchData di atas, kita akan arahkan ke dalam sebuah route yang
bernama search dan kita tambahkan query / parameter di URL dengan nama q dan isinya
adalah data yang di ketik di dalam form pencarian.

Langkah 2 - Membuat Halaman Pencarian


Sekarang kita akan buat halaman untuk pencarian, dimana di dalam halaman ini kita akan
menangkap data yang dikirim dari URL berupa parameter q dan isinya akan kita buat
sebagai paramerer pencarina ke Rest API. Dan jika data didapatkan, maka akan ditampilkan
di halaman ini.

Silahkan buat folder baru dengan nama search di dalam folder pages. Dan di dalam folder
search tersebut silahkan buat file baru dengan nama index.vue, kemudian masukkan
kode berikut ini di dalamnya.

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">

872
<div class="row">
<div class="col-md-12">
<h3>PENCARIAN DENGAN KATA KUNCI : <strong>{{
$route.query.q }}</strong></h3>
<!-- Solid divider -->
<hr class="solid">
</div>
</div>

<div class="row" v-if="products.data.length > 0">


<div class="col-md-3 mt-1 mb-4" v-for="product in products.data"
:key="product.id">
<div class="card h-100 border-0 rounded shadow-sm">
<div class="card-body">
<div class="card-img-actions">
<img :src="product.image" class="card-img img-fluid">
</div>
</div>
<div class="card-body bg-light-custom text-center rounded-
bottom">
<div class="mb-2">
<h6 class="font-weight-semibold mb-2">
<nuxt-link :to="{name: 'products-slug', params: {slug:
product.slug}}" class="text-default mb-2"
data-abc="true">{{ product.title }}</nuxt-link>
</h6>
<nuxt-link :to="{name: 'categories-slug', params: {slug:
product.category.slug}}" class="text-muted"
data-abc="true">{{ product.category.name }}</nuxt-
link>
</div>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> /
<strong>{{ product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}
</h5>
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>

873
</div>
</div>
</div>

<div class="row justify-content-center" v-else>


<div class="col-md-12">
<b-alert show variant="danger">DATA PRODUK TIDAK
DITEMUKAN!</b-alert>
</div>
</div>

</div>
</div>
</template>

<script>
export default {

//meta
head() {
return {
title: `Pencarian untuk : ${this.$route.query.q} - MI STORE -
Distributor Xiaomi Indonesia Resmi`,
}
},

//watch query URL


watchQuery: ["q"],

//hook "asyncData"
async asyncData({ store, query }) {
await store.dispatch('web/product/getProductsData', query.q)
},

//computed
computed: {

//products
products() {
return this.$store.state.web.product.products
},
},
}
</script>

<style>

874
</style>

Di atas, pertama kita atur meta title untuk component / view ini. Disini kita menampilkan
keyword pencarian di dalam title website.

//meta
head() {
return {
title: `Pencarian untuk : ${this.$route.query.q} - MI STORE -
Distributor Xiaomi Indonesia Resmi`,
}
},

Dan di dalam hook asyncData, kita melakukan dispatch ke dalam action yang bernama
getProductsData dengan mengirimkan parameter data yang diambil dari query yang
bernama q di URL.

//hook "asyncData"
async asyncData({ store, query }) {
await store.dispatch('web/product/getProductsData', query.q)
},

Dan setelah itu, kita akan ambil datanya menggunakan computed properti.

//computed
computed: {

//products
products() {
return this.$store.state.web.product.products
},
},

Di dalam template, kita membuat kondisi untuk menampilkan hasil pencarian data, jika data
di dalam computed properti lebih dari 0, artinya datanya ada, maka kita akan menampilkan
data products menggunakan directive v-for. Tapi, jika data-nya sama dengan atau kurang
dari 0, maka akan menampilkan pesan DATA PRODUK TIDAK DITEMUKAN!.

875
<div class="row" v-if="products.data.length > 0">

//tampilkan data products


</div>

<div v-else>
//DATA PRODUK TIDAK DITEMUKAN!
</div>

Langkah 3 - Uji Coba Proses Pencarian


Silahkan lakukan uji coba pencarian dengan cara memasukkan kata kunci di dalam form,
kemudian tekan ENTER atau klik button SEARCH. Jika berhasil maka kita akan mendapatkan
hasil seperti berikut ini :

876
Langkah 4 - Menambahkan Fitur Pagination
Bisa jadi di dalam proses pencarian mendapatkan result data yang banyak dan tentu tidak
boleh ditampilkan semuanya sekaligus, karena akan meberatkan website-nya. Dengan
menggunakan pagination, maka data yang ditampilkan akan di batasi perhalaman.

Silahkan buka file pages/search/index.vue, kemudian ubah semua kode yang ada
menjadi seperti berikut ini :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">

<div class="row">
<div class="col-md-12">
<h3>PENCARIAN DENGAN KATA KUNCI : <strong>{{
$route.query.q }}</strong></h3>
<!-- Solid divider -->
<hr class="solid">
</div>
</div>

<div class="row" v-if="products.data.length > 0">


<div class="col-md-3 mt-1 mb-4" v-for="product in products.data"
:key="product.id">
<div class="card h-100 border-0 rounded shadow-sm">
<div class="card-body">
<div class="card-img-actions">
<img :src="product.image" class="card-img img-fluid">
</div>
</div>
<div class="card-body bg-light-custom text-center rounded-
bottom">
<div class="mb-2">
<h6 class="font-weight-semibold mb-2">
<nuxt-link :to="{name: 'products-slug', params: {slug:
product.slug}}" class="text-default mb-2"
data-abc="true">{{ product.title }}</nuxt-link>
</h6>
<nuxt-link :to="{name: 'categories-slug', params: {slug:
product.category.slug}}" class="text-muted"
data-abc="true">{{ product.category.name }}</nuxt-
link>
</div>

877
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> /
<strong>{{ product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}
</h5>
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>
</div>
</div>
</div>

<div class="row justify-content-center" v-else>


<div class="col-md-12">
<b-alert show variant="danger">DATA PRODUK TIDAK
DITEMUKAN!</b-alert>
</div>
</div>

<!--pagination -->
<div class="row justify-content-center mt-4 mb-4">
<div class="text-center">
<b-pagination align="center" :value="products.current_page"
:total-rows="products.total"
:per-page="products.per_page" @change="changePage" aria-
controls="my-table"></b-pagination>
</div>
</div>

</div>
</div>
</template>

<script>
export default {

//meta
head() {
return {

878
title: `Pencarian untuk : ${this.$route.query.q} - MI STORE -
Distributor Xiaomi Indonesia Resmi`,
}
},

//watch query URL


watchQuery: ["q"],

//hook "asyncData"
async asyncData({ store, query }) {
await store.dispatch('web/product/getProductsData', query.q)
},

//computed
computed: {

//products
products() {
return this.$store.state.web.product.products
},
},

//method
methods: {

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('web/product/SET_PAGE', page)

//dispatch on action "getProductsData"


this.$store.dispatch('web/product/getProductsData',
this.$route.query.q)
},
}
}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita menambahkan component baru dari Boostrap
Vue, yaitu <b-paginate>. Dan di dalamnya kita berikan data yang diambil dari computed

879
properti yang berisi data pagination.

Ketika navigasi pagination di klik, maka akan mencalankan event @change yang mengarah
ke dalam method yang bernama changePage.

<b-pagination align="center" :value="products.current_page" :total-


rows="products.total" :per-page="products.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

Dan di dalam method changePage, kurang lebih seperti berikut ini :

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('web/product/SET_PAGE', page)

//dispatch on action "getProductsData"


this.$store.dispatch('web/product/getProductsData',
this.$route.query.q)
},

Di dalam method di atas, pertama kita melakukan commit ke dalam mutation yang bernama
SET_PAGE yang berada di dalam store/web/product.js dan untuk parameter-nya akan
berupa data angka pagination yang dikirim dari component <b-paginate>.

Dan yang kedua akan menjalankan dispatch ke dalam action yang bernama
getProductsData, dengan tujuan agar melakukan fetching ulang dengan data yang
sesuai dengan pagination dan query URL.

Sekarang, silahkan reload halaman pencarian products, jika berhasil maka kita akan
mendapatkan hasil kurang lebih seperti berikut ini :

880
881
Membuat Vuex Web Cart

Di materi kali ini kita semua akan belajar membuat konfigurasi Vuex untuk cart. Dimana
disini kita akan menambahkan beberapa state, mutations dan action untuk kebutuhan
cart. Dengan menggunakan Vuex, maka data cart yang kita buat akan bersifat global dan
mempermudah kita dalam pemanggilan data tersebut di semua component / view yang
membutuhkan.

Membuat Konfigurasi Vuex Web Cart


Silahkan file baru dengan nama cart.js di dalam folder store/web, kemudian masukkan
kode berikut ini di dalamnya.

//state
export const state = () => ({

//carts
carts: [],

//cartPrice
cartPrice: 0,

//cartWeight
cartWeight: 0

})

//mutations
export const mutations = {

//mutation "SET_CARTS_DATA"
SET_CARTS_DATA(state, payload) {

//set value state "carts"


state.carts = payload
},

//mutation "SET_CART_PRICE"
SET_CART_PRICE(state, payload) {

//set value state "cartPrice"


state.cartPrice = payload

882
},

//mutation "SET_CART_WEIGHT"
SET_CART_WEIGHT(state, payload) {

//set value state "cartWeight"


state.cartWeight = payload
},

//actions
export const actions = {

//get carts data


getCartsData({ dispatch, commit }) {

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/carts" with method "GET"


this.$axios.get('/api/web/carts')
//success
.then((response) => {

//commit ti mutation "SET_CARTS_DATA"


commit('SET_CARTS_DATA', response.data.data)

//dispatch "getCartPrice"
dispatch('getCartPrice')

//dispatch "getCartWeight"
dispatch('getCartWeight')

//resolve promise
resolve()
})

})

},

//get cart price


getCartPrice({ commit }) {

//set promise

883
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/carts/total_price" with method


"GET"
this.$axios.get('/api/web/carts/total_price')
//success
.then((response) => {

//commit ti mutation "SET_CART_PRICE"


commit('SET_CART_PRICE', response.data.data)

//resolve promise
resolve()
})

})

},

//get cart weight


getCartWeight({ commit }) {

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/carts/total_weight" with


method "GET"
this.$axios.get('/api/web/carts/total_weight')
//success
.then((response) => {

//commit ti mutation "SET_CART_WEIGHT"


commit('SET_CART_WEIGHT', response.data.data)

//resolve promise
resolve()
})

})

},

//store cart
storeCart({ dispatch }, payload) {

//set promise

884
return new Promise((resolve, reject) => {

//store to Rest API "/api/web/carts" with method "POST"


this.$axios.post('/api/web/carts', payload)

//success
.then(() => {

//dispatch action "getCartsData"


dispatch('getCartsData')

//resolve promise
resolve()

})

//error
.catch(error => {
reject(error)
})

})
},

//remove cart
removeCart({ dispatch }, payload) {

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/carts/remove" with method


"GET"
this.$axios.post('/api/web/carts/remove', payload)
//success
.then(() => {

//dispatch "getCartsData"
dispatch('getCartsData')

//resolve promise
resolve()
})

})

},

885
}

Dari penambahan kode di atas, pertama kita membuat 3 state baru, yaitu carts,
cartPrice dan cartWeight.

//carts
carts: [],

//cartPrice
cartPrice: 0,

//cartWeight
cartWeight: 0

State carts akan digunakan untuk menyimpan data carts berdasarkan customer yang
sedang login. Dan untuk state cartPrice digunakan untuk menyimpan total harga yang
ada di dalam cart tersebut. Sedangkan state cartWeight digunakan untuk menyimpan
total berat dari semua product yang ada di dalam cart tersebut.

kemudian di dalam mutations, kita membuat 3 method baru, yaitu SET_CARTS_DATA,


SET_CART_PRICE dan SET_CART_WEIGHT.

SET_CARTS_DATA

Mutation ini akan digunakan untuk mengubah isi dari state carts dengan data yang
dikirimkan oleh action, dimana data tersebut nantinya akan berupa data carts yang di
dapatkan dari Rest API berupa response JSON.

//mutation "SET_CARTS_DATA"
SET_CARTS_DATA(state, payload) {

//set value state "carts"


state.carts = payload
},

SET_CART_PRICE

Mutation ini akan digunakan untuk mengubah isi dari state cartPrice dan datanya akan
didapatkaan dari action berupa response JSON dari Rest API.

886
//mutation "SET_CART_PRICE"
SET_CART_PRICE(state, payload) {

//set value state "cartPrice"


state.cartPrice = payload
},

SET_CART_WEIGHT

Sama seperti di atas, mutation ini akan digunakan untuk mengubah isi dari state
cartWeight dengan data yang dikirimkan oleh action.

//mutation "SET_CART_WEIGHT"
SET_CART_WEIGHT(state, payload) {

//set value state "cartWeight"


state.cartWeight = payload
},

Dan di dalam actions kita menambahkan 5 method baru, yaitu :

getCartsData
getCartPrice
getCartWeight
storeCart
removeCart

getCartsData

Action ini digunakan untuk mendapatkan data carts dari database sesuai dengan customer
yang sedang login. Di dalam action ini kita melakukan http request menggunakan Axios
dengan method GET ke dalam endpoint /api/web/carts.

//fetching Rest API "/api/web/carts" with method "GET"


this.$axios.get('/api/web/carts')

Jika dari proses fetching di atas berhasil, maka kita akan menjalankan beberapa kode lagi,
pertama kita akan melakukan commit ke dalam mutation yang bernama SET_CARTS_DATA
dengan mengirimkan parameter response data.

887
//commit ti mutation "SET_CARTS_DATA"
commit('SET_CARTS_DATA', response.data.data)

Setelah itu, kita akan menjalankan dispatch ke dalam 2 action yang nanti akan kita bahas,
yaitu getCartPrice dan getCartWeight.

//dispatch "getCartPrice"
dispatch('getCartPrice')

//dispatch "getCartWeight"
dispatch('getCartWeight')

Tujuannya menjalankan 2 action tersebut agar kita bisa mengambil data total price dan
weight secara bersamaan.

getCartPrice

Action ini akan digunakan untuk mendapatkan data total price yang ada di dalam cart
sesuai dengan customer yang sedang login. Disini kita melakukan http request
menggunakan Axios dengan method GET ke dalam endpoint
/api/web/cart/total_price.

//fetching Rest API "/api/web/carts/total_price" with method "GET"


this.$axios.get('/api/web/carts/total_price')

Jika proses fetching di atas berhasil, maka kita akan melakukan commit ke dalam mutation
yang bernama SET_CART_PRICE dan kita kirimkan parameter berupa data response yang
didapatkan dari Rest API.

//commit ti mutation "SET_CART_PRICE"


commit('SET_CART_PRICE', response.data.data)

getCartWeight

Sama seperti dia tas, bedanya action ini digunakan untuk mengambil total berat dari semua
product yang ada di dalam carts. Disini kita melakukan http request menggunakan Axios
dengan method GET ke dalam endpoint /api/web/cart/total_weight.

888
//fetching Rest API "/api/web/carts/total_weight" with method "GET"
this.$axios.get('/api/web/carts/total_weight')

Jika proses fetching di atas berhasil dilakukan, maka kita akan melakukan commit ke dalam
mutation yang bernama SET_CART_WEIGHT dengan menambahkan parameter data
repsonse dari Rest API.

//commit ti mutation "SET_CART_WEIGHT"


commit('SET_CART_WEIGHT', response.data.data)

storeCart

Action ini digunakan untuk proses insert data cart ke dalam database atau biasa disebut
dengan add to cart. Dan disini kita melakukan sending data menggunakan Axios dengan
method POST ke dalam endpoint /api/web/cart dengan mengirimkan parameter
payload. Parameter tersebut nanti akan berisi beberapa data yang dikirimkan oleh
component / view, seperti product_id, price, qty dan lain-lain.

//store to Rest API "/api/web/carts" with method "POST"


this.$axios.post('/api/web/carts', payload)

Jika proses sending data di atas berhasil dilakukan, maka kita akan melakukan proses
dispatch ke dalam action yang bernama getCartsData, dengan tujuan agar dilakukan
fetching ulang untuk mendapatkan data carts yang terbaru.

//dispatch action "getCartsData"


dispatch('getCartsData')

removeCart

Terakhir adalah action yang digunakan untuk menghapus data cart. Disini kita melakukan
http request menggunakan Axios dengan method POST ke dalam endpoint
/api/web/cart/remove dan kita juga mengirimkan parameter payload, dimana
parameter tersebut akan berisi cart_id yang dikirimkan oleh component / view.

889
//fetching Rest API "/api/web/carts/remove" with method "GET"
this.$axios.post('/api/web/carts/remove', payload)

Jika proses hapus data cart berhasil dilakukan, maka kita akan melakukan dispatch ke dalam
action yang bernama getCartsData, agar data carts diperbarui setelah adanya proses
hapus data.

//dispatch "getCartsData"
dispatch('getCartsData')

890
Membuat Proses Add To Cart.md

Setelah berhasil menambahkan Vuex untuk cart, sekarang kita akan lanjutkan belajat
membuat fitur add to cart atau melakukan proses insert data cart ke dalam database sesuai
dengan customer yang sedang login.

Langkah 1 - Menambahkan Proses Add To Cart


Disini kita akan melakukan modifikasi di halaman detail product, karena fungsinya akan
dibuat di halaman tersebut. Silahkan buka file pages/products/_slug.vue, kemudian
ubah semua kode-nya menjadi seperti berikut ini :

<template>
<div class="container mt-custom mb-5">
<div class="fade-in">
<div class="row">

<div class="col-md-4 mb-4">


<div class="card border-0 rounded shadow-sm">
<div class="card-body">
<img :src="product.image" class="w-100 rounded">
</div>
</div>
</div>

<div class="col-md-8">
<div class="card border-0 rounded shadow-sm">
<div class="card-body">
<h4>{{ product.title }}</h4>
<hr>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> /
<strong>{{ product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}
</h5>
<div class="mt-3">
<div v-html="product.description"></div>
</div>
<div class="table-responsive">
<table class="table table-sm table-borderless mb-0">
<tbody>

891
<client-only>
<tr>
<th class="pl-0 w-25"
scope="row"><strong>BERAT</strong></th>
<td><strong>{{ product.weight }}</strong>
gram</td>
</tr>
<tr>
<th class="pl-0 w-25"
scope="row"><strong>STOK</strong></th>
<td><strong>{{ product.stock }}</strong></td>
</tr>
</client-only>
</tbody>
</table>
</div>
<hr>
<button @click="addToCart(product.id,
calculateDiscount(product), product.weight)" class="btn btn-lg btn-
warning border-0 shadow-sm"><i class="fa fa-shopping-cart"></i> TAMBAH
KE
KERANJANG</button>
</div>
</div>
</div>

</div>

<div class="row mt-4">


<div class="col-md-12">
<div class="card border-0 rounded shadow-sm">
<div class="card-body">
<h5><i class="fa fa-comments"></i> ULASAN PRODUK (
<strong>{{ product.reviews_count }}</strong> ulasan )</h5>
<hr>
<div class="card bg-light shadow-sm rounded" v-for="review
in product.reviews" :key="review.id">
<div class="card-body">
<div class="row">
<div class="col-md-1">
<div class="review-avatar avatar-sm">
<img
:src="`https://ui-
avatars.com/api/?name=${review.customer.name}&amp;background=4e73df&amp;
color=ffffff&amp;size=100`">
</div>

892
</div>
<div class="col-md-11">
<client-only>
<vue-star-rating class="mb-2"
:rating="review.rating" :star-size="20" :read-only="true" :show-
rating="false">
</vue-star-rating>
</client-only>
<strong>
<span class="text-dark">{{ review.customer.name
}}</span>
</strong>
<div class="description mt-2">
<span style="color: rgb(119, 118, 118);font-
size:15px;font-style:italic">
{{ review.review }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
export default {

//meta
head() {
return {
title: `${this.product.title} - MI STORE - Distributor Xiaomi
Indonesia Resmi`,
meta: [{
hid: 'og:title',
name: 'og:title',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:site_name',

893
name: 'og:site_name',
content: `${this.product.title} - MI STORE - Distributor
Xiaomi Indonesia Resmi`,
},
{
hid: 'og:image',
name: 'og:image',
content: this.product.image
},
{
hid: 'description',
name: 'description',
content: `${this.product.title.substr(0, 30)}...`,
},
]
}
},

//hook "asyncData"
async asyncData({ store, route }) {
await store.dispatch('web/product/getDetailProduct',
route.params.slug)
},

//computed
computed: {

//product
product() {
return this.$store.state.web.product.product
},
},

//method
methods: {

//method "addToCart"
async addToCart(productId, price, weight) {

//check loggedIn "false"


if (!this.$auth.loggedIn) {

//redirect
return this.$router.push({
name: 'customer-login'
})

894
}

//check customer role


if (this.$auth.strategy.name != "customer") {

//redirect
return this.$router.push({
name: 'customer-login'
})
}

//dispatch to action "storeCart" vuex


await this.$store.dispatch('web/cart/storeCart', {
product_id: productId,
price: price,
qty: 1,
weight: weight
})

//success add to cart


.then(() => {

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Ditambahkan di Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 3000
})

})

}
}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita manmabhkan event @click di dalam button
TAMBAH KE KERANJANG, dimana event tersebut kita arahkan ke dalam method yang

895
bernama addToCart.

<button @click="addToCart(product.id, calculateDiscount(product),


product.weight)" class="btn btn-lg btn-warning border-0 shadow-sm"><i
class="fa fa-shopping-cart"></i> TAMBAH KE KERANJANG</button>

Di dalam pemanggilan method tersebut, kita melakukan parsing 3 paramater, yaitu


product_id price dan weight.

Dan sekarang, di dalam method addToCart, pertama-tama kita melakukan pengecekan


terlebih dahulu, apakah customer sudah melakukan proses otentikasi atau belum, jika
belum maka akan di arahkan ke dalam route login customer.

//check loggedIn "false"


if (!this.$auth.loggedIn) {

//redirect
return this.$router.push({
name: 'customer-login'
})
}

Yang kedua, pengecekan apakah user yang login tersebut itu customer atau bukan, jika
buka customer maka akan diarahkan juga ke dalam route login customer.

//check customer role


if (this.$auth.strategy.name != "customer") {

//redirect
return this.$router.push({
name: 'customer-login'
})
}

Jika kedua kondisi di atas sudah terpenuhi, sekarang kita bisa melakukan proses add to cart.
Disini kita melakukan dispatch ke dalam action yang bernama storeCart dengan
mengirimkan beberapa parameter.

896
//dispatch to action "storeCart" vuex
await this.$store.dispatch('web/cart/storeCart', {
product_id: productId,
price: price,
qty: 1,
weight: weight
})

Parameter yang dikirim yaitu product_id, price, qty dan weight.

Jika proses add to cart yang ada di dalam action Vuex berhasil dilakukan, maka kita akan
menampilkan alert sukses menggunakan Sweet Alert2.

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Ditambahkan di Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 3000
})

Langkah 2 - Uji Coba Proses Add To Cart

Sekarang silahkan buka salah satu product dan klik button TAMBAH KE KERANJANG. Jika
kita belum melakukan otentikasi, maka akan diarahkan ke halaman login. Tapi jika sudah
melakukan otentikasi dan klik button tersebut, maka kita akan mendapatkan hasil seperti
berikut ini :

897
Dan kita bisa memastikan apakah data cart tersebut sudah berhasil dimasukkan, kita bisa
cek di dalam database dan di datable carts. Kurang lebih seperti berikut ini :

898
Menampilkan Data Cart di Header

Jika kita perhatikan di materi sebelumnya, saat kita berhasil melakukan proses add to cart,
di dalam header / navbar kita belum menampilkan data cart tersebut secara realtime /
reactive. Yups, karena memang kita belum menambahkan kode untuk menampilkan data
cart tersebut.

Langkah 1 - Menampilkan Cart di Header


Silahkan buka file components/web/header.vue, kemudian ubah semua kode-nya
menjadi seperti berikut ini :

<template>
<header class="section-header fixed-top">
<section class="header-main border-bottom">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-lg-3 col-sm-4 col-md-4 col-5">
<nuxt-link to="/" class="brand-wrap" data-abc="true">
<img src="/images/xiaomi.png" width="35" class="bg-light
p-2 rounded">
<span class="logo">MI STORE</span>
</nuxt-link>
</div>
<div class="col-lg-4 col-xl-5 col-sm-8 col-md-4 d-none d-md-
block">
<div class="search-wrap">
<div class="input-group w-100">
<input type="text" class="form-control search-form" v-
model="search" @keypress.enter="searchData" style="width:55%;"
placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button @click="searchData" class="btn btn-primary
search-button"> <i class="fa fa-search"></i> </button>
</div>
</div>
</div>
</div>
<div class="col-lg-5 col-xl-4 col-sm-8 col-md-4 col-7">
<div class="d-flex justify-content-end">
<nuxt-link :to="{name: 'cart'}" class="btn search-button
btn-md d-md-block ml-4"><i class="fa fa-shopping-cart"></i> <span

899
class="ml-2">{{ cartTotal }}</span> | Rp. {{ formatPrice(cartPrice)
}}</nuxt-link>
</div>
</div>
</div>
</div>
</section>
<nav class="navbar navbar-expand-md navbar-main border-bottom p-2">
<div class="container-fluid">
<div class="d-md-none my-2">
<div class="input-group">
<input type="search" name="search" class="form-control" v-
model="search" @keypress.enter="searchData" placeholder="mau belanja apa
hari ini ?">
<div class="input-group-append">
<button @click="searchData" class="btn btn-warning"> <i
class="fa fa-search"></i></button>
</div>
</div>
</div>
<button class="navbar-toggler collapsed" type="button" data-
toggle="collapse" data-target="#dropdown6"
aria-expanded="false"> <span class="navbar-toggler-
icon"></span> </button>
<div class="navbar-collapse collapse" id="dropdown6">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown"> <a class="nav-link dropdown-
toggle" href="#" data-toggle="dropdown"
data-abc="true" aria-expanded="false"><i class="fa fa-
list-ul"></i> KATEGORI</a>
<div class="dropdown-menu">
<nuxt-link :to="{name: 'categories-slug', params: {slug:
category.slug}}" class="dropdown-item" v-for="category in categories"
:key="category.id">
<img :src="category.image" width="50"> {{
category.name }}
</nuxt-link>
<div class="dropdown-divider"></div>
<nuxt-link :to="{name: 'categories'}" class="dropdown-
item active text-center" href="" data-abc="true">
LIHAT SEMUA KATEGORI <i class="fa fa-long-arrow-alt-
right"></i>
</nuxt-link>
</div>
</li>
<li class="nav-item"> <nuxt-link :to="{name: 'products'}"

900
class="nav-link" data-abc="true"><i class="fa fa-shopping-bag"></i>
SEMUA PRODUK</nuxt-link> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-info-circle"></i> TENTANG</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-comments"></i> KONTAK</a> </li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown" v-if="!$auth.loggedIn">
<nuxt-link :to="{name: 'customer-login'}" class="nav-link"
href="#" role="button" aria-expanded="false"> <i class="fa fa-user-
circle"></i>
ACCOUNT</nuxt-link>
</li>
<li class="nav-item dropdown" v-if="$auth.loggedIn">
<nuxt-link :to="{name: 'customer-dashboard'}" class="nav-
link" href="#" role="button" aria-expanded="false"> <i class="fa fa-
tachometer-alt"></i>
DASHBOARD</nuxt-link>
</li>
</ul>
</div>
</div>
</nav>
</header>
</template>

<script>
export default {

//hook "fetch"
async fetch() {

//fething sliders on Rest API


await this.$store.dispatch('web/category/getCategoriesData')

if(this.$auth.loggedIn && this.$auth.strategy.name == 'customer')


{

//fething carts on Rest API


await this.$store.dispatch('web/cart/getCartsData')
await this.$store.dispatch('web/cart/getCartPrice')

}
},

901
//computed
computed: {
//categories
categories() {
return this.$store.state.web.category.categories
},

//cartPrice
cartPrice() {
return this.$store.state.web.cart.cartPrice
},

//cartTotal
cartTotal() {
return this.$store.state.web.cart.carts.length
},
},

//data function
data() {
return {

//state search
search: ''
}
},

//method
methods: {
searchData() {
this.$router.push({
name: 'search',
query: {
q: this.search
}
});
}
}

}
</script>

<style scoped>
.btn {
font-size: initial;
}

902
</style>

Dari penambahan kode di atas, pertama di dalam hook fetch kita melakukan dispatch ke
dalam 2 action Vuex, yaitu getCartsData dan getCartPrice.

if (this.$auth.loggedIn && this.$auth.strategy.name == 'customer') {

//fething carts on Rest API


await this.$store.dispatch('web/cart/getCartsData')
await this.$store.dispatch('web/cart/getCartPrice')

Di atas, kita buat sebuah kondisi terlebih dahulu sebelum melakukan dispatch, yaitu apakah
user tersebut sudah melakukan proses otentikasi dan memiliki role sebagai customer.

Setelah itu, kita akan memanggil data total price dan total cart di dalam computed properti,
yang mana datanya akan diambil dari state yang ada di dalam Vuex.

//cartPrice
cartPrice() {
return this.$store.state.web.cart.cartPrice
},

//cartTotal
cartTotal() {
return this.$store.state.web.cart.carts.length
},

Dan untuk menampilkan data tersebut di dalam template, kita bisa menggunakan kode
seperti berikut ini :

{{ cartTotal }}</span> | Rp. {{ formatPrice(cartPrice) }}

Langkah 2 - Uji Coba Menampilkan Cart di Header


Silahkan refresh / reload halaman website-nya. Dan jika berhasil maka kita akan

903
menampilkan data carts tersebut di dalam header. Kurang lebih seperti berikut ini :

904
Menampilkan Cart Setelah Login dan Menghapus Cart
Setelah Logout

Sekarang kita mengalami problem lagi, silahkan logout dan perhatikan di dalam header data
carts tersebut masih ada. Seharusnya jika kita sudah melakukan proses logout, maka data
carts juga ikut dihilangkan. Sekarang silahkan reload / refresh halamannya, baru data carts
tersebut berhasil hilang di dalam header.

Dan disini kita mengalami problem lagi yang kedua, setelah berhasil logout, sekarang
silahkan login lagi ke dalam akun customer dan perhatikan di dalam header, data carts tidak
ditampilkan. Seharusnya ketika berhasil login maka data carts tersebut harus ditampilkan.

Langkah 1 - Menampilkan Carts Setelah Login


Pertama-tama kita akan mengatasi problem bagaimana cara menampilkan data cart setelah
berhasil melakukan proses otentikasi / login.

Silahkan buka file pages/customer/login.vue, kemudian cari kode berikut ini :

905
async login() {

await this.$auth.loginWith('customer', {
data: {
email: this.user.email,
password: this.user.password
}
})

.then(() => {

//redirect
this.$router.push({
name: 'customer-dashboard'
})

})

.catch(error => {
//assign validation
this.validation = error.response.data
})
}

Kemudian ubaah kode tersebut menjadi seperti berikut ini :

906
async login() {

await this.$auth.loginWith('customer', {
data: {
email: this.user.email,
password: this.user.password
}
})

.then(() => {

//fething carts on Rest API


this.$store.dispatch('web/cart/getCartsData')
this.$store.dispatch('web/cart/getCartPrice')

//redirect
this.$router.push({
name: 'customer-dashboard'
})

})

.catch(error => {
//assign validation
this.validation = error.response.data
})
}

Di atas, kita melakukan dispatch ke dalam 2 action jika proses otentikasi berhasil dilakukan.

//fething carts on Rest API


this.$store.dispatch('web/cart/getCartsData')
this.$store.dispatch('web/cart/getCartPrice')

Sekarang, silahkan login dan perhatikan di header, maka data carts sudah berhasil
ditampilkan.

Langkah 2 - Mengahapus Carts Setelah Logout


Sekarang kita akan mengatasi problem yang satunya, yaitu menghapus data carts setelah
berhasil melakukan proses otentikasi.

907
Silahkan buka file components/web/sidebar.vue, kemudian cari kode berikut ini :

//method "logout"
async logout() {

//logout auth
await this.$auth.logout()

//redirect route customer login


this.$router.push({
name: 'customer-login'
})

Dan ubahlah menjadi seperti berikut ini :

//method "logout"
async logout() {

//logout auth
await this.$auth.logout()

//set state
this.$store.commit('web/cart/SET_CARTS_DATA', [])
this.$store.commit('web/cart/SET_CART_PRICE', 0)

//redirect route customer login


this.$router.push({
name: 'customer-login'
})

Di atas, kita melakukan commit ke dalam 2 mutation setelah proses logout berhasil, yaitu
melakukan set ke SET_CARTS_DATA dengan array kosong dan SET_CART_PRICE dengan
angka 0.

Sekarang, silahkan lakukan proses logout, maka data carts di header juga akan dihapus atau
dihilangkaan.

908
Menampilkan Halaman Cart

Setelah berhasil membuat proses add to cart, maka sekarang kita lanjutkan belajar
bagaimana cara membuat halaman yang digunakan untuk menampilkan data carts
tersebut.

Langkah 1 - Menampilkan Data Carts


Sekarang, silahkan buat folder baru dengan nama cart di dalam folder pages dan di dalam
folder cart tersebut silahkan buat file baru dengan nama index.vue, kemudian masukkan
kode berikut ini di dalamnya.

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row" v-if="carts.length > 0">
<!-- jika data carts ada, maka tampilkan -->
<div class="col-md-7">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL PESANAN</h5>
<hr>
<table class="table table-cart">
<tbody>
<client-only>
<tr v-for="cart in carts" :key="cart.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="cart.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ cart.product.title }}</b></h5>
<table class="table-borderless table-font">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ cart.qty
}}</b></td>
</tr>

909
</table>

</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(cart.price) }}
</p>

<p class="m-0">
<s style="text-decoration-color:red">Rp.
{{ formatPrice(cart.product.price * cart.qty)
}}</s>
</p>

<br>
<div class="text-right">
<button class="btn btn-sm btn-danger">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
</client-only>
</tbody>
</table>

<table class="table table-default">


<tbody>
<tr>
<td class="set-td text-left" width="60%">
<p class="m-0">JUMLAH </p>
</td>
<td class="set-td text-right" width="30%">&nbsp; :
Rp.</td>
<td class="text-right set-td ">
<p class="m-0" id="subtotal"> {{
formatPrice(cartPrice) }}
</p>
</td>
</tr>
<tr>
<td class="set-td text-left border-0">
<p class="m-0">ONGKOS KIRIM (<strong>{{ cartWeight
}}</strong> gram)</p>
</td>
<td class="set-td border-0 text-right">&nbsp; :

910
Rp.</td>
<td class="set-td border-0 text-right">
<p class="m-0" id="ongkir-cart">
</p>
</td>
</tr>
<tr>
<td class=" text-left border-0">
<p class="font-weight-bold m-0 h5 text-
uppercase">Grand Total </p>
</td>
<td class=" border-0 text-right">&nbsp; : Rp.</td>
<td class=" border-0 text-right">
<p class="font-weight-bold m-0 h5" align="right">
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL CUSTOMER</h5>
<hr>
<div class="row">

<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NAMA LENGKAP</label>
<input type="text" class="form-control"
id="nama_lengkap" placeholder="Nama Lengkap"
v-model="customer.name">
<div v-if="validation.name" class="mt-2 alert alert-
danger">
Masukkan Nama Lengkap
</div>
</div>
</div>

<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NO. HP /
WHATSAPP</label>

911
<input type="number" class="form-control" id="phone"
placeholder="No. HP / WhatsApp"
v-model="customer.phone">
<div v-if="validation.phone" class="mt-2 alert alert-
danger">
Masukkan No. Telp
</div>
</div>
</div>

<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">ALAMAT LENGKAP</label>
<textarea class="form-control" id="alamat" rows="3"
placeholder="Alamat Lengkap"
v-model="customer.address"></textarea>
<div v-if="validation.address" class="mt-2 alert
alert-danger">
Masukkan Alamat Lengkap
</div>
</div>
</div>

</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center" v-else>
<!-- data carts tidak tersedia -->
<div class="col-md-10">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<div class="col-sm-12 empty-cart-cls text-center">
<img src="/images/shopping-cart.png" width="150"
height="150" class="img-fluid mb-4 mr-3">
<h3><strong>Keranjang Belanja Kosong :)</strong></h3>
<nuxt-link :to="{name: 'products'}" class="btn btn-warning
btn-lg mt-4" data-abc="true">LANJUTKAN BELANJA
</nuxt-link>
</div>
</div>
</div>
</div>
</div>
</div>

912
</div>
</template>

<script>
export default {

//middleware
middleware: 'isCustomer',

//meta
head() {
return {
title: 'Cart - MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/shopping-cart.png'
},
{
hid: 'description',
name: 'description',
content: 'Cart - Official Toko Online Penjualan Produk
Xiaomi'
},
]
}
},

//hook asyncData
async asyncData({ store }) {

//call action vuex "getCartsData"


await store.dispatch('web/cart/getCartsData')

913
},

//computed
computed: {

//cart data
carts() {
return this.$store.state.web.cart.carts
},

//cart weight
cartWeight() {
return this.$store.state.web.cart.cartWeight
},

//cart price
cartPrice() {
return this.$store.state.web.cart.cartPrice
},
},

//data function
data() {
return {
//state customer
customer: {
name: '',
phone: '',
address: ''
},

//state validation
validation: {
name: false,
phone: false,
address: false
},
}
},
}
</script>

<style scoped>
.table-cart {
border-style: solid !important;
border-color: rgb(198, 206, 214) !important;

914
}

.table-font {
font-size: 14px;
}
</style>

Dari penambahan kode di atas, pertama kita atur component / view ini menggunakan
middleware isCustomer, artinya hanya bisa diakses jika memiliki role sebagai customer.

//middleware
middleware: 'isCustomer',

Dan kita atur juga meta tag dari component / view ini, seperrti title, og:image dan lain-
lain.

915
//meta
head() {
return {
title: 'Cart - MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/shopping-cart.png'
},
{
hid: 'description',
name: 'description',
content: 'Cart - Official Toko Online Penjualan Produk
Xiaomi'
},
]
}
},

Di dalam hook asyncData kita melakukan dispatch ke dalam action di dalam Vuex yang
bernama getCartsData.

//hook asyncData
async asyncData({ store }) {

//call action vuex "getCartsData"


await store.dispatch('web/cart/getCartsData')

},

916
Jika proses dari action di atas berhasil, maka sekarang kita akan mengambil beberapa data
dari state yang ada di Vuex, yaitu data carts, total price dan total weight.

Disini kita menggunakan computed properti untuk mengambil data-data tersebut dari state
yang ada di Vuex.

//computed
computed: {

//cart data
carts() {
return this.$store.state.web.cart.carts
},

//cart weight
cartWeight() {
return this.$store.state.web.cart.cartWeight
},

//cart price
cartPrice() {
return this.$store.state.web.cart.cartPrice
},
},

Dan di dalam data function kita membuat 2 state baru, yaitu customer dan validation.
Untuk state customer akan digunakan untuk menampung isi yang dikirimkan dari input
form. Sedangkan validation akan digunakan untuk menampilkan error valaidasi.

917
//data function
data() {
return {
//state customer
customer: {
name: '',
phone: '',
address: ''
},

//state validation
validation: {
name: false,
phone: false,
address: false
},
}
},

Di dalam template, kita membuat kondisi untuk menampilkan data carts, jika data carts
lebih dari 0, maka kita akan melakukan perulangan data carts tersebut. Tapi jika data carts
sama dengan atau kurang dari 0, maka kita akan menampilkan pesan bahwa data carts
tidak tersedia.

<div class="row" v-if="carts.length > 0">


<!-- jika data carts ada, maka tampilkan -->
<tr v-for="cart in carts" :key="cart.id" style="background:
#edf2f7;">
</tr>
<div>
<div class="row justify-content-center" v-else>
<!-- data carts tidak tersedia -->

</div>

Langkah 2 - Uji Coba Menampilkan Data Carts


Sekarang, silahkan klik button cart yang ada di header atau bisa ke URL berikut ini
http://localhost:3000/cart, jika berhasil maka kita akan mendapatkan hasil seperti berikut ini
:

918
Tapi, jika kita belum memiliki data carts, maka akan mendapatkan tampilan seperti berikut
ini :

919
Membuat Fungsi Remove Cart

Setelah berhasil menampilkan data carts di component / view, sekarang kita akan lanjutkan
untuk membuat fitur remove data cart atau menghapus item cart. Kondisi ini sangat sering
terjadi, customer memasukkan data product ke cart dan ingin membatalkannya.

Langkah 1 - Menambahkan Fungsi Remove Cart


Silahkan buka file pages/cart/index.vue, kemudian ubah semua kode-nya menjadi
seperti berikut ini :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row" v-if="carts.length > 0">
<!-- jika data carts ada, maka tampilkan -->
<div class="col-md-7">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL PESANAN</h5>
<hr>
<table class="table table-cart">
<tbody>
<client-only>
<tr v-for="cart in carts" :key="cart.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="cart.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ cart.product.title }}</b></h5>
<table class="table-borderless table-font">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ cart.qty
}}</b></td>
</tr>
</table>

920
</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(cart.price) }}
</p>

<p class="m-0">
<s style="text-decoration-color:red">Rp.
{{ formatPrice(cart.product.price * cart.qty)
}}</s>
</p>

<br>
<div class="text-right">
<button @click.prevent="removeCart(cart.id)"
class="btn btn-sm btn-danger">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
</client-only>
</tbody>
</table>

<table class="table table-default">


<tbody>
<tr>
<td class="set-td text-left" width="60%">
<p class="m-0">JUMLAH </p>
</td>
<td class="set-td text-right" width="30%">&nbsp; :
Rp.</td>
<td class="text-right set-td ">
<p class="m-0" id="subtotal"> {{
formatPrice(cartPrice) }}
</p>
</td>
</tr>
<tr>
<td class="set-td text-left border-0">
<p class="m-0">ONGKOS KIRIM (<strong>{{ cartWeight
}}</strong> gram)</p>
</td>
<td class="set-td border-0 text-right">&nbsp; :
Rp.</td>

921
<td class="set-td border-0 text-right">
<p class="m-0" id="ongkir-cart">
</p>
</td>
</tr>
<tr>
<td class=" text-left border-0">
<p class="font-weight-bold m-0 h5 text-
uppercase">Grand Total </p>
</td>
<td class=" border-0 text-right">&nbsp; : Rp.</td>
<td class=" border-0 text-right">
<p class="font-weight-bold m-0 h5" align="right">
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL CUSTOMER</h5>
<hr>
<div class="row">

<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NAMA LENGKAP</label>
<input type="text" class="form-control"
id="nama_lengkap" placeholder="Nama Lengkap"
v-model="customer.name">
<div v-if="validation.name" class="mt-2 alert alert-
danger">
Masukkan Nama Lengkap
</div>
</div>
</div>

<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NO. HP /
WHATSAPP</label>
<input type="number" class="form-control" id="phone"

922
placeholder="No. HP / WhatsApp"
v-model="customer.phone">
<div v-if="validation.phone" class="mt-2 alert alert-
danger">
Masukkan No. Telp
</div>
</div>
</div>

<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">ALAMAT LENGKAP</label>
<textarea class="form-control" id="alamat" rows="3"
placeholder="Alamat Lengkap"
v-model="customer.address"></textarea>
<div v-if="validation.address" class="mt-2 alert
alert-danger">
Masukkan Alamat Lengkap
</div>
</div>
</div>

</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center" v-else>
<!-- data carts tidak tersedia -->
<div class="col-md-10">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<div class="col-sm-12 empty-cart-cls text-center">
<img src="/images/shopping-cart.png" width="150"
height="150" class="img-fluid mb-4 mr-3">
<h3><strong>Keranjang Belanja Kosong :)</strong></h3>
<nuxt-link :to="{name: 'products'}" class="btn btn-warning
btn-lg mt-4" data-abc="true">LANJUTKAN BELANJA
</nuxt-link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

923
</template>

<script>
export default {

//middleware
middleware: 'isCustomer',

//meta
head() {
return {
title: 'Cart - MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/shopping-cart.png'
},
{
hid: 'description',
name: 'description',
content: 'Cart - Official Toko Online Penjualan Produk
Xiaomi'
},
]
}
},

//hook asyncData
async asyncData({ store }) {

//call action vuex "getCartsData"


await store.dispatch('web/cart/getCartsData')

},

924
//computed
computed: {

//cart data
carts() {
return this.$store.state.web.cart.carts
},

//cart weight
cartWeight() {
return this.$store.state.web.cart.cartWeight
},

//cart price
cartPrice() {
return this.$store.state.web.cart.cartPrice
},
},

//data function
data() {
return {
//state customer
customer: {
name: '',
phone: '',
address: ''
},

//state validation
validation: {
name: false,
phone: false,
address: false
},
}
},

//method
methods: {
//method "removeCart"
async removeCart(cartId) {

await this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",

925
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {

//call action vuex "getCartsData"


this.$store.dispatch('web/cart/removeCart', {
cart_id: cartId
})

.then(async () => {
//dispatch action "getCartPrice"
await
this.$store.dispatch('web/cart/getCartPrice')

//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Dihapus dari
Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

})
}
})
},
}

}
</script>

<style scoped>
.table-cart {
border-style: solid !important;
border-color: rgb(198, 206, 214) !important;
}

.table-font {
font-size: 14px;

926
}
</style>

Dari pernamabahan kode di atas, pertama kita berikan event @click di dalam button
delete cart, yang mana kita arahkan ke dalam method yang bernama removeCart dengan
memberikan parameter cart_id di dalamnya.

<button @click.prevent="removeCart(cart.id)"class="btn btn-sm btn-


danger"><i class="fa fa-trash"></i></button>

Setelah itu, kita membuat method tersebut dan di dalamnya kita kombinasikan dengan
Sweet Alert2.

await this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
})

Jika kita klik button YA, HAPUS!, maka akan menjalankan dispatch ke dalam action yang
bernama removeCart dengan parameter cart_id.

//call action vuex "getCartsData"


this.$store.dispatch('web/cart/removeCart', {
cart_id: cartId
})

Dan jika proses action tersebut berhasil dilakukan, maka kita akan melakukan dispatch lagi
untuk memperbaruin total price yang ada di dalam carts.

927
//dispatch action "getCartPrice"
await this.$store.dispatch('web/cart/getCartPrice')

Kemudian kita tampilkan alert sukses hapus data menggunakan Sweet Alert2. Kurang lebih
seperti berikut ini :

//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Dihapus dari Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Langkah 2 - Uji Coba Proses Remove Cart


Silahkan masukkan data product ke dalam cart sebanyak mungkin, kemudian lakukan
haapus slah satu data cart tersebut, maka data akan berhasil dihapus dan total price juga
ikut terupdate secara realtime / reactive, baik di header maupun di halaman cart itu sendiri.

928
Membuat Vuex Web RajaOngkir

Sebelum kita lanjutkan lagi di halaman cart untuk menampilkan informasi provinsi,
kota/kabupaten dan perhitungan biaya ongkos kirim, maka kita harus melakukan konfigurasi
Vuex untuk RajaOngkir terlebih dahulu.

Membuat Konfigurasi Vuex Web RajaOngkir


Silahkan file baru dengan nama rajaongkir.js di dalam folder store/web, kemudian
masukkan kode berikut ini di dalamnya.

//state
export const state = () => ({

//provinces
provinces: [],

//cities
cities: [],

//costs ongkir
costs: []

})

//mutations
export const mutations = {

//mutation "SET_PROVINCES_DATA"
SET_PROVINCES_DATA(state, payload) {

//set value state "provinces"


state.provinces = payload
},

//mutation "SET_CITIES_DATA"
SET_CITIES_DATA(state, payload) {

//set value state "cities"


state.cities = payload
},

929
//mutation "SET_COSTS_DATA"
SET_COSTS_DATA(state, payload) {

//set value state "costs"


state.costs = payload
},

//actions
export const actions = {

//get provinces data


getProvincesData({ commit }) {

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/rajaongkir/provinces" with


method "GET"
this.$axios.get('/api/web/rajaongkir/provinces')
//success
.then((response) => {

//commit ti mutation "SET_PROVINCES_DATA"


commit('SET_PROVINCES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//get cities data


getCitiesData({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/rajaongkir/cities" with method


"POST"
this.$axios.post('/api/web/rajaongkir/cities', payload)
//success
.then((response) => {

930
//commit ti mutation "SET_CITIES_DATA"
commit('SET_CITIES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//getOngkirData
getOngkirData({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/rajaongkir/checkOngkir" with


method "POST"
this.$axios.post('/api/web/rajaongkir/checkOngkir', payload)
//success
.then((response) => {

//commit ti mutation "SET_COSTS_DATA"


commit('SET_COSTS_DATA', response.data.data)

//resolve promise
resolve()
})

})
}

Dari penambahan kode di atas, pertama kita menambahkan 3 state baru, yaitu provinces,
cities dan costs.

931
//provinces
provinces: [],

//cities
cities: [],

//costs ongkir
costs: []

Untuk state provinces nantinya akan digunakan untuk menyimpan list data provinsi di
Indonesia. Dan untuk state cities akan digunakan untuk menampilkan list data
kota/kabupaten berdasarkan provinsi tertentu. Kemudian yang terakhir, yaitu state costs
akan digunakan untuk menyimpan data ongkos kirim dari kurir tertentu.

Dan di dalam mutations kita juga menambahkan 3 method baru, yaitu :

SET_PROVINCES_DATA
SET_CITIES_DATA
SET_COSTS_DATA

SET_PROVINCES_DATA

Mutation ini akan digunakan untuk mengubah isi dari state provinces dengan data yang
dikirimkan dari action. Data tersebut berupa response list data provinsi yang di dapatkan
dari Rest API.

//mutation "SET_PROVINCES_DATA"
SET_PROVINCES_DATA(state, payload) {

//set value state "provinces"


state.provinces = payload
},

SET_CITIES_DATA

Mutation ini digunakan untuk mengubah isi dari state cities, dimana datanya akan
dikirimkan oleh action berupa list data kota/kabupaten berdasarkan provinsi tertentu.

932
//mutation "SET_CITIES_DATA"
SET_CITIES_DATA(state, payload) {

//set value state "cities"


state.cities = payload
},

SET_COSTS_DATA

Mutation ini digunakan untuk mengubah isi dari state costs dengan data yang dikirimkan
oleh action dan data yang dikirimkan akan berupa informasi biaya pengiriman / ongkos kirim
dari kurir.

//mutation "SET_COSTS_DATA"
SET_COSTS_DATA(state, payload) {

//set value state "costs"


state.costs = payload
},

Dan di dalam actions kita juga menambahkan 3 method baru, diantaranya adalah

getProvincesData
getCitiesData
getOngkirData

getProvincesData

Action ini akan digunakan untuk mendapatkan list data provinsi dari database. Dimana di
dalam action ini kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/web/rajaongkir/provinces.

//fetching Rest API "/api/web/rajaongkir/provinces" with method "GET"


this.$axios.get('/api/web/rajaongkir/provinces')

Jika proses http request di atas berhasil dilakukan, maka akan melakukan commit ke dalam
mutation yang bernama SET_PROVINCES_DATA dengan parameter berupa data response.

933
//commit ti mutation "SET_PROVINCES_DATA"
commit('SET_PROVINCES_DATA', response.data.data)

getCitiesData

Action ini akan digunakan untuk mendapatkan list data kota/kabupaten berdasarkan
provinsi tertentu. Disini kita melakukan http request menggunakan Axios dengan method
POST ke dalam endpoint /api/web/rajaongkir/cities dan kita kirimkan juga
parameter payload yang isinya adalah ID dari provinsi .

//fetching Rest API "/api/web/rajaongkir/cities" with method "POST"


this.$axios.post('/api/web/rajaongkir/cities', payload)

Jika dari proses fetching di atas berhasil dilakukan, maka akan melakukan commit ke dalam
mutation yang bernama SET_CITIES_DATA dengan parameter berupa data response.

//commit ti mutation "SET_CITIES_DATA"


commit('SET_CITIES_DATA', response.data.data)

getOngkirData

Action ini akan digunakan untuk melakukan perhitungan biaya ongkos kirim. Disini kita
melakukan http request menggunakan Axios dengan method POST ke dalam endpoint
/api/web/rajaongkir/checkOngkir dan kita tambahkan parameter payload. Dimana
di dalam parameter tersebut akan berisi beberapa data, diantaranya seperti ID
kota/kabupaten tujuan, weight dan courier.

//fetching Rest API "/api/web/rajaongkir/checkOngkir" with method "POST"


this.$axios.post('/api/web/rajaongkir/checkOngkir', payload)

Jika dari proses perhitungan biaya ongkos kirim berhasil dilakukan, maka akan melakukan
commit ke dalam mutation yang bernama SET_COSTS_DATA dengan parameter data
response.

934
//commit ti mutation "SET_COSTS_DATA"
commit('SET_COSTS_DATA', response.data.data)

935
Menghitung Biaya Ongkos Kirim

Pada materi kali ini, kita akan menambahkan proses perhitungan biaya ongkos kirim di
dalam halaman cart. Disini kita akan menampilkan list data provinsi, kota/kabupaten dan
kurir. Ketiga data tersebutlah yang akan digunakan untuk menentukan biaya ongkos kirim di
dalam RajaOngkir.

Langkah 1 - Menambahkan Proses Biaya Ongkos Kirim


Silahkan buka file pages/cart/index.vue, kemudian ubah semua kode-nya menjadi
seperti berikut ini :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row" v-if="carts.length > 0">
<!-- jika data carts ada, maka tampilkan -->
<div class="col-md-7">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL PESANAN</h5>
<hr>
<table class="table table-cart">
<tbody>
<client-only>
<tr v-for="cart in carts" :key="cart.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">
<div class="wrapper-image-cart">
<img :src="cart.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ cart.product.title }}</b></h5>
<table class="table-borderless table-font">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ cart.qty
}}</b></td>
</tr>

936
</table>

</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(cart.price) }}
</p>

<p class="m-0">
<s style="text-decoration-color:red">Rp.
{{ formatPrice(cart.product.price * cart.qty)
}}</s>
</p>

<br>
<div class="text-right">
<button @click.prevent="removeCart(cart.id)"
class="btn btn-sm btn-danger">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
</client-only>
</tbody>
</table>

<table class="table table-default">


<tbody>
<tr>
<td class="set-td text-left" width="60%">
<p class="m-0">JUMLAH </p>
</td>
<td class="set-td text-right" width="30%">&nbsp; :
Rp.</td>
<td class="text-right set-td ">
<p class="m-0" id="subtotal"> {{
formatPrice(cartPrice) }}
</p>
</td>
</tr>
<tr>
<td class="set-td text-left border-0">
<p class="m-0">ONGKOS KIRIM (<strong>{{ cartWeight
}}</strong> gram)</p>
</td>

937
<td class="set-td border-0 text-right">&nbsp; :
Rp.</td>
<td class="set-td border-0 text-right">
<p class="m-0" id="ongkir-cart">
{{ formatPrice(courier.courier_cost) }}
</p>
</td>
</tr>
<tr>
<td class=" text-left border-0">
<p class="font-weight-bold m-0 h5 text-
uppercase">Grand Total </p>
</td>
<td class=" border-0 text-right">&nbsp; : Rp.</td>
<td class=" border-0 text-right">
<p class="font-weight-bold m-0 h5" align="right">
{{ formatPrice(grandTotal) }}
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL CUSTOMER</h5>
<hr>
<div class="row">

<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NAMA LENGKAP</label>
<input type="text" class="form-control"
id="nama_lengkap" placeholder="Nama Lengkap"
v-model="customer.name">
<div v-if="validation.name" class="mt-2 alert alert-
danger">
Masukkan Nama Lengkap
</div>
</div>
</div>

<div class="col-md-6">

938
<div class="form-group">
<label class="font-weight-bold">NO. HP /
WHATSAPP</label>
<input type="number" class="form-control" id="phone"
placeholder="No. HP / WhatsApp"
v-model="customer.phone">
<div v-if="validation.phone" class="mt-2 alert alert-
danger">
Masukkan No. Telp
</div>
</div>
</div>

<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">PROVINSI</label>
<select class="form-control" v-
model="rajaongkir.province_id" @change="getCities">
<option value="">-- pilih provinsi --</option>
<option v-for="province in provinces"
:key="province.id" :value="province.province_id">
{{ province.name }}</option>
</select>
</div>
</div>

<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">KOTA /
KABUPATEN</label>
<select class="form-control" v-
model="rajaongkir.city_id" @change="showCourier">
<option value="">-- pilih kota --</option>
<option v-for="city in cities" :key="city.id"
:value="city.city_id">{{ city.name }}</option>
</select>
</div>
</div>

<div class="col-md-12">
<div class="form-group" v-if="courier.showCourier">
<label class="font-weight-bold">KURIR
PENGIRIMAN</label>
<br>
<div class="form-check form-check-inline">
<input class="form-check-input select-courier"

939
type="radio" name="courier" id="ongkos_kirim-jne"
value="jne" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold
mr-4" for="ongkos_kirim-jne">
JNE</label>
<input class="form-check-input select-courier"
type="radio" name="courier" id="ongkos_kirim-tiki"
value="tiki" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold
mr-4" for="ongkos_kirim-jnt">TIKI</label>
<input class="form-check-input select-courier"
type="radio" name="courier" id="ongkos_kirim-pos"
value="pos" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold"
for="ongkos_kirim-jnt">POS</label>
</div>
</div>
</div>

<div class="col-md-12">
<div class="form-group" v-if="courier.showService">
<hr>
<label class="font-weight-bold">SERVICE KURIR</label>
<br>
<div v-for="value in costs" :key="value.service"
class="form-check form-check-inline">
<input class="form-check-input" type="radio"
name="cost" :id="value.service"
:value="value.cost[0].value+'|'+value.service" v-
model="courier.courier_service_cost"
@change="getServiceCost">
<label class="form-check-label font-weight-normal
mr-5" :for="value.service">
{{ value.service }} - Rp. {{
formatPrice(value.cost[0].value) }}</label>
</div>
</div>
</div>

<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">ALAMAT LENGKAP</label>
<textarea class="form-control" id="alamat" rows="3"

940
placeholder="Alamat Lengkap"
v-model="customer.address"></textarea>
<div v-if="validation.address" class="mt-2 alert
alert-danger">
Masukkan Alamat Lengkap
</div>
</div>
</div>

<div class="col-md-12" v-if="btnCheckout">


<button class="btn btn-warning btn-lg btn-
block">CHECKOUT</button>
</div>

</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center" v-else>
<!-- data carts tidak tersedia -->
<div class="col-md-10">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<div class="col-sm-12 empty-cart-cls text-center">
<img src="/images/shopping-cart.png" width="150"
height="150" class="img-fluid mb-4 mr-3">
<h3><strong>Keranjang Belanja Kosong :)</strong></h3>
<nuxt-link :to="{name: 'products'}" class="btn btn-warning
btn-lg mt-4" data-abc="true">LANJUTKAN BELANJA
</nuxt-link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
export default {

//middleware
middleware: 'isCustomer',

941
//meta
head() {
return {
title: 'Cart - MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/shopping-cart.png'
},
{
hid: 'description',
name: 'description',
content: 'Cart - Official Toko Online Penjualan Produk
Xiaomi'
},
]
}
},

//hook asyncData
async asyncData({ store }) {

//call action vuex "getCartsData"


await store.dispatch('web/cart/getCartsData')

//call action vuex "getProvincesData"


await store.dispatch('web/rajaongkir/getProvincesData')

},

//computed
computed: {

//cart data

942
carts() {
return this.$store.state.web.cart.carts
},

//cart weight
cartWeight() {
return this.$store.state.web.cart.cartWeight
},

//cart price
cartPrice() {
return this.$store.state.web.cart.cartPrice
},

//provinces
provinces() {
return this.$store.state.web.rajaongkir.provinces
},

//cities
cities() {
return this.$store.state.web.rajaongkir.cities
},

//costs
costs() {
return this.$store.state.web.rajaongkir.costs
}
},

//data function
data() {
return {
//state customer
customer: {
name: '',
phone: '',
address: ''
},

//state validation
validation: {
name: false,
phone: false,
address: false
},

943
//state rajaongkir
rajaongkir: {
province_id: '',
city_id: ''
},

//state courier
courier: {
showCourier: false,
showService: false,
courier_name: '',
courier_service_cost: '',
courier_service: '',
courier_cost: ''
},

//grandTotal
grandTotal: 0,

//state button checkout


btnCheckout: false
}
},

//method
methods: {
//method "removeCart"
async removeCart(cartId) {

await this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {

//call action vuex "getCartsData"


this.$store.dispatch('web/cart/removeCart', {
cart_id: cartId
})

944
.then(async () => {
//dispatch action "getCartPrice"
await this.$store.dispatch('web/cart/getCartPrice')

//sum grandTotal after remove cart


this.grandTotal = parseInt(this.cartPrice) +
parseInt(this.courier.courier_cost)

//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Dihapus dari Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

})
}
})
},

//method "getCities"
getCities() {
this.$store.dispatch('web/rajaongkir/getCitiesData', {
province_id: this.rajaongkir.province_id
})
},

//method "showCourier"
showCourier() {
this.courier.showCourier = true
},

//method "showService"
async showService() {

//check weight product


if (this.cartWeight == 0) {
alert('silahkan pilih produk terlebih dahulu!')
return
}

await this.$store.dispatch('web/rajaongkir/getOngkirData', {
destination: this.rajaongkir.city_id,
weight: this.cartWeight,

945
courier: this.courier.courier_name
})

.then(() => {
this.courier.showService = true
})
},

//method "getServiceCost"
getServiceCost() {

//split value dengan menghapus string -> |


let shipping = this.courier.courier_service_cost.split("|")

//set state cost dan service


this.courier.courier_cost = shipping[0]
this.courier.courier_service = shipping[1]

//sum grandTotal
this.grandTotal = parseInt(this.cartPrice) +
parseInt(this.courier.courier_cost)

//show button checkout


this.btnCheckout = true
},
}

}
</script>

<style scoped>
.table-cart {
border-style: solid !important;
border-color: rgb(198, 206, 214) !important;
}

.table-font {
font-size: 14px;
}
</style>

Dari penambahan kode di atas, mari kita bahas satu-persatu. Pertama kita menambahakn
action di dalam hook asyncData yang mana action tersebut akan dijalankan pertama kali
saat halaman dibuka.

946
//call action vuex "getProvincesData"
await store.dispatch('web/rajaongkir/getProvincesData')

Di atas kita melakukan dispatch ke dalam action yang bernama getProvincesData.

Setelah itu, kita akan ambil data provinsi di dalam state Vuex menggunakan computed
properti. Kurang lebih seperti berikut ini :

//provinces
provinces() {
return this.$store.state.web.rajaongkir.provinces
},

Dan untuk menampilkan list data provinsi di dalam template, kita bisa menggunakan kode
seperti berikut ini :

<div class="form-group">
<label class="font-weight-bold">PROVINSI</label>
<select class="form-control" v-model="rajaongkir.province_id"
@change="getCities">
<option value="">-- pilih provinsi --</option>
<option v-for="province in provinces" :key="province.id"
:value="province.province_id">
{{ province.name }}
</option>
</select>
</div>

Di atas, kita melakukan perulangan data provinces menggunakan directive v-for di


dalam select option. Di dalam select option kita menambahkan event @change, dimana jika
select option di ubah/pilih, maka akan memanggil method getCities dengan parameter
state province_id yang berisi data ID provinsi.

947
//method "getCities"
getCities() {
this.$store.dispatch('web/rajaongkir/getCitiesData', {
province_id: this.rajaongkir.province_id
})
},

Di dalam method getCities di atas, kita melakukan dispatch ke dalam action yang
bernama getCitiesData dengan mengirimkan parameter ID Province.

Setelah proses di dalam action berhasil dilakukan, maka kita akan mengambil list data
kota/kabupaten yang ada di dalam state Vuex menggunakan computed properti.

//cities
cities() {
return this.$store.state.web.rajaongkir.cities
},

Dan kita akan menampilkan data kota/kabupaten tersebut di dalam template menggunakan
perulangan v-for.

<div class="form-group">
<label class="font-weight-bold">KOTA / KABUPATEN</label>
<select class="form-control" v-model="rajaongkir.city_id"
@change="showCourier">
<option value="">-- pilih kota --</option>
<option v-for="city in cities" :key="city.id"
:value="city.city_id">{{ city.name }}</option>
</select>
</div>

Sama seperti sebelumnya, di dalam select option city kita menambahkan event @change
yang mengarah ke dalam method showCourier.

Di dalam method showCourier kita akan mengubah nilai dari state


courier.showCourier yang ada di dalam data function menjadi true, yang artinya
template yang berisi pilihan data kurir akan di tampilkan.

948
//method "showCourier"
showCourier() {
this.courier.showCourier = true
},

<div class="form-group" v-if="courier.showCourier">


<label class="font-weight-bold">KURIR PENGIRIMAN</label>
<br>
<div class="form-check form-check-inline">
<input class="form-check-input select-courier" type="radio"
name="courier" id="ongkos_kirim-jne" value="jne" v-
model="courier.courier_name" @change="showService">
<label class="form-check-label font-weight-bold mr-4"
for="ongkos_kirim-jne">
JNE</label>
<input class="form-check-input select-courier" type="radio"
name="courier" id="ongkos_kirim-tiki" value="tiki" v-
model="courier.courier_name" @change="showService">
<label class="form-check-label font-weight-bold mr-4"
for="ongkos_kirim-jnt">TIKI</label>
<input class="form-check-input select-courier" type="radio"
name="courier" id="ongkos_kirim-pos" value="pos" v-
model="courier.courier_name" @change="showService">
<label class="form-check-label font-weight-bold"
for="ongkos_kirim-jnt">POS</label>
</div>
</div>

Di atas, bisa kita perhatikan, jika state courier.showCourier bernilai true, maka pilihan
kurir akan di tampilkan. Di atas, kita memiliki 3 pilihan kurir, yaitu JNE, TIKI dan POS.

v-if="courier.showCourier"

Di dalam input kurir di atas, kita menggunakan event @change yang akan di arahkan ke
dalam method yang bernama showService, method inilah yang akan digunakan untuk
menghitung biaya ongkos kirim.

949
//method "showService"
async showService() {

//check weight product


if (this.cartWeight == 0) {
alert('silahkan pilih produk terlebih dahulu!')
return
}

await this.$store.dispatch('web/rajaongkir/getOngkirData', {
destination: this.rajaongkir.city_id,
weight: this.cartWeight,
courier: this.courier.courier_name
})

.then(() => {
this.courier.showService = true
})
},

Di atas, di dalam method showService pertama kita lakukan pengecekan untuk berat dari
cart, jika berat sama dengan 0, artinya data cart masih kosong, maka akan menampilkan
sebuah alert/message silahkan pilih produk terlebih dahulu!.

Setelah itu, kita melakukan dispatch ke dalam action Vuex untuk proses perhitungan biaya
ongkos kirim. Dan di dalamnya kita berikan beberapa parameter.

destination (kota tujuan)


weight (berat)
courier (kurir)

await this.$store.dispatch('web/rajaongkir/getOngkirData', {
destination: this.rajaongkir.city_id,
weight: this.cartWeight,
courier: this.courier.courier_name
})

Jika dari proses action di atas berhasil dilakukan, maka kita akan ambil data biaya ongkos
kirim di state costs yang ada di Vuex dengan computed properti.

950
//costs
costs() {
return this.$store.state.web.rajaongkir.costs
}

Dan kita juga tampilkan hasil dari perhitungan biaya ongkos kirim tersebut di dalam
template.

this.courier.showService = true

<div class="form-group" v-if="courier.showService">


<hr>
<label class="font-weight-bold">SERVICE KURIR</label>
<br>
<div v-for="value in costs" :key="value.service" class="form-check
form-check-inline">
<input class="form-check-input" type="radio" name="cost"
:id="value.service" :value="value.cost[0].value+'|'+value.service" v-
model="courier.courier_service_cost" @change="getServiceCost">
<label class="form-check-label font-weight-normal mr-5"
:for="value.service">
{{ value.service }} - Rp. {{
formatPrice(value.cost[0].value) }}</label>
</div>
</div>

Di atas, ketika kita memilih jasa service kurir, maka akan menjalankan event @change yang
mengarah ke dalam method yang bernama getServiceCost, di dalam method ini kita
akan melakukan penjumlahan untuk mendapatkan grand total dari pembelian, dimana
yang akan di hitung adalah total cart + biaya kurir.

951
//method "getServiceCost"
getServiceCost() {

//split value dengan menghapus string -> |


let shipping = this.courier.courier_service_cost.split("|")

//set state cost dan service


this.courier.courier_cost = shipping[0]
this.courier.courier_service = shipping[1]

//sum grandTotal
this.grandTotal = parseInt(this.cartPrice) +
parseInt(this.courier.courier_cost)

//show button checkout


this.btnCheckout = true
},

Saat kita memilih service kurir dan menjalankan method getServiceCost, maka akan
mengirim 2 data yaitu biaya ongkoso kirim dan nama service pengiriman.

value.cost[0].value+'|'+value.service

Di atas, 2 data tersebut di pisahkan menggunakan |. Oleh sebab itu di dalam method
getServiceCost kita melakukan split dengan | yang bertujuan untuk memisahkan data
tersebut.

//split value dengan menghapus string -> |


let shipping = this.courier.courier_service_cost.split("|")

Setelah itu, kita masukkan 2 data tersebut ke dalam variable yang berbeda.

//set state cost dan service


this.courier.courier_cost = shipping[0] // <-- biaya ongkos kirim
this.courier.courier_service = shipping[1] // <-- nama service/layanan
(REG, OK dan lain-lain)

952
Kemudian melakukan penjumlahan grand total, yang diambil dari total price cart dan biaya
ongkos kirim.

//sum grandTotal
this.grandTotal = parseInt(this.cartPrice) +
parseInt(this.courier.courier_cost)

Dan yang terakhir, kita atur agar button CHECKOUT ditampilkan.

//show button checkout


this.btnCheckout = true

Langkah 2 - Uji Coba Proses Hitung Biaya Ongkos Kirim


Sekarang, kita akan lakukan uji coba untuk menghitung biaya ongkos kirim, silahkan
masuka ke halaman cart / di http://localhost:3000/cart, maka kurang lebih tampilannya
seperti berikut ini :

953
Sekarang, silahkan pilih data provinsi di dalam select option, maka kita akan menampilkan
data kota berdasarkan provinsi tersebut.

Kemudian, ketika kita memilih data kota, maka pilihan kurir akan di tampilkan, sepeti
berikut ini :

954
Sekarang, jika kita klik salah satu kurir, maka akan menampilkan service dan biaya ongkos
kirim.

955
Dan jika kita pilih service dan biaya ongkos kirim, maka akan mengupdate nilai grand
total dan menampilkan button checkout.

956
957
Menampilkan Categories di Header

Sekarang, kita akan belajar bagaimana cara menampilkan data categories di Header atau
Navbar website yang sedang kita buat.

Langkah 1 - Menampilkan Categories di Header


Silahkan buka file components/web/header.vue, kemudian ubah semua kode-nya
menjadi seperti berikut ini :

<template>
<header class="section-header fixed-top">
<section class="header-main border-bottom">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-lg-3 col-sm-4 col-md-4 col-5">
<nuxt-link to="/" class="brand-wrap" data-abc="true">
<img src="/images/xiaomi.png" width="35" class="bg-light
p-2 rounded">
<span class="logo">MI STORE</span>
</nuxt-link>
</div>
<div class="col-lg-4 col-xl-5 col-sm-8 col-md-4 d-none d-md-
block">
<div class="search-wrap">
<div class="input-group w-100">
<input type="text" class="form-control search-form"
style="width:55%;" placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button class="btn btn-primary search-button"> <i
class="fa fa-search"></i> </button>
</div>
</div>
</div>
</div>
<div class="col-lg-5 col-xl-4 col-sm-8 col-md-4 col-7">
<div class="d-flex justify-content-end">
<a href="#" class="btn search-button btn-md d-md-block
ml-4"><i class="fa fa-shopping-cart"></i> <span class="ml-2">0</span> |
Rp. 0</a>
</div>
</div>

958
</div>
</div>
</section>
<nav class="navbar navbar-expand-md navbar-main border-bottom p-2">
<div class="container-fluid">
<div class="d-md-none my-2">
<div class="input-group">
<input type="search" name="search" class="form-control"
placeholder="mau belanja apa hari ini ?">
<div class="input-group-append">
<button class="btn btn-warning"> <i class="fa fa-
search"></i></button>
</div>
</div>
</div>
<button class="navbar-toggler collapsed" type="button" data-
toggle="collapse" data-target="#dropdown6"
aria-expanded="false"> <span class="navbar-toggler-
icon"></span> </button>
<div class="navbar-collapse collapse" id="dropdown6">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown"> <a class="nav-link dropdown-
toggle" href="#" data-toggle="dropdown"
data-abc="true" aria-expanded="false"><i class="fa fa-
list-ul"></i> KATEGORI</a>
<div class="dropdown-menu">
<nuxt-link :to="{name: 'categories-slug', params: {slug:
category.slug}}" class="dropdown-item" v-for="category in categories"
:key="category.id">
<img :src="category.image" width="50"> {{
category.name }}
</nuxt-link>
<div class="dropdown-divider"></div>
<nuxt-link :to="{name: 'categories'}" class="dropdown-
item active text-center" href="" data-abc="true">
LIHAT SEMUA KATEGORI <i class="fa fa-long-arrow-alt-
right"></i>
</nuxt-link>
</div>
</li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-shopping-bag"></i> SEMUA PRODUK</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-info-circle"></i> TENTANG</a> </li>
<li class="nav-item"> <a href="#" class="nav-link" data-
abc="true"><i class="fa fa-comments"></i> KONTAK</a> </li>

959
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown" v-if="!$auth.loggedIn">
<nuxt-link :to="{name: 'customer-login'}" class="nav-link"
href="#" role="button" aria-expanded="false"> <i class="fa fa-user-
circle"></i>
ACCOUNT</nuxt-link>
</li>
<li class="nav-item dropdown" v-if="$auth.loggedIn">
<nuxt-link :to="{name: 'customer-dashboard'}" class="nav-
link" href="#" role="button" aria-expanded="false"> <i class="fa fa-
tachometer-alt"></i>
DASHBOARD</nuxt-link>
</li>
</ul>
</div>
</div>
</nav>
</header>
</template>

<script>
export default {

//hook "fetch"
async fetch() {

//fething sliders on Rest API


await this.$store.dispatch('web/category/getCategoriesData')
},

//computed
computed: {
//categories
categories() {
return this.$store.state.web.category.categories
},
}

}
</script>

<style scoped>
.btn {
font-size: initial;
}

960
</style>

Dari perubahan kode di atas, pertama kita memanggil hook yang bernama fetch,
fungsinya untuk memanggil atau melakukan dispatch ke dalam action Vuex secara SSR
atau server side rendering.

//hook "fetch"
async fetch() {

//fething sliders on Rest API


await this.$store.dispatch('web/category/getCategoriesData')
},

Setelah itu, kita menggunakan computed properti untuk mengambil data dari state yang
bernama categories di dalam Vuex.

//computed
computed: {

//categories
categories() {
return this.$store.state.web.category.categories
},
}

Dan untuk menampilkan data-nya, kita menggunakan directive v-for, karena data yang
ditampilkan lebih dari 1.

<nuxt-link :to="{name: 'categories-slug', params: {slug:


category.slug}}" class="dropdown-item" v-for="category in categories"
:key="category.id">

//...
</nuxt-link>

961
Langkah 2 - Melihat Hasil Categories di Header
Silahkan reload / refresh halaman website-nya, jika berhasil maka kita akan melihat
tampilan menu dropdown berisi data categories, kurang lebih seperti berikut ini :

962
Membuat Proses Checkout

Setelah berhasil melakukan perhitungan biaya ongkos kirim di dalam halaman cart, maka
sekarang kita akan lanjutkan untuk membuat proses checkout. Dimana di dalam proses
checkout tersebut kita akan melakukan request ke dalam server Midtrans untuk
mengenerate sebuah snap token. Dan sebelum itu, kita juga akan membuat module di Vuex
dulu untuk menghandle proses checkout di Nuxt.js.

Langkah 1 - Konfigurasi Vuex Web Checkout


Pertama, kita akan membuat module di Vuex terlebih dahulu. Silahkan buat file baru dengan
nama checkout.js di dalam folder store/web. Dan masukkan kode berikut ini di
dalamnya.

963
//actions
export const actions = {

//store checkout
storeCheckout({ dispatch, commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//store to Rest API "/api/web/checkout" with method "POST"


this.$axios.post('/api/web/checkout', payload)

//success
.then(response => {

//resolve promise
resolve(response.data.data)
//dispatch cart
dispatch('web/cart/getCartsData', null, { root: true })

})

//error
.catch(error => {
reject(error)
})

})
},

Dari penambahan kode di atas, kita hanya menambahkan 1 action baru yang bernama
storeCheckout, action tersebut yang nanti digunakan mengirimkan data ke server untuk
proses checkout.

Di dalam action tersebut kita melakukan sending data menggunakan Axios dengan method
POST ke dalam endpoint /api/web/checkout. Dan kita kirimkan parameter payload
yang mana isinya adalah data-data seperti nama, phone, alamat provinsi dan lain-lain.

964
//store to Rest API "/api/web/checkout" with method "POST"
this.$axios.post('/api/web/checkout', payload)

Jika proses di atas berhasil, maka kita akan lakukan resolve dengan parameter response
data. Karena kita akan menggunakan response data tersebut di dalam component / view.

//resolve promise
resolve(response.data.data)

Dan terakhir kita melakukan dispatch ke dalam action yang bernama getCartsData,
action ini berada di luar module checkout, oleh sebab itu kita harus menambahkan root:
true. Dengan melakukan dispatch ke dalam action tersebut, maka data cart yang ada akan
diperbarui atau dihapus.

//dispatch cart
dispatch('web/cart/getCartsData', null, { root: true })

Langkah 2 - Menambahkan Proses Checkout


Sekarang kita akan menambahkan proses checkout di dalam halaman cart. Silahkan buka
file pages/cart/index.vue, kemudian ubah semua kode-nya menjadi seperti berikut ini :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row" v-if="carts.length > 0">
<!-- jika data carts ada, maka tampilkan -->
<div class="col-md-7">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL PESANAN</h5>
<hr>
<table class="table table-cart">
<tbody>
<client-only>
<tr v-for="cart in carts" :key="cart.id"
style="background: #edf2f7;">
<td class="b-none" width="25%">

965
<div class="wrapper-image-cart">
<img :src="cart.product.image" style="width:
100%;border-radius: .5rem">
</div>
</td>
<td class="b-none" width="50%">
<h5><b>{{ cart.product.title }}</b></h5>
<table class="table-borderless table-font">
<tr>
<td style="padding: .20rem">QTY</td>
<td style="padding: .20rem">:</td>
<td style="padding: .20rem"><b>{{ cart.qty
}}</b></td>
</tr>
</table>

</td>
<td class="b-none text-right">
<p class="m-0 font-weight-bold">Rp. {{
formatPrice(cart.price) }}
</p>

<p class="m-0">
<s style="text-decoration-color:red">Rp.
{{ formatPrice(cart.product.price * cart.qty)
}}</s>
</p>

<br>
<div class="text-right">
<button @click.prevent="removeCart(cart.id)"
class="btn btn-sm btn-danger">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
</client-only>
</tbody>
</table>

<table class="table table-default">


<tbody>
<tr>
<td class="set-td text-left" width="60%">
<p class="m-0">JUMLAH </p>

966
</td>
<td class="set-td text-right" width="30%">&nbsp; :
Rp.</td>
<td class="text-right set-td ">
<p class="m-0" id="subtotal"> {{
formatPrice(cartPrice) }}
</p>
</td>
</tr>
<tr>
<td class="set-td text-left border-0">
<p class="m-0">ONGKOS KIRIM (<strong>{{ cartWeight
}}</strong> gram)</p>
</td>
<td class="set-td border-0 text-right">&nbsp; :
Rp.</td>
<td class="set-td border-0 text-right">
<p class="m-0" id="ongkir-cart">
{{ formatPrice(courier.courier_cost) }}
</p>
</td>
</tr>
<tr>
<td class=" text-left border-0">
<p class="font-weight-bold m-0 h5 text-
uppercase">Grand Total </p>
</td>
<td class=" border-0 text-right">&nbsp; : Rp.</td>
<td class=" border-0 text-right">
<p class="font-weight-bold m-0 h5" align="right">
{{ formatPrice(grandTotal) }}
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<h5>DETAIL CUSTOMER</h5>
<hr>
<div class="row">

967
<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NAMA LENGKAP</label>
<input type="text" class="form-control"
id="nama_lengkap" placeholder="Nama Lengkap"
v-model="customer.name">
<div v-if="validation.name" class="mt-2 alert alert-
danger">
Masukkan Nama Lengkap
</div>
</div>
</div>

<div class="col-md-6">
<div class="form-group">
<label class="font-weight-bold">NO. HP /
WHATSAPP</label>
<input type="number" class="form-control" id="phone"
placeholder="No. HP / WhatsApp"
v-model="customer.phone">
<div v-if="validation.phone" class="mt-2 alert alert-
danger">
Masukkan No. Telp
</div>
</div>
</div>

<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">PROVINSI</label>
<select class="form-control" v-
model="rajaongkir.province_id" @change="getCities">
<option value="">-- pilih provinsi --</option>
<option v-for="province in provinces"
:key="province.id" :value="province.province_id">
{{ province.name }}</option>
</select>
</div>
</div>

<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">KOTA /
KABUPATEN</label>
<select class="form-control" v-
model="rajaongkir.city_id" @change="showCourier">

968
<option value="">-- pilih kota --</option>
<option v-for="city in cities" :key="city.id"
:value="city.city_id">{{ city.name }}</option>
</select>
</div>
</div>

<div class="col-md-12">
<div class="form-group" v-if="courier.showCourier">
<label class="font-weight-bold">KURIR
PENGIRIMAN</label>
<br>
<div class="form-check form-check-inline">
<input class="form-check-input select-courier"
type="radio" name="courier" id="ongkos_kirim-jne"
value="jne" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold
mr-4" for="ongkos_kirim-jne">
JNE</label>
<input class="form-check-input select-courier"
type="radio" name="courier" id="ongkos_kirim-tiki"
value="tiki" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold
mr-4" for="ongkos_kirim-jnt">TIKI</label>
<input class="form-check-input select-courier"
type="radio" name="courier" id="ongkos_kirim-pos"
value="pos" v-model="courier.courier_name"
@change="showService">
<label class="form-check-label font-weight-bold"
for="ongkos_kirim-jnt">POS</label>
</div>
</div>
</div>

<div class="col-md-12">
<div class="form-group" v-if="courier.showService">
<hr>
<label class="font-weight-bold">SERVICE KURIR</label>
<br>
<div v-for="value in costs" :key="value.service"
class="form-check form-check-inline">
<input class="form-check-input" type="radio"
name="cost" :id="value.service"
:value="value.cost[0].value+'|'+value.service" v-

969
model="courier.courier_service_cost"
@change="getServiceCost">
<label class="form-check-label font-weight-normal
mr-5" :for="value.service">
{{ value.service }} - Rp. {{
formatPrice(value.cost[0].value) }}</label>
</div>
</div>
</div>

<div class="col-md-12">
<div class="form-group">
<label class="font-weight-bold">ALAMAT LENGKAP</label>
<textarea class="form-control" id="alamat" rows="3"
placeholder="Alamat Lengkap"
v-model="customer.address"></textarea>
<div v-if="validation.address" class="mt-2 alert
alert-danger">
Masukkan Alamat Lengkap
</div>
</div>
</div>

<div class="col-md-12" v-if="btnCheckout">


<button @click.prevent="checkout" class="btn btn-warning
btn-lg btn-block">CHECKOUT</button>
</div>

</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center" v-else>
<!-- data carts tidak tersedia -->
<div class="col-md-10">
<div class="card border-0 rounded border-top-orange shadow-sm">
<div class="card-body">
<div class="col-sm-12 empty-cart-cls text-center">
<img src="/images/shopping-cart.png" width="150"
height="150" class="img-fluid mb-4 mr-3">
<h3><strong>Keranjang Belanja Kosong :)</strong></h3>
<nuxt-link :to="{name: 'products'}" class="btn btn-warning
btn-lg mt-4" data-abc="true">LANJUTKAN BELANJA
</nuxt-link>
</div>

970
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
export default {

//middleware
middleware: 'isCustomer',

//meta
head() {
return {
title: 'Cart - MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Cart - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/shopping-cart.png'
},
{
hid: 'description',
name: 'description',
content: 'Cart - Official Toko Online Penjualan Produk
Xiaomi'
},
]
}
},

//hook asyncData

971
async asyncData({ store }) {

//call action vuex "getCartsData"


await store.dispatch('web/cart/getCartsData')

//call action vuex "getProvincesData"


await store.dispatch('web/rajaongkir/getProvincesData')

},

//computed
computed: {

//cart data
carts() {
return this.$store.state.web.cart.carts
},

//cart weight
cartWeight() {
return this.$store.state.web.cart.cartWeight
},

//cart price
cartPrice() {
return this.$store.state.web.cart.cartPrice
},

//provinces
provinces() {
return this.$store.state.web.rajaongkir.provinces
},

//cities
cities() {
return this.$store.state.web.rajaongkir.cities
},

//costs
costs() {
return this.$store.state.web.rajaongkir.costs
}
},

//data function
data() {

972
return {
//state customer
customer: {
name: '',
phone: '',
address: ''
},

//state validation
validation: {
name: false,
phone: false,
address: false
},

//state rajaongkir
rajaongkir: {
province_id: '',
city_id: ''
},

//state courier
courier: {
showCourier: false,
showService: false,
courier_name: '',
courier_service_cost: '',
courier_service: '',
courier_cost: ''
},

//grandTotal
grandTotal: 0,

//state button checkout


btnCheckout: false
}
},

//method
methods: {
//method "removeCart"
async removeCart(cartId) {

await this.$swal.fire({
title: 'APAKAH ANDA YAKIN ?',

973
text: "INGIN MENGHAPUS DATA INI !",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'YA, HAPUS!',
cancelButtonText: 'TIDAK',
}).then((result) => {
if (result.isConfirmed) {

//call action vuex "getCartsData"


this.$store.dispatch('web/cart/removeCart', {
cart_id: cartId
})

.then(async () => {
//dispatch action "getCartPrice"
await this.$store.dispatch('web/cart/getCartPrice')

//sum grandTotal after remove cart


this.grandTotal = parseInt(this.cartPrice) +
parseInt(this.courier.courier_cost)

//alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Product Berhasil Dihapus dari Keranjang!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

})
}
})
},

//method "getCities"
getCities() {
this.$store.dispatch('web/rajaongkir/getCitiesData', {
province_id: this.rajaongkir.province_id
})
},

//method "showCourier"
showCourier() {

974
this.courier.showCourier = true
},

//method "showService"
async showService() {

//check weight product


if (this.cartWeight == 0) {
alert('silahkan pilih produk terlebih dahulu!')
return
}

await this.$store.dispatch('web/rajaongkir/getOngkirData', {
destination: this.rajaongkir.city_id,
weight: this.cartWeight,
courier: this.courier.courier_name
})

.then(() => {
this.courier.showService = true
})
},

//method "getServiceCost"
getServiceCost() {

//split value dengan menghapus string -> |


let shipping = this.courier.courier_service_cost.split("|")

//set state cost dan service


this.courier.courier_cost = shipping[0]
this.courier.courier_service = shipping[1]

//sum grandTotal
this.grandTotal = parseInt(this.cartPrice) +
parseInt(this.courier.courier_cost)

//show button checkout


this.btnCheckout = true
},

//method "checkout"
async checkout() {

//ceck apakah ada nama, phone, address dan berat produk ?


if (this.customer.name && this.customer.phone &&

975
this.customer.address && this.cartWeight) {

//define formData
let formData = new FormData();

formData.append('courier', this.courier.courier_name)
formData.append('courier_service',
this.courier.courier_service)
formData.append('courier_cost', this.courier.courier_cost)
formData.append('weight', this.cartWeight)
formData.append('name', this.customer.name)
formData.append('phone', this.customer.phone)
formData.append('address', this.customer.address)
formData.append('city_id', this.rajaongkir.city_id)
formData.append('province_id', this.rajaongkir.province_id)
formData.append('grand_total', this.grandTotal)

//sending data to action "storeCheckout" vuex


await this.$store.dispatch('web/checkout/storeCheckout',
formData)

//success
.then(response => {

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Checkout Berhasil Dilakukan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

//redirect route "detail invoice"


this.$router.push({
name: 'customer-invoices-show-snap_token',
params: {
snap_token: response.snap_token
}
})

})

//check validasi name

976
if (!this.customer.name) {
this.validation.name = true
}

//check validasi phone


if (!this.customer.phone) {
this.validation.phone = true
}

//check validasi address


if (!this.customer.address) {
this.validation.address = true
}
}
}

}
</script>

<style scoped>
.table-cart {
border-style: solid !important;
border-color: rgb(198, 206, 214) !important;
}

.table-font {
font-size: 14px;
}
</style>

DI atas, pertama kita menambahkan sebuah event @click di dalam button checkout yang
kita arahkan ke dalam method yang bernama checkout.

<button @click.prevent="checkout" class="btn btn-warning btn-lg btn-


block">CHECKOUT</button>

Dan di dalam method checkout pertama-tama kita membuat validasi terlebih dahulu,
apakah state customer.name, customer.phone, customer.address dan cartWeight
memiliki value.

977
//ceck apakah ada nama, phone, address dan berat produk ?
if (this.customer.name && this.customer.phone && this.customer.address
&& this.cartWeight) {

//proses checkout

//validasi

Jika semua state tersebut sudah terpenuhi, artinya semua kolom sudah memiliki value dan
disini kita akan membuat inisialisasi sebuah FormData, yang nanti akan digunakan untuk
menampung data yang didapatkan dari input form.

//define formData
let formData = new FormData();

Setelah berhasil melakukan inisialisasi, sekarang kita append beberapa data yang
didapatkan dari input form ke dalam formData.

formData.append('courier', this.courier.courier_name)
formData.append('courier_service', this.courier.courier_service)
formData.append('courier_cost', this.courier.courier_cost)
formData.append('weight', this.cartWeight)
formData.append('name', this.customer.name)
formData.append('phone', this.customer.phone)
formData.append('address', this.customer.address)
formData.append('city_id', this.rajaongkir.city_id)
formData.append('province_id', this.rajaongkir.province_id)
formData.append('grand_total', this.grandTotal)

Jika sudah, maka kita akan melakukan dispatch ke dalam action yang bernama
storeCheckout dengan mengirimkan parameter formData.

//sending data to action "storeCheckout" vuex


await this.$store.dispatch('web/checkout/storeCheckout', formData)

978
Jika proses checkout berhasil dilakukan di dalam action Vuex, maka kita akan menampilkan
pesan sukses menggunakan Sweet Alert2.

//sweet alert
this.$swal.fire({
title: 'BERHASIL!',
text: "Checkout Berhasil Dilakukan!",
icon: 'success',
showConfirmButton: false,
timer: 2000
})

Setelah itu, kita arahkan ke dalam halaman yang memiliki nama route customer-
invoices-show-snap_token dan kita sertakan parameter snap_token yang didapatkan
dari hasil response checkout di action Vuex.

//redirect route "detail invoice"


this.$router.push({
name: 'customer-invoices-show-snap_token',
params: {
snap_token: response.snap_token
}
})

Tapi, jika state customer.name, customer.phone, customer.address dan


cartWeight tidak memiliki value, maka akan melakukan set validasi.

979
//check validasi name
if (!this.customer.name) {
this.validation.name = true
}

//check validasi phone


if (!this.customer.phone) {
this.validation.phone = true
}

//check validasi address


if (!this.customer.address) {
this.validation.address = true
}

Langkah 3 - Uji Coba Checkout


Sekarang, kita akan melakukan uji coba untuk proses checkout, silahkan pilih product dan
pilih jasa pengiriman tapi tanpa mengisi nama, phone dan alamat. Maka akan mendapatkan
validasi seperti berikut ini :

980
Sekarang, silahkan isi nama, phone dan alamat dan coba lagi untuk klik button CHECKOUT,
jika berhasil kita akan di arahkan ke halaman detail order, seperti berikut ini :

981
982
983
Melakukan Pembayaran Dengan Midtrans

Setelah berhasil melakukan proses checkout, maka sekarang kita akan belajar bagaimana
cara untuk melakukan pembayaran di dalam Midtrans.

Silaahkan masuk ke dalam detail invoice order dan klik button BAYAR SEKARANG. Jika
berhasil maka kita akan mendapatkan taampilan SNAP PAY dari Midrans.

Setelah itu, silahkan klik CONTINUE, maka kita akan mendapatkan banyak pilihan metode
pembayaran, seperti :

BANK Transfer
Credit Card
Gopay
ShopeePay
BCA KlikPay
Indomaret
Alfamart
Dan lain-lain.

INFORMASI : jika menggunakan environment PRODUCTION, maka semua metode


pembayaran tidak akan dibuka semua.

Sekarang kita lanjutkan, disini kita akan mencoba melakukan simulasi pembayaran
menggunakan BCA Virtual Account. Silahkan klik ATM/Bank Transfer, maka akan
mendapatkan tampilan seperti berikut ini :

984
Klik BCA, dan setelah itu, klik SEE ACCOUNT NUMBER, maka kita akan berhasil
mendapatkan nomor Virtual Account dari Bank BCA dan klik PLEASE COMPLETE
PAYMENT.

Dan sekarang kita akan lakukan pembayaran menggunakan simulator dari Midtrans.
Silahkan buka link berikut ini https://simulator.sandbox.midtrans.com/bca/va/index, kurang
lebih tampilannya seperti ini :

985
Silahkan masukkan nomor Virtual Account yang didapatkan dari SNAP PAY ke dalam
input simulator di atas dan klik Inquire.

Dan silahkan klik Pay, maka pembayaran berhasil dilakukan.

986
Sekarang, silahkan klik button BAYAR SEKARANG yang ada di dalam detail invoice order
lagi, jika berhasil maka kita akan mendapatkan hasil seperti berikut ini :

INFORMASI PENTING : untuk status invoice akan tetap pending, karena masih di dalam
localhost, ketika website sudah di online-kan dan kita melakukan konfigurasi notifikasi
handler Midtrans, maka status invoice akan otomatis berubah menjadi success.

987
Membuat Vuex Web Slider

Pada materi ini kita akan belajar membuat state mutation dan action untuk data slider
di Vuex. Dengan menggunakan Vuex maka data akan bersifat global dan bisa digunakan
disemua component yang ada, dan tentu saja proses maintenance lebih mudah dilakukan.

Membuat Konfigurasi Vuex Web Slider


Silahkan file baru dengan nama slider.js di dalam folder store/web, kemudian
masukkan kode berikut ini di dalamnya.

//state
export const state = () => ({

//sliders
sliders: []

})

//mutations
export const mutations = {

//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {

//set value state "sliders"


state.sliders = payload
}

//actions
export const actions = {

//get sliders data


getSlidersData({ commit }) {

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/sliders" with method "GET"


this.$axios.get('/api/web/sliders')

988
//success
.then((response) => {

//commit ti mutation "SET_SLIDERS_DATA"


commit('SET_SLIDERS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Dari penambahan kode di atas, pertama kita membuat state baru dengan nama sliders,
state tersebut yang nanti akan digunakan untuk menyimpan data response dari Rest API
untuk di tampilkan di dalam component / view.

//sliders
sliders: []

Di dalam mutation, kita menambahkan 1 method baru dengan nama SET_SLIDERS_DATA,


fungsinya akan digunakan untuk mengisi nilai state sliders dengan data response yang
dikirimkan oleh action.

//mutation "SET_SLIDERS_DATA"
SET_SLIDERS_DATA(state, payload) {

//set value state "sliders"


state.sliders = payload
}

Dan di dalam action, kita juga menambahkan 1 method baru dengan nama
getSlidersData.

989
//get sliders data
getSlidersData({ commit }) {

//...
}

Di dalam method tersebut, kita melakukan http request menggunakan Axios dengan
method GET ke dalam endpoint /api/web/sliders.

//fetching Rest API "/api/web/sliders" with method "GET"


this.$axios.get('/api/web/sliders')

Jika proses http request berhasil dilakukan, maka kita akan lakukan commit ke dalam
mutation yang bernama SET_SLIDERS_DATA dengan parameter data response yang di
dapatkan dari Rest API.

//commit ti mutation "SET_SLIDERS_DATA"


commit('SET_SLIDERS_DATA', response.data.data)

990
Membuat dan Menampilkan Component Slider

Setelah berhasil membuat store di dalam Vuex, maka sekarang kita lanjutkan membuat
component slider dan menampilkan component tersebut di halaman homepage website.

Langkah 1 - Membuat Component Slider

Silahkan buat file baru dengan nama slider.vue di dalam folder components/web,
kemudian masukkan kode berikut ini di dalamnya.

<template>
<div class="container-fluid mt-custom">
<div id="mycarousel" class="carousel slide" data-ride="carousel"
data-interval="4000">
<div class="carousel-inner">

<div class="carousel-item" v-for="(slider, id) in sliders"


:class="{ active: id==0 }" :key='slider.id'>
<a :href="slider.link" target="_blank">
<img :src="slider.image" class="d-block w-100 rounded">
</a>
</div>

</div>

<a class="carousel-control-prev" href="#mycarousel" role="button"


data-slide="prev">
<div class="banner-icons"> <span class="fa fa-angle-
left"></span> </div> <span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#mycarousel" role="button"
data-slide="next">
<div class="banner-icons"> <span class="fa fa-angle-
right"></span> </div> <span class="sr-only">Next</span>
</a>
</div>
</div>
</template>

<script>
export default {

991
//hook "fetch"
async fetch() {

//fething sliders on Rest API


await this.$store.dispatch('web/slider/getSlidersData')

},

//computed
computed: {

//sliders
sliders() {
return this.$store.state.web.slider.sliders
}
}

}
</script>

<style>

</style>

Di atas, kita memanggil atau melakukan dispatch ke dalam action yang bernama
getSlidersData di dalam hook fetch, tujuannya agar proses tersebut dilakukan secara
SSR atau server side rendering.

//hook "fetch"
async fetch() {

//fething sliders on Rest API


await this.$store.dispatch('web/slider/getSlidersData')

},

Jika proses di dalam action berhasil dilakukan, maka kita akan mengambil datanya di dalam
state sliders yang ada di dalam Vuex. Dan disini kita akan menggunakan computed
properti.

992
//computed
computed: {

//sliders
sliders() {
return this.$store.state.web.slider.sliders
}
}

Dan untuk menampilkan data sliders di dalam template, kita akan menggunakn directive v-
for karena datanya bisa jadi lebih dari 1 atau banyak.

<div class="carousel-item" v-for="(slider, id) in sliders" :class="{


active: id==0 }" :key='slider.id'>

//...
</div>

Langkah 2 - Menampilkan Component Slider di Homepage


Setelah berhasil membuat component slider, sekarang kita akan lanjutkan untuk
menampilkan component tersebut di halaman homepage website.

Silahkan buka file pages/index.vue kemudian ubah kode-nya menjadi seperti berikut ini :

<template>
<div class="mr-custom mb-5">
<div class="fade-in">

<!-- slider -->


<Slider />
<!-- end slider -->

</div>
</div>
</template>

<script>
//import slider
import Slider from '@/components/web/slider.vue'

993
export default {

//register components
components: {
Slider
},

//meta
head() {
return {
title: 'MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Official Toko Online Penjualan Produk Xiaomi'
},
]
}
},

}
</script>

<style>

</style>

Dari perubahan kode di atas, pertama kita melakukan import component slider.

994
//import slider
import Slider from '@/components/web/slider.vue'

Setelah itu, kita register component tersebut agar bisa digunakan di dalam template.

//register components
components: {
Slider
},

Dan kita panggil component tersebut di dalam template, kurang lebih seperti berikut ini :

<!-- slider -->


<Slider />
<!-- end slider -->

Dan kita juga atur untuk meta tag website, seperti title, og:site, og:name, og:image
dan description. Meta tersebut berguna untuk menunjang SEO pada website yang kita
buat agar mudah dicari di mesin pencarian google.

995
//meta
head() {
return {
title: 'MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: 'images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Official Toko Online Penjualan Produk Xiaomi'
},
]
}
},

Sekarang, silahkan reload / refresh halaman homepage website atau ke URL berikut ini
http://localhost:3000/, jika berhasil maka kita akan mendapatkan hasil seperti berikut ini :

996
997
Membuat Vuex Web Product

Sekarang kita akan belajar membuat module product Vuex yang nantinya akan digunakan
untuk menampilkan data di dalam component / view. Disini kita akan menambahkan state
untuk menampung data yang di dapatkan dari Rest API, mutation untuk mengubah nilai
state dan action untuk melakukan http request ke Rest API.

Membuat Konfigurasi Vuex Web Product


Silahkan file baru dengan nama product.js di dalam folder store/web, kemudian
masukkan kode berikut ini di dalamnya.

//state
export const state = () => ({

//products
products: [],

//page
page: 1,

})

//mutations
export const mutations = {

//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {

//set value state "products"


state.products = payload
},

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

998
//actions
export const actions = {

//get products data


getProductsData({ commit, state }, payload) {

//search
let search = payload ? payload : ''

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/products" with method "GET"


this.$axios.get(`/api/web/products?q=${search}&page=${state.page}`)
//success
.then((response) => {

//commit ti mutation "SET_PRODUCTS_DATA"


commit('SET_PRODUCTS_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

Dari penambahan kode di atas, pertama kita membuat 2 state baru, yaitu products dan
page.

//products
products: [],

//page
page: 1,

Dan di dalam mutation kita juga menambahkan 2 method baru, yaitu :

SET_PRODUCTS_DATA
SET_PAGE

999
SET_PRODUCTS_DATA

Mutation ini akan digunakan untuk mengubah isi dari state products dengan data
response yang dikirimakan oleh action.

//mutation "SET_PRODUCTS_DATA"
SET_PRODUCTS_DATA(state, payload) {

//set value state "products"


state.products = payload
},

SET_PAGE

Mutation ini akan digunakan untuk mengubah isi dari state page dengan data berupa nomor
pagination yang nanti akan dikirimkan oleh component / view.

//mutation "SET_PAGE"
SET_PAGE(state, payload) {

//set value state "page"


state.page = payload
},

Di dalam action kita juga menambahkan 1 method baru dengan nama getProductsData.

//get products data


getProductsData({ commit, state }, payload) {

//...
}

Di dalam action tersebut, pertama kita membuat sebuah variable dengan nama search
dengan value payload yang nanti isinya akan didapatkan dari component / view.

1000
//search
let search = payload ? payload : ''

Setelah itu, kita melakukan http request menggunakan Axios dengan method GET ke
dalam endpoint /api/web/products?q=""&page="". Untuk parameter q akan diisi
variable search dan parameter page diambil dari state yang bernama page.

//fetching Rest API "/api/web/products" with method "GET"


this.$axios.get(`/api/web/products?q=${search}&page=${state.page}`)

Jika proses fetching di atas berhasil, maka kita akan melakukan commit ke dalam mutation
yang bernama SET_PRODUCTS_DATA dengan parameter berupa data response yang di
dapatkan dari Rest API.

//commit ti mutation "SET_PRODUCTS_DATA"


commit('SET_PRODUCTS_DATA', response.data.data)

1001
Menampilkan Products di Homepage

Setelah berhasil menambahkan module Vuex, sekarang kita akan coba menampilkan data
product di halaman homepage dari website toko online yang sedang kita buat.

Langkah 1 - Menampilkan Product di Homepage


Silahkan buka file pages/index.vue, kemudian ubah semua kode di dalamnya menjadi
seperti berikut ini :

<template>
<div class="mr-custom mb-5">
<div class="fade-in">

<!-- slider -->


<Slider />
<!-- end slider -->

<!-- product -->


<div class="container-fluid mt-4 mb-5">
<div class="mb-4">
<h5 class="text-uppercase"><i class="fa fa-shopping-bag"></i>
PRODUK TERBARU</h5>
<!-- Solid divider -->
<hr class="solid">
</div>
<div class="row">

<div class="col-md-3 mt-1 mb-4" v-for="product in products.data"


:key="product.id">
<div class="card h-100 border-0 rounded shadow-sm">
<div class="card-body">
<div class="card-img-actions">
<img :src="product.image" class="card-img img-fluid">
</div>
</div>
<div class="card-body bg-light-custom text-center rounded-
bottom">
<div class="mb-2">
<h6 class="font-weight-semibold mb-2">
<nuxt-link :to="{name: 'products-slug', params: {slug:
product.slug}}" class="text-default mb-2" data-abc="true">{{

1002
product.title }}</nuxt-link>
</h6>
<nuxt-link :to="{name: 'categories-slug', params: {slug:
product.category.slug}}" class="text-muted" data-abc="true">{{
product.category.name }}</nuxt-link>
</div>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> / <strong>{{
product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}</h5>
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>
</div>
</div>

</div>

<div class="row justify-content-center mt-4">


<div class="text-center">
<nuxt-link :to="{name: 'products'}" class="btn btn-lg btn-
warning border-0 rounded shadow-sm">LIHAT LEBIH BANYAK</nuxt-link>
</div>
</div>

</div>
<!-- end product -->

</div>
</div>
</template>

<script>
//import slider
import Slider from '@/components/web/slider.vue'

export default {

//register components

1003
components: {
Slider
},

//meta
head() {
return {
title: 'MI STORE - Distributor Xiaomi Indonesia Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: 'images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Official Toko Online Penjualan Produk Xiaomi'
},
]
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/product/getProductsData')
},

//computed
computed: {

//products
products() {
return this.$store.state.web.product.products
},
},

1004
}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita melakukan dispatch ke dalam action Vuex
yang bernama getProductsData, yang mana proses-nya ada di dalam hook asyncData,
tujuannya agar berjalan secara SSR atau server side rendering.

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/product/getProductsData')
},

Setelah itu, kita akan ambil data products yang didapatkan dari action di dalam state yang
ada di Vuex. Dan disini kita akan menggunakan computed properti untuk mengambilnya.

//computed
computed: {

//products
products() {
return this.$store.state.web.product.products
},
},

Dan sekarang, kita akan menampilkan data products tersebut di dalam template
menggunakan directive v-for, karena data yang ditampilkan bisa lebih dari 1 atau banyak.

<div class="col-md-3 mt-1 mb-4" v-for="product in products.data"


:key="product.id">

//...
</div>

1005
Di dalam block perulangan data product, kita menampilkan beberapa data, seperti harga,
harga yang telah di diskon, rating dan lain-lain.

Rp. {{ formatPrice(product.price) }}

Kode di atas, digunakan untuk menampilkan harga asli dari productnya. Dan berikut ini
digunakan untuk menampilkan harga yang telah dipotong diskon. Disini kita letakkan di
dalam method yang bernama formatPrice dan calculateDiscount, dimana kedua
method tersebut adalah helper yang sudah kita buat sebelumnya di dalam plugin mixins.

Rp. {{ formatPrice(calculateDiscount(product)) }}

Kemudian, untuk menampilkan rating, kita menggunakan kode seperti berikut ini :

<client-only>
<vue-star-rating :rating="parseFloat(product.reviews_avg_rating)"
:increment="0.5" :star-size="20" :read-only="true" :show-rating="false"
:inline="true"></vue-star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>

Di atas, ada properti :rating dimana properti tersebut yang akan digunakan untuk
menampilkan rating di dalm website, dan disini kita set value-nya menggunakan data
average rating yaitu product.reviews_avg_rating dan kita jadikan type datanya
float dengan method parseFloat.

Dan kita juga menampilkan jumlah rating yang diberikaan oleh product tersebut,
menggunakan kode seperti berkut ini :

(<strong>{{ product.reviews_count }}</strong> ulasan)

Langkah 2 - Uji Coba Menampilkan Data Product di


Homepage
Sekarang silahkan reload / refresh halaman homepage dan jika berhasil maka akan
mendapatkan hasil seperti berkut ini :

1006
1007
Menampilkan Index Data Products

Setelah berhasil menampilkan data products di halaman homepage, sekarang kita lanjutkan
untuk menampilkannya di halaman index products sendiri. Dan disini kita tidak perlu
melakukan konfigurasi Vuex lagi, karena akan menggunakan Vuex yang sudah kita buat
sebelumnya.

Langkah 1 - Menampilkan Index Data Products


Sekarang, silahkan buat folder baru dengan nama products di dalam folder pages dan di
dalam folder products tersebut silahkan buat file baru dengan nama index.vue dan
masukkan kode berikut ini di dalamnya.

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3 mt-1 mb-4" v-for="product in
products.data" :key="product.id">
<div class="card h-100 border-0 rounded shadow-sm">
<div class="card-body">
<div class="card-img-actions">
<img :src="product.image" class="card-img img-fluid">
</div>
</div>
<div class="card-body bg-light-custom text-center rounded-
bottom">
<div class="mb-2">
<h6 class="font-weight-semibold mb-2">
<nuxt-link :to="{name: 'products-slug', params: {slug:
product.slug}}" class="text-default mb-2" data-abc="true">{{
product.title }}</nuxt-link>
</h6>
<nuxt-link :to="{name: 'categories-slug', params: {slug:
product.category.slug}}" class="text-muted" data-abc="true">{{
product.category.name }}</nuxt-link>
</div>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> / <strong>{{
product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}</h5>

1008
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>
</div>
</div>

</div>
</div>
</div>
</template>

<script>
export default {

//meta
head() {
return {
title: 'Products - MI STORE - Distributor Xiaomi Indonesia
Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Products - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Jual Produk Original Xiaomi Indonesia Bergaransi
Resmi'

1009
},
]
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/product/getProductsData')
},

//computed
computed: {

//products
products() {
return this.$store.state.web.product.products
},
},

}
</script>

<style>

</style>

Di atas, pertama kita menambahkan meta tag untuk website kita, yaitu title, og:image
dan lain-lain. Tujuannya agar website kita dapat di cari di google dengan lebih mudah.

1010
//meta
head() {
return {
title: 'Products - MI STORE - Distributor Xiaomi Indonesia
Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Products - MI STORE - Distributor Xiaomi
Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Jual Produk Original Xiaomi Indonesia
Bergaransi Resmi'
},
]
}
},

Dan di dalam hook asyncData, kita melakukan dispatch ke dalam action yang ada di Vuex
dengan nama getProductsData. Dengan menggunakan asyncData, maka proses diaptch
akan berjalan secara SSR.

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/product/getProductsData')
},

Setelah itu, kita ambil data yang ada di dalam state products di Vuex menggunakan
computed properti, kurang lebih seperti berikut ini :

1011
//computed
computed: {

//products
products() {
return this.$store.state.web.product.products
},
},

Dan untuk menampilkan data product di template, tentu saja kita akan menggunakan
directive v-for, karena data yang ditampilkan lebih dari 1 atau banyak.

<div class="col-md-3 mt-1 mb-4" v-for="product in products.data"


:key="product.id">

//...
</div>

Langkah 2 - Mengaktifkan Link di Menu Navbar


Selanjutnya, kita akan mengaktifkan link di menu navbar, dimana link tersebut akan kita
arahkan ke halaman index products. Silahkan buka file components/web/header.vue,
kemudian cari kode berikut ini :

<li class="nav-item"> <a href="#" class="nav-link" data-abc="true"><i


class="fa fa-shopping-bag"></i> SEMUA PRODUK</a> </li>

Dan ubahlah menjadi seperti berikut ini :

<li class="nav-item"> <nuxt-link :to="{name: 'products'}" class="nav-


link" data-abc="true"><i class="fa fa-shopping-bag"></i> SEMUA
PRODUK</nuxt-link> </li>

Di atas, kita ubah yang semula masih menggunakan href="#" menjadi <nuxt-link dan
kita arahkan ke dalam route yang bernama products.

1012
Sekarang, silahkan klik menu di navbar SEMUA PRODUK atau bisa ke URL berikut ini
http://localhost:3000/products, dan jika berhasil maka kita akan mendapatkan hasil seperti
berikut ini :

Langkah 3 - Menambahkan Fitur Pagination


Sekarang, kita akan menambahkan fitur pagination, fitur ini akan menampilkan button
navigasi di bawah untuk berpindah-pindah ke halaman yang lain.

1013
Silahkan buka file pages/products/index.vue, kemudian ubah semua kode-nya menjadi
seperti berikut ini :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-3 mt-1 mb-4" v-for="product in
products.data" :key="product.id">
<div class="card h-100 border-0 rounded shadow-sm">
<div class="card-body">
<div class="card-img-actions">
<img :src="product.image" class="card-img img-fluid">
</div>
</div>
<div class="card-body bg-light-custom text-center rounded-
bottom">
<div class="mb-2">
<h6 class="font-weight-semibold mb-2">
<nuxt-link :to="{name: 'products-slug', params: {slug:
product.slug}}" class="text-default mb-2" data-abc="true">{{
product.title }}</nuxt-link>
</h6>
<nuxt-link :to="{name: 'categories-slug', params: {slug:
product.category.slug}}" class="text-muted" data-abc="true">{{
product.category.name }}</nuxt-link>
</div>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> / <strong>{{
product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}</h5>
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>
</div>
</div>

</div>

1014
<!-- pagination -->
<div class="row justify-content-center mt-4 mb-4">
<div class="text-center">
<b-pagination align="center"
:value="products.current_page" :total-rows="products.total"
:per-page="products.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>
</div>
</div>
</div>
</div>
</template>

<script>
export default {

//meta
head() {
return {
title: 'Products - MI STORE - Distributor Xiaomi Indonesia
Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'MI STORE - Distributor Xiaomi Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Products - MI STORE - Distributor Xiaomi Indonesia
Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Jual Produk Original Xiaomi Indonesia Bergaransi
Resmi'
},
]
}
},

1015
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/product/getProductsData')
},

//computed
computed: {

//products
products() {
return this.$store.state.web.product.products
},
},

//method
methods: {

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('web/product/SET_PAGE', page)

//dispatch on action "getProductsData"


this.$store.dispatch('web/product/getProductsData')
},
}

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita menambahkan component baru dari Boostrap
Vue, yaitu <b-paginate>. Dan di dalamnya kita berikan data yang diambil dari computed
properti yang berisi data pagination.

Ketika navigasi pagination di klik, maka akan menjalankan event @change yang mengarah
ke dalam method yang bernama changePage.

1016
<b-pagination align="center" :value="products.current_page" :total-
rows="products.total" :per-page="products.per_page" @change="changePage"
aria-controls="my-table"></b-pagination>

Dan di dalam method changePage, kurang lebih seperti berikut ini :

//method "changePage"
changePage(page) {

//commit to mutation "SET_PAGE"


this.$store.commit('web/product/SET_PAGE', page)

//dispatch on action "getProductsData"


this.$store.dispatch('web/product/getProductsData')
},

Di dalam method di atas, pertama kita melakukan commit ke dalam mutation yang bernama
SET_PAGE yang berada di dalam store/web/product.js dan untuk parameter-nya akan
berupa data angka pagination yang dikirim dari component <b-paginate>.

Dan yang kedua akan menjalankan dispatch ke dalam action yang bernama
getProductsData, dengan tujuan agar melakukan fetching ulang dengan data yang
sesuai dengan pagination.

Sekarang, silahkan reload halaman index products, jika berhasil maka kita akan
mendapatkan hasil kurang lebih seperti berikut ini :

1017
1018
Menampilkan Index Data Categories

Kita akan lanjutkan menampilkan list data category di halaman index categories. Dan
karena sebelumnya sudah membuaat konfigurasi Vuex, maka kita tinggal menggunakannya
saja di dalam component / view.

Langkah 1 - Menampilkan Index Data Categories


Silahkan buat folder baru dengan nama categories di dalam folder pages dan di dalam
folder categories tersebut silahkan buat file baru dengan nama index.vue, kemudian
masukkan kode berikut ini di dalamnya.

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row justify-content-center">
<div class="col-md-3 mb-3" v-for="category in categories"
:key="category.id">
<div class="card border-0 rounded shadow-sm">
<div class="card-body text-center">
<nuxt-link :to="{name: 'categories-slug', params:
{slug: category.slug}}">
<img :src="category.image" width="100">
<hr>
{{ category.name }}
</nuxt-link>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
export default {

//meta
head() {
return {
title: 'Semua Kategori - MI STORE - Distributor Xiaomi Indonesia
Resmi',

1019
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Semua Kategori - MI STORE - Distributor Xiaomi
Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Semua Kategori - MI STORE - Distributor Xiaomi
Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Semua Kategori dari Produk Xiaomi'
},
]
}
},

//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/category/getCategoriesData')
},

//computed
computed: {

//categories
categories() {
return this.$store.state.web.category.categories
},
},

}
</script>

<style>

</style>

1020
Dari penambahan kode di atas, pertama kita atur meta tag dari component /view ini agar
sesuai dengan data yang ditampilkan.

//meta
head() {
return {
title: 'Semua Kategori - MI STORE - Distributor Xiaomi Indonesia
Resmi',
meta: [{
hid: 'og:title',
name: 'og:title',
content: 'Semua Kategori - MI STORE - Distributor Xiaomi
Indonesia Resmi'
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: 'Semua Kategori - MI STORE - Distributor Xiaomi
Indonesia Resmi'
},
{
hid: 'og:image',
name: 'og:image',
content: '/images/logo.png'
},
{
hid: 'description',
name: 'description',
content: 'Semua Kategori dari Produk Xiaomi'
},
]
}
},

Setelah itu, kita melakukan dispatch ke dalam action Vuex yang bernama
getCategoriesData. Dan kita melakukan dispatch tersebut di dalam hook asyncData,
agar prosesnya dilakukan secara SSR atau server side rendering.

1021
//hook "asyncData"
async asyncData({ store }) {
await store.dispatch('web/category/getCategoriesData')
}

Jika dari proses dispatch action Vuex di atas berhasil dilakukan, maka kita akan mengambil
data categories yang ada di dalam state categories di Vuex. Dan disini kita menggunakan
computed properti untuk mengambilnya.

//computed
computed: {

//categories
categories() {
return this.$store.state.web.category.categories
},
},

Dan untuk menampilkan data categories di dalam template, kita menggunakan directive v-
for, karena datanya lebih dari satu.

<div class="col-md-3 mb-3" v-for="category in categories"


:key="category.id">

//...
</div>

Langkah 2 - Uji Coba Halaman Index Categories


Sekarang silahkan klik menu KATEGORI dan pilih LIHAT SEMUA KATEGORI atau bisa ke
URL berikut ini http://localhost:3000/categories, jika berhasil maka kita akan mendapatkan
hasil seperti berikut ini :

1022
1023
Menampilkan Data Product Berdasarkan Category

Sekarang kita akan lanjutkan belajar bagaimana cara menampilkan detail data category,
dimana di dalam detail data tersebut akan berisi beberapa data product yang related
dengan category tersebut. Tentu saja kita akan menambahkan beberapa konfigurasi terlebih
dahulu di dalam Vuex untuk state, mutation dan action.

Langkah 1 - Menambahkan State, Mutation dan Action

Silahkan buka file store/web/category.js, kemudian ubah semua isi yang ada di
dalamnya dengan kode berikut ini :

//state
export const state = () => ({

//categories
categories: [],

//category
category: {},

})

//mutations
export const mutations = {

//mutation "SET_CATEGORIES_DATA"
SET_CATEGORIES_DATA(state, payload) {

//set value state "categories"


state.categories = payload
},

//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {

//set value state "category"


state.category = payload
},

1024
//actions
export const actions = {

//get categories data


getCategoriesData({ commit }) {

//set promise
return new Promise((resolve, reject) => {

//fetching Rest API "/api/web/categories" with method "GET"


this.$axios.get('/api/web/categories')
//success
.then((response) => {

//commit ti mutation "SET_CATEGORIES_DATA"


commit('SET_CATEGORIES_DATA', response.data.data)

//resolve promise
resolve()
})

})

},

//get detail category


getDetailCategory({ commit }, payload) {

//set promise
return new Promise((resolve, reject) => {

//get to Rest API "/api/web/categories/:slug" with method


"GET"
this.$axios.get(`/api/web/categories/${payload}`)

//success
.then(response => {

//commit to mutation "SET_CATEGORY_DATA"


commit('SET_CATEGORY_DATA', response.data.data)

//resolve promise
resolve()

})

1025
})

},

Dari penambahan kode di atas, pertama kita tambahkan 1 state baru dengan nama
category. State tersebut nantinya akan berisi data detail category dan beberapa products
yang related dengan category tersebut.

//category
category: {},

Dan di dalam mutation kita menambahkan 1 method baru dengan nama


SET_CATEGORY_DATA, mutation tersebut akan digunakan untuk mengubah isi state
category dengan data yang dikirimkan oleh action.

//mutation "SET_CATEGORY_DATA"
SET_CATEGORY_DATA(state, payload) {

//set value state "category"


state.category = payload
},

Kemudian di dalam action, kita juga menambahkan 1 method baru, yaitu


getDetailCategory.

//get detail category


getDetailCategory({ commit }, payload) {

//...
}

Di dalam action di atas, kita akan melakukan http request menggunakan Axios dengan
method GET ke dalam endpoint /api/web/categories/:slug.

1026
//get to Rest API "/api/web/categories/:slug" with method "GET"
this.$axios.get(`/api/web/categories/${payload}`)

Jika proses fetching di atas berhasil dilakukan, maka akan melakukan commit ke dalam
mutation yang bernama SET_CATEGORY_DATA dengan parameter data response yang di
dapatkan dari Rest API.

//commit to mutation "SET_CATEGORY_DATA"


commit('SET_CATEGORY_DATA', response.data.data)

Langkah 2 - Membuat View Detail Category


Kita lanjutkan untuk membuat component / view detail category. Silahkan buat file baru
dengan nama _slug.vue di dalam folder pages/categories dan di dalam folder
tersebut, silahkan masukkan kode berikut ini :

<template>
<div class="container-fluid mt-custom">
<div class="fade-in">
<div class="row">
<div class="col-md-12">
<h3>CATEGORY : <strong>{{ category.name.toUpperCase()
}}</strong></h3>
<!-- Solid divider -->
<hr class="solid">
</div>
</div>
<div class="row">
<div class="col-md-3 mt-1 mb-4" v-for="product in
category.products" :key="product.id">
<div class="card h-100 border-0 rounded shadow-sm">
<div class="card-body">
<div class="card-img-actions">
<img :src="product.image" class="card-img img-fluid">
</div>
</div>
<div class="card-body bg-light-custom text-center rounded-
bottom">
<div class="mb-2">
<h6 class="font-weight-semibold mb-2">

1027
<nuxt-link :to="{name: 'products-slug', params: {slug:
product.slug}}" class="text-default mb-2" data-abc="true">{{
product.title }}</nuxt-link>
</h6>
<nuxt-link :to="{name: 'categories-slug', params: {slug:
product.category.slug}}" class="text-muted" data-abc="true">{{
product.category.name }}</nuxt-link>
</div>
<h6 class="mb-0 font-weight-semibold"><s class="text-
red">Rp. {{ formatPrice(product.price) }}</s> / <strong>{{
product.discount }} %</strong></h6>
<h5 class="mb-0 font-weight-semibold mt-3 text-
success">Rp. {{ formatPrice(calculateDiscount(product)) }}</h5>
<hr>
<client-only>
<vue-star-rating
:rating="parseFloat(product.reviews_avg_rating)" :increment="0.5" :star-
size="20" :read-only="true" :show-rating="false" :inline="true"></vue-
star-rating>
(<strong>{{ product.reviews_count }}</strong> ulasan)
</client-only>
</div>
</div>
</div>

</div>
</div>
</div>
</template>

<script>
export default {

//meta
head() {
return {
title: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`,
meta: [{
hid: 'og:title',
name: 'og:title',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
{
hid: 'og:site_name',

1028
name: 'og:site_name',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
{
hid: 'og:image',
name: 'og:image',
content: `${this.category.image}`
},
{
hid: 'description',
name: 'description',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
]
}
},

//hook "asyncData"
async asyncData({ store , route}) {
await store.dispatch('web/category/getDetailCategory',
route.params.slug)
},

//computed
computed: {

//category
category() {
return this.$store.state.web.category.category
},
},

}
</script>

<style>

</style>

Dari penambahan kode di atas, pertama kita atur meta tag secara dinamis dengan
menambil data dari computed properti.

1029
//meta
head() {
return {
title: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`,
meta: [{
hid: 'og:title',
name: 'og:title',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
{
hid: 'og:site_name',
name: 'og:site_name',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
{
hid: 'og:image',
name: 'og:image',
content: `${this.category.image}`
},
{
hid: 'description',
name: 'description',
content: `Category : ${this.category.name} - MI STORE -
Distributor Xiaomi Indonesia Resmi`
},
]
}
},

Dan di dalam hook asyncData kita melakukan dispatch ke dalam action Vuex yang
bernama getDetailCategory dengan menambahkan parameter slug yang di dapatkan
dari URL browser.

//hook "asyncData"
async asyncData({ store , route}) {
await store.dispatch('web/category/getDetailCategory',
route.params.slug)
},

1030
Dan kita akan mengambil data dari state category yang ada di dalam Vuex menggunakan
computed properti, kurang lebih seperti berikut ini :

//computed
computed: {

//category
category() {
return this.$store.state.web.category.category
},
},

Di dalam template, kita menampilkan nama category dengan kode seperti berikut ini :

<h3>CATEGORY : <strong>{{ category.name.toUpperCase() }}</strong></h3>

Dan untuk menampilkan data products yang related dengan category, maka kita perlu
menggunakan perulangan v-for, karena datanya akan lebih dari 1.

<div class="col-md-3 mt-1 mb-4" v-for="product in category.products"


:key="product.id">

//...
</div>

Langkah 3 - Uji Coba Detail Category


Silahkan klik salah satu data category yang ada dan pastikan di dalam category tersebut
terdapat products. Jika berhasil maka kurang lebih hasilnya seperti berikut ini :

1031
1032
Deployment Project Laravel di cPanel (Shared Hosting)

Setelah menyelesaikan semua materi di dalam ebook, baik dari sisi backend (Laravel) dan
frontend (Nuxt.js), maka sekarang kita akan lanjutkan lagi belajar bagaimana cara
melakukan deployment atau proses meng-onlinekan project yang sudah kita buat agar
dapat diakses secara global di internet.

Disini saya akan berikan rekomendasi Hosting atau cPanel yang sudah support Laravel dan
akan mendapatkan potongan harga dengan memasukkan kode kupon. Dan untuk fitur-fitur
dari cPanel yang direkomendasikan ini kurang lebih seperti berikut :

Git
SSH
Composer
Terminal
Dan lain-lain.
Gratis SSL (https://)

Kupon Diskon Hosting di RiauCyberSoluion.net / HelloWorldHost.com

Link pembelian Hosting : https://riaucybersolution.net/cart.php?gid=58


Kupon/Voucher : SantriKodingFullstack

❗️ CATATAN : Minimal pembelian Hosting 3 bulan

Disini saya akan menggunakan domain https://appdev.my.id dan untuk Laravel atau
backend-nya nanti akan kita letakkan di dalam subdomain, katakanlah di subdomain berikut
ini https://ecommerce.appdev.my.id. Untuk nama subdomain ini sifatnya bebas dan silahkan
disesuaikan dengan keinginan masing-masing.

Langkah 1 - Membuat Subdomain


Sampai disini saya kira teman-teman semuanya sudah mempunyai domain dan hosting
untuk proses pembelajaran, pertama kita akan belajar bagaimana membuat subdomain
baru di dalam cPanel..

Silahkan buka cPanel dengan mengetikan http://namadomain.com/cpanel, jika berhasil


maka akan muncul halaman login dari cPanel kurang lebih seperti berikut ini :

CATATAN! : silahkan ganti namadomain.com sesuai dengan domain yang teman-teman


miliki.

1033
Jika berhasil melakukan otentikasi, maka akan masuk ke dalam halaman dashboard dari
cPanel, kurang lebih seperti berikut ini :

Setelah itu, silahkan klik menu Subdomains dan jika berhasil akan membuka halaman
seperti berikut ini :

1034
Sekarang, kita akan membuat subdomain baru yang nantinya akan kita gunakan untuk
menaruh project Laravel. Silahkan buat subdomain sesuai dengan keinginan masing-masing.
Dalam contoh kali ini saya membuat seperti berikut ini :

Di atas saya berikan contoh untuk nama subdomain-nya adalah ecommerce dan untuk
domain yang digunakan adalah appdev.my.id. Maka nanti hasilnya seperti berikut ini :
https://ecommerce.appdev.my.id.

Langkah 2 - Export Database di Localhost


Sekarang kita akan melakukan export database yang ada di lokal komputer kita. Silahkan
buka http://localhost/phpmyadmin, kemudian pilih database yang akan di export.

1035
Langkah 3 - Buat dan Import Database di cPanel
Setelah di lokal database kita berhasil di export, sekarang kita lanjutkan untuk import
database kita di cPanel. Silahkan klik menu MySQL Database, seperti pada gambar di
bawah ini :

1036
Silahkan buat nama database terlebih dahulu, di atas saya contohkan untuk nama database-
nya adalah appdev_ecommerce, setelah itu klik create database.

Setelah database sudah berhasil terbuat, sekarang kita lanjutkan untuk membuat user dan
password untuk database kita. Masih di menu yang sama di MySQL Database, akan tetapi
kita secroll kebawah dan kita akan temukan inputan untuk membuat user dan password,
kurang lebih seperti ini :

Di atas saya membuat user-nya sama dengan nama ecommerce, dan ini sesuai keinginan
kita, dengan syarat nanti harus bisa mengingatnya ya.

Kemudian untuk password silahkan diisi dengan password yang kita inginkan. Setelah itu
klik create user dan tunggu prosesnya sampai selesai.

Kemudian sekarang kita lanjutkan untuk assign untuk user dan database. masih di menu
yang sama, silahkan scroll kebawah dan kurang lebih seperti ini :

Di atas silahkan pilih user dan database yang sudah kita buat sebelumnya, maka kurang
lebih hasilnya seperti di atas. Kemudian klik add. Kemudian akan muncul Manage User
Privileges, Silahkan centang semua dan klik Make Changes.

1037
Sekarang kita lanjutkan untuk proses import database kita di cPanel, silahkan kembali ke
halaman awal cPanel, kemudian masuk ke menu phpmyadmin. Kurang lebih seperti berikut
ini :

Tunggu beberapa saat, kita akan di arahkan ke halaman phpmyadmin milik cPanel dan kita
bisa lakukan proses import database yang sudah kita export sebelumnya.

1038
Langkah 4 - Upload Project Laravel
Setelah kita berhasil import database di cPanel, sekarang kita lanjutkan untuk upload
project website-nya, langsung saja kita mulai. Silahkan klik kanan pada project Laravel dan
compress menjadi format .zip, kurang lebih seperti berikut ini :

Setelah project sudah berhasil menjadi zip file, sekarang kita kembali ke halaman cPanel
dan klik menu File Manager, kurang lebih seperti berikut ini tampilannya.

1039
Di atas, silahkan upload project dengan format zip. Jika sudah berhasil terupload, kurang
lebih seperti berikut ini :

1040
Langkah 5 - Konfigurasi Project Laravel di cPanel
Setelah file project dengan format zip sudah terupload, sekarang kita akan lakukan
konfigurasinya. Langsung saja kita mulai. Silahkan extract file zip-nya kurang lebih seperti
beriku ini :

1041
Di atas kita akan extract projectnya dengan nama backend-ecommerce kemudian silahkan
tunggu proses extracting sampai selesai. Kemudian klik reload di filemanager, maka kita
sudah mendapatkan sebuah folder dengan nama backend-ecommerce kemudian buka
folder tersebut maka kita akan mendapatkan folder lagi dengan nama backend-
ecommerce. Kemudian masuk ke folder tersebut dan itulah project kita, hasilnya seperti
berikut ini :

Kemudian kita lanjutkan untuk menampilkan hidden file, secara default cPanel meng-
hidden file dengan awalan dot (.) oleh karena itu file .htaccess, .env, dan lain-lain
semua di hidden.

1042
Untuk menampilkannya kita bisa seperti berikut ini :

Disini kita akan melakukan move data 2 kali, silahkan masuk ke folder backend-
ecommerce/backend-ecommerce/public kemudian select all file dan folder
kemudian pilih move. Kurang lebih seperti berikut ini :

1043
Di atas kita move ke dalam folder /ecommerce.appdev.my.id.

Setelah itu kita juga akan move lagi semua file yang ada di dalam folder backend-
ecommerce ke dalam folder backend-ecommerce yang diluar.

1044
Sekarang, kita lanjutkan untuk merubah beberapa kode di dalam file index.php di folder
ecommerce.appdev.my.id, silahkan masuk ke ecommerce.appdev.my.id kemudian
pilih file index.php klik kanan dan klik edit. Kurang lebih seperti berikut ini :

1045
Ubah di bagian autoload.php menjadi seperti berikut ini :

require __DIR__.'/../backend-ecommerce/vendor/autoload.php’;

Kemudian ubah juga bagian app.php menjadi seperti beirkut ini :

$app = require_once __DIR__.'/../backend-ecommerce/bootstrap/app.php’;

Kemudian kita juga akan merubah beberapa kode lagi untuk App Service Providers,
silahkan ikuti gambar di bawah ini :

1046
Dari gambar di atas, pada bagian function boot, silahkan tambahkan kode berikut ini :

$this->app->bind('path.public', function() {
return base_path().'/../ecommerce.appdev.my.id';
});

Jadi di atas kita set untuk base path dari project kita adalah folder
ecommerce.appdev.my.id. Setelah itu kita akan lanjutkan untuk konfigurasi file .env
untuk mengatur koneksi database kita, silahkan buka file .env di dalam folder backend-

1047
ecommerce. Kemudian klik kanan dan edit.

Langkah 6 - Menjalankan Storage:link Laravel


Sebelum kita menjalankan storage:link atau symlink, kita harus hapus symlink yang
lama bawaan dari localhost terlebih dahulu. Silahkan masuk ke folder
ecommerce.appdev.my.idl. kemudian klik kanan pada folder storage dan
delete.

1048
Sekarang kita lanjutkan untuk menjalankan php artisan storage:link di cPanel,
silahkan buka menu terminal di halaman utama cPanel, kurang lebih seperti berikut ini :

1049
Sebelum menjalankan perintah berikut ini, silahkan perhatikan gambar di atas, silahkan
ganti appdev dengan username cPanel kalian dan ganti ecommerce.appdev.my.id
dengan subdomain atau domain kalain. Setelah itu jalankan perintah berikut ini di dalam
terminal :

ln -s /home/username_cpanel/backend-ecommerce/storage/app/public
/home/username_cpanel/subdomain_kalian.com/storage

Langkah 7 - Mencoba Project


Sekarang jika kita coba jalankan project admin dari donasi onlin-nya, dalam studi kasus kali
ini saya menggunakan nama domain https://ecommerce.appdev.my.id/ dan kurang lebih
hasilnya seperti berikut ini :

1050
Terus kita coba akses salah satu Rest API-nya di link berikut ini
https://ecommerce.appdev.my.id/api/web/products, jika berhasil maka kurang lebih hasilnya
seperti berikut ini :

1051
Deploy Project Nuxt.js di Vercel (SSR)

Pada tahap kali ini kita semua akan belajar bagaimana cara melakukan deployment project
Nuxt.js di dalam Vercel. Jadi apa itu Vercel ?

Vercel merupakan salah satu layanan build tools yang memiliki fitur CI/CD atau Continue
Integration dan Continue Development. Dengan Vercel kita dapat men-deploy website
static ataupun SSR dengan Git host yang sudah terkenal seperti Github, GitLab dan
Bitbucket.

Konsepnya nanti, kita akan membuat repository di salah satu Git host di atas, dalam praktek
ini yang akan kita gunakan adalah GitHub dan kita akan menggunakan jenis repository
private agar source codenya tidak bisa dilihat oleh orang lain.

Setelah source code berhasil di push atau upload ke GitHub selanjutnya kita akan
sambungkan dengan Vercel untuk proses deployment secara otomatis. Ketika kita merubah
source code dan kita push ulang di GitHub maka Vercel juga akan melakukan proses
CI/CD dari perubahan yang dilakukan di dalam repository.

Langkah 1 - Membuat Repository di GitHub


Silahkan daftar atau login di situs resmi GitHub yaitu : https://github.com kemudian
silahkan buat repository baru https://github.com/new dengan jenis private, seperti gambar
berikut ini :

1052
Dari konfigurasi di atas, silahkan disesuaikan sendiri untuk nama dan deskripsi dari project
Nuxt.js kita di GitHub. Setelah berhasil membuat repository langkah selanjutnya kita akan
upload project Nuxt.js kita di GitHub.

Langkah 2 - Konfigurasi Nuxt.js


Sebelum kita lakukan push atau upload project Nuxt.js ke dalam GitHub, maka kita akan
lakukan beberapa konfigurasi terlebih dahulu.

Konfigurasi baseURL

Silahkan buka file nuxt.config.js, kemudian cari kode berikut ini :

1053
axios: {
baseURL: 'http://localhost:8000'
},

Kemudian ubah kode-nya menjadi seperti berikut ini :

axios: {
baseURL: 'https://ecommerce.appdev.my.id'
},

CATATAN ! : untuk domain https://ecommerce.appdev.my.id silahkan disesuaikan


denagn domain yang kalian gunakan untuk upload project Laravel.

Konfigurasi Vercel Builder

Silahkan buat file baru di dalam root project Nuxt.js dengan nama vercel.json, dimana
file ini sejejar dengan file nuxt.config.js, package.json dan lain-lain. Setelah itu,
silahkan masukkan kode berikut ini di dalam file tersebut :

{
"version": 2,
"builds": [
{
"src": "nuxt.config.js",
"use": "@nuxtjs/vercel-builder"
}
]
}

1054
Setelah itu, buat lagi file baru dengan nama now.json dan masukkan kode berikut ini :

{
"version": 2,
"env": {
"ON_VERCEL": "true"
},
"builds": [
{
"src": "api/**/*.js",
"use": "@now/node"
},
{
"src": "nuxt.config.js",
"use": "@nuxtjs/now-builder"
}
],
"routes": [
{ "src": "/api/(.*)", "dest": "api/index.js" },
{ "src": "/api", "dest": "api/index.js" },
{ "src": "/(.*)", "dest": "$1" }
]
}

1055
Delete File package-lock.json

Silahkan hapus file yang bernama package-lock.json di dalam root project Nuxt.js.

Langkah 3 - Upload Project ke GitHub


Sekarang kita lanjutkan untuk proses upload atau push project Nuxt.js ke dalam GitHub,
silahkan ikuti perintah-perintah berikut ini dan pastikan menjalankannya di dalam project
Nuxt.js.

git init

Perintah di atas digunakan untuk menginisialisasi git di dalam project kita.

git add .

Perintah di atas digunakan untuk menambahkan semua file dan folder ke dalam git.

git commit -m "initial commit"

Perintah di atas digunakan untuk memberikan komentar dari file-file dan folder yang kita

1056
tambahkan di atas, kita bisa mengganti kata initial commit dengan sesuai keinginan.

git remote add origin https://github.com/maulayyacyber/nuxt-


ecommerce.git

Perintah di atas digunakan untuk menambahkan alamat origin dengan repository yang
sudah kita buat sebelumnya. Di atas contohnya untuk alamat repository saya adalah
https://github.com/maulayyacyber/nuxt-ecommerce.git, maka silahkan
disesuaikan dengan alamat dari repository masing-masing.

git push origin master

Perintah di atas digunakan untuk melakukan push/upload semua file project ke dalam
repository GitHub. Silahkan tunggu proses uploadnya sampai selesai.

Jika semua file project sudah ter-upload, maka hasilnya kurang lebih seperti berikut ini :

Langkah 4 - Deployment Dengan Vercel


Sekarang kita lanjutkan untuk proses deployment di Vercel, silahkan buka situs resi Vercel
di https://vercel.com/ lebih seperti berikut ini :

1057
Setelah itu, silahkan LOGIN atau SIGN UP di dalam website Vercel, disini kita bisa
melakukan proses SIGN UP menggunakan GitHub agar mempermudah proses deployment-
nya.

Setelah berhasil mendaftar, maka kita akan mendapatkan tampilan kurang lebih seperti
berikut ini :

1058
Dari gambar di atas, kita akan mendapatkan list respository yang ada di dalam akun GitHub
kita, sekarang kita pilih respository dari project Nuxt.js yang sebelumnya sudah kita upload.

Silahkan klik IMPORT, maka kurang lebih hasilnya seperti berikut ini :

Di atas, silahkan klik Skip. Karena kita akan menggunakan tipe akun personal, bukan tim.
Setelah itu, klik Deploy.

1059
Proses deployment berhasil dilakukan dan website kita sudah bisa diakses secara global di
internet.

Langkah 5 - Mencoba Hasil Deployment


Di atas, untuk alamat domain saya ubah menjadi https://nuxt-ecommerce-ssr.vercel.app/,
silahkan bisa disesuaikan nama domain tersebut di menu setting domain. Kita juga bisa
mengubahnya dengan domain TLD, seperti .com, .id, .net dan lain-lain.

1060
1061
Mencoba Fitur PWA

Jika kita perhatikan di dalam URL browser kita mendapti sebuah icon untuk melakukan
installasi website kita. Kurang lebih seperti berikut ini :

Jika kita Install, maka website kita bisa terbuka seolah-olah seperti aplikasi Desktop dan
jika dibuka di dalam HP, maka seolah-olah seperti aplikasi yang diunduh dari Play Store atau
App Store.

Dan jika kita cek di dalam developer tools browser, kurang lebih hasilnya seperti berikut ini :

1062
Konfigurasi Notifikasi Handler Midtrans

Sekarang, kita akan lanjutkan untuk melakukan konfigurasi notifikasi handler di dalam
Midtrans, notifikasi ini digunakan untuk menerima pembaruan status dari data order yang
dilakukan ke dalam Midtrans dan meng-update status tersebut di dalam database kita.

Silahkan login ke Midtrans, dan jangan lupa pilih environment menggunakan Sandbox,
karena kita masih dalam tahap pengembangan.

Selanjutnya, silahkan klik menu SETTINGS dan pilih KONFIGURATION.

1063
Di atas, kita harus mengatur 6 endpoint, yang digunakan untuk menerima notifikasi dari
Midtrans dan action redirect dari Midtrans.

Silahkan isi konfigurasi di atas kurang lebih seperti berikut ini :

attribute value keterangan

Alamat
dimana
Midtrans akan
Payment
mengirimkan
Notification https://ecommerce.appdev.my.id/api/web/notification
notifikasi
URL*
melalui
request HTTP
Post.

Alamat
dimana
Midtrans akan
Recurring mengirimkan
Notification https://ecommerce.appdev.my.id/api/web/notification notifikasi
URL* berulang
melalui
permintaan
HTTP Post.

1064
attribute value keterangan

Alamat
dimana
Midtrans akan
Pay
mengirimkan
Account
https://ecommerce.appdev.my.id/api/web/notification notifikasi
Notification
berulang
URL*
melalui
permintaan
HTTP Post.

Halamanan
Finish
ketika
Redirect https://nuxt-ecommerce-ssr.vercel.app/customer/invoices
pembayaran
URL*
berhasil

Halamanan
Unfinish ketika
Redirect https://nuxt-ecommerce-ssr.vercel.app/customer/invoices pembayaran
URL* belum di
proses

Halamanan
Error
ketika
Redirect https://nuxt-ecommerce-ssr.vercel.app/customer/invoices
pembayaran
URL*
error/gagal
CATATAN! : Silahkan ganti nama domain dengan yang kalian miliki.

Sekarang, jika kita melakukan pembelian di dalam website dan melakukan pembayaran,
maka di database website kita juga akan ikut terupdate sesuai dengan status yang
dikirimkan oleh Midtrans dan tentu saja stock product juga akan berkurang sesuai dengan
quantity di dalam data order.

1065
Source Code

Untuk link unduh project silahkan bias mengunjungi link berikut ini :

Laravel (BackEnd):
https://drive.google.com/file/d/1njZG3mpoLhtR_77m4dbR7DwU1KDWUBlX/view?usp=s
haring
Nuxt.js (FrontEnd):
https://drive.google.com/file/d/1PLWdIL_Yz9XCzKIWPdvsbSll0pqGHR5o/view?usp=shari
ng

PERINGATAN ! : jangan gunakan source code ini apabila belum mengikuti materi sampai
selesai.

1066
Penutup

Terima kasih saya sampaikan kepada teman-teman semuanya yang sudah belajar melalui
buku yang sangat-sangat jauh dari kata sempurna. Tak lupa saya memohon maaf atas
segala kesalahan penulisan, code maupun gambar yang saya sajikan dalam tulisan ini.

Semoga dengan selesainya mempelajari semua isi yang ada di dalam buku ini dapat
memberikan gambaran bagaiaman cara menjadi seorang FullStack Web Developer yang
bisa diandalkan di era digital yang berkembang dengan sangat pesat ini.

Saya berharap juga kepada teman-teman yang sudah membeli buku ini untuk tidak
membagikan kepada orang lain secara GRATIS. Karena mempelajari atau menggunakan
sesuatu yang sifatnya bajakan tidak akan membuat ilmu dan rezeki menjadi berkah.

Jangan lupa nantikan seri buku yang lainnya dari SantriKoding.com dan teruslah belajar
untuk menambah skill dan jangan mudah puas dengan apa yang sudah kita raih saat ini.

"Kebutuhan manusia terhadap ilmu jauh lebih besar daripada kebutuhannya terhadap
makan dan minum karena makanan dan minuman hanya dibutuhkan sekali atau dua kali
saja dalam sehari, sedang ilmu, dibutuhkan dalam setiap embusan napas."

1067

Anda mungkin juga menyukai