Membuat Fitur Pencarian dengan Vuejs dan Google Custom Search API
Ngeblog menggunakan static site generator (SSG) seperti Hugo memang ada kelebihan dan kekurangannya.
Salah satu kekurangannya:
“SSG tidak menggunakan database untuk menyimpan kontennya”
Namun ini justru bisa jadi kelebihan, karena dapat mengurangi biaya dan tentunya lebih aman.
Tapi untuk membuat fitur pencarian, kita harus menyediakan back-end sendiri atau menggunakan back-end pihak ketiga.
Ada beberapa back-end yang bisa digunakan untuk membuat pencarian di SSG:
- Menggunakan Algolia
- Menggunakan Duckduckgo
- menggunakan lunrjs
- menggunakan typesense.org
- Menggunakan Google CSE (Custom Search Engine)
- dll.
Silahkan pilih sendiri sesuai kebutuhan…
Saya memilih Google CSE, karena:
Semua konten Petanikode sudah terindek di Google
Tidak perlu membuat atau submit indeks manual ke back-end seperti Algolia, karena Google Bot akan yang melakukannya
Cukup Cepat
Hasil pencariannya lebih akurat meskipun ada typo
Sebelumnya Petanikode memang sudah menggunakan CSE dengan metode embed form.
Kekurangan dari metode ini… hasil pencariannya tidak bisa kita kustom sendiri.
Saya mengharapkan, hasil pencariannya bisa dikasi CSS sendiri agar mengikuti desain pada Petanikode.
Akhirnya saya menemukan cara dengan menggunakan API.
Langhka-langhak membuatnya seperti ini:
- Membuat CSE baru
- Membuat API Key untuk akeses API
- Implementasi API ke website kita
Untuk implementasi, kita akan menggunakan Vuejs. Karena menrurut saya lebih sederhana dibandingkan menggunakan JQuery.
Oke, kalau begitu, mari kita mulai…
1. Membuat Custom Search Engine (CSE)
Silahkan buka: https://cse.google.com/cse/create/new untuk membuat CSE baru.
Isi nama domain dan pilih bahasanya, lalu klik create.
Setelah itu, masuk ke menu Edit Search Engine->Setup. Lalu, klik tombol Search Engine ID untuk mendapatkan ID CSE.
Simpan ID tersebut, karena akan kita gunakan untuk mengaksesnya dari API.
2. Membuat API Key
Setelah kita membuat custom search engine, selanjutnya kita membuatuhkan API Key untuk mengaksesnya melalui melalui API.
API Key dapat dibuat melalui Console Developer Google.
Sebelum membuat API Key, kita harus sudah punya project aplikasi di dalam Console Developer Google.
Silahkan buat project baru, buka link ini https://console.developers.google.com.
Klik tombol pilih bila sudah membuat atau klik tombol buat untuk membuat project baru.
Kita buat saja yang baru…
Isi nama project-nya, lalu klik BUAT.
Berikutnya silahkan masuk ke Dashboard, lalu klik Aktifkan API dan Layanan.
Pada kotak pencarian, cari dengan kata kunci “Custom Search”.
Berikutnya, silahkan aktifkan layanan Custom Search API. Klik tombol Aktifkan.
Setelah itu, buat kerdensial untuk mengakses layanan ini. Klik tombol Buat Kredensial.
Berikutnya klik tombol Kredensial apa yang saya butuhkan?
Maka akan muncul API Key yang kita butuhkan.
Simpan baik-baik API Key ini, karena akan kita butuhkan untuk mengakses layanan CSE.
3. Mengakses CSE melalui API
Ada dua macam URL yang digunakan untuk mengakses CSE:
- https://www.googleapis.com/customsearch/v1 (punya batasan 10.000 query/hari)
- https://www.googleapis.com/customsearch/v1/siterestrict (tidak punya batasan)
Apa bedanya?
URL yang nomer 1 bisa di dipakai di mana saja, sedangkan yang no 2 hanya bisa dipakai di situs spesifik saja.
Lebih detailnya kamu bisa lihat tabel ini:
Kemudian untuk parameter yang wajib dicantumkan di URL-nya
ada key
, cx
dan q
(query).
Sehingga formatnya akan seperti ini:
https://www.googleapis.com/customsearch/v1?key=API_KEY&cx=CSE_ID&q=KEYWORDS
Perhatikan alamat URL tersebut, di sana terdapat beberapa parameter yang dibutuhkan:
API_KEY
adalah API Key yang sudah kita buat;CSE_ID
adalah ID dari custom search yang sudah dbuat;KEYWORDS
adalah kata kunci pencarian.
Mari kita coba…
Saya akan menggunakan parameter seperti ini:
API_KEY=AIzaSyBAEEvhRcBk8FVOmxv6jJON2VhMUpqQgI8
CSE_ID=000680021646118888977:u60wgdclz_0
KEYWORDS=nodejs
Maka kita bisa susun URL-nya menjadi seperti ini:
https://www.googleapis.com/customsearch/v1?key=AIzaSyBAEEvhRcBk8FVOmxv6jJON2VhMUpqQgI8&cx=000680021646118888977:u60wgdclz_0&q=nodejs
Coba buka URL tersebut melalui browser…
Kita akan mendapatkan data hasil pencarian dalam bentuk JSON. Data inilah yang akan kita tampilkan ke pengguna dengan bantuan Vuejs.
4. Membuat Fitur Pencarian dengan Vuejs
Pertama-tama silahkan buat file HTMLnya.
Kode HTML:
<div id="search-app" class="container">
<!-- Search form section -->
<section class="my-5 row justify-content-center">
<div class="col-md-6">
<div class="input-group mb-3">
<input type="text" v-model="q" class="form-control" placeholder="Kata kunci..." aria-label="Recipient's username" aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-secondary" type="button" v-on:click="doSearch()">Cari</button>
</div>
</div>
</div>
</section>
<!-- Search result -->
<section class="row mb-5">
<post-card
v-for="post in searchResult.items"
v-bind:post="post">
</post-card>
</section>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.1/vue-router.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="/js/search.js"></script>
Ada lima file Javascript yang kita butuhkan di dalam fitur pencarian ini:
vuejs
script inti dari vuejs;vue-router.min.js
untuk routing, nanti kita akan pakai untuk mengambil query string dari URL;axios.min.js
untuk membuat HTTP Request ke API Google;lodash.min.js
kita butuhkan di dalam scriptsearch.js
;search.js
adalah script aplikasi dari fitur pencarian yang akan kita buat.
Lalu di dalam kode HTML di atas, kita membagi menjadi dua section:
- Section pertama untuk menampilkan form untuk pencarian
- Section kedua untuk menampilkan hasil pencarian.
Pada hasil pencarian kita menggunakan komponen <post-card>
, nanti kita akan buat komponennya.
Jika kita lihat di browser, kira-kira tampilannya akan seperti ini:
Oya, kode HTML di atas menggunakan Bootstrap 4.
Berikutnya, kita akan membuat script pencariannya. Silahkan buat file search.js
dengan isi sebagai berikut:
// Membuat Component
Vue.component('post-card', {
props: ['post'],
template: '<div class="col-md-6 col-lg-4 mb-4 d-flex">'+
'<div class="card card-shadow">' +
'<a :href="post.link">' +
'<img class="card-img-top" :src="post.pagemap.cse_image[0].src"/>'+
'</a>'+
'<div class="card-body">'+
'<h5 class="card-title">'+
'<a class="text-dark" :href="post.link">{{ post.title }}</a>' +
'</h5>' +
'</div>' +
'</div>' +
'</div>'
})
// Membuat Router
var router = new VueRouter({
mode: 'history',
routes: []
});
// Aplikasi Fitur Pencarian
var app = new Vue({
router,
el: '#search-app',
data: {
q: "",
searchResult: ""
},
watch: {
q: function () {
// console.log(this.q);
this.debouncedDoSearch()
}
},
created: function () {
// _.debounce is a function provided by lodash to limit how
// often a particularly expensive operation can be run.
// In this case, we want to limit how often we access
// yesno.wtf/api, waiting until the user has completely
// finished typing before making the ajax request. To learn
// more about the _.debounce function (and its cousin
// _.throttle), visit: https://lodash.com/docs#debounce
this.debouncedDoSearch = _.debounce(this.doSearch, 500);
this.q = this.$route.query.q;
// console.log(this.q);
},
methods: {
doSearch: function(){
var app = this;
if (this.q != undefined && this.q !== "") {
let API_KEY = "AIzaSyBAEEvhRcBk8FVOmxv6jJON2VhMUpqQgI8";
let CSE_ID = "000680021646118888977:u60wgdclz_0";
axios.get('https://www.googleapis.com/customsearch/v1?key='+API_KEY+'&cx='+CSE_ID+'&q=' + this.q)
.then(function (response) {
app.searchResult = response.data;
console.log(app.searchResult);
})
.catch(function (error) {
console.log(error);
})
}
}
//console.log(this.q);
}
});
Silahkan ganti nilai API_KEY
dan CSE_ID
dengan milikmu.
Penjelsannya:
Pertama-tama, kita mulai dari komponen dulu:
// Membuat Component
Vue.component('post-card', {
props: ['post'],
template: '<div class="col-md-6 col-lg-4 mb-4 d-flex">'+
'<div class="card card-shadow">' +
'<a :href="post.link">' +
'<img class="card-img-top" :src="post.pagemap.cse_image[0].src"/>'+
'</a>'+
'<div class="card-body">'+
'<h5 class="card-title">'+
'<a class="text-dark" :href="post.link">{{ post.title }}</a>' +
'</h5>' +
'</div>' +
'</div>' +
'</div>'
})
Ini adalah komponen yang akan kita gunakan untuk menampilkan hasil pencarian.
Perhatikan, di sana ada props: ['post']
, artinya nanti saat menggunakan komponen ini…
kita harus membarikan sebuah objek post
seperti ini:
Berikutnya membuat router…
// Membuat Router
var router = new VueRouter({
mode: 'history',
routes: []
});
Router ini kita perlukan untuk mengambil nilai dari query string pada URL.
Contoh Query string:
https://www.petanikode.com/search/?q=katakunci
Di sana ada query string q=katakunci
, untuk mengambil nilai ini,
kita bisa memanfaatkan router seperti ini:
this.$route.query.q
Selanjutnya di dalam aplikasi pencarian, kita menyediakan dua data:
q
adalah sebuah string yang berisi kata kunci pencarian;searchResult
adalah sebuah objek JSON dari hasil yang didapatkan dari API.
Lalu methode watch()
berfungsi untuk memantau perubahan dari data q
.
watch: {
q: function () {
// console.log(this.q);
this.debouncedDoSearch()
}
}
Jika nilai q
berubah, maka kita eksekusi fungsi this.debounceDoSearch()
.
Fungsi ini sebenarnya untuk menentukan jeda, kapan keyboard akan berhenti diketik.
Misalnya dalam 500 milidetik.
Jika kita tidak melakukan ini, maka disetiap ketikan keyboard… Program ini akan mengirim HTTP Request sebanyak huruf yang diketik.
Hal ini tentu sebuah pemborosan. 😄
Karena itu, kita berikan jeda selama 500 milidetik untuk melakukan HTTP Request ke API setelah keyboard ditekan.
Terakhir dan yang paling penting:
Mengeksekusi method doSearch()
.
Fungsi ini akan melakukan HTTP Request dengan axios
ke URL atau Endpoint API.
Lalu hasilnya akan disimpan dalam data searchResult
.
doSearch: function(){
var app = this;
if (this.q != undefined && this.q !== "") {
let API_KEY = "AIzaSyBAEEvhRcBk8FVOmxv6jJON2VhMUpqQgI8";
let CSE_ID = "000680021646118888977:u60wgdclz_0";
axios.get('https://www.googleapis.com/customsearch/v1?key='+API_KEY+'&cx='+CSE_ID+'&q=' + this.q)
.then(function (response) {
app.searchResult = response.data;
console.log(app.searchResult);
})
.catch(function (error) {
console.log(error);
})
}
}
Jangan lupa untuk mengganti nilai API_KEY
dan CSE_ID
dengan milikmu. Karena saya yakin, menggunakan API Key
dan CSE_ID di atas tidak akan bisa.
Sekarang silahkan dicoba…
Inilah hasilnya, pencarian dengan katakunci nodejs
:
5. Proteksi API Key CSE
Sebenarnya, menulis kerdensial seperti password dan API Key di dalam kode program sangat tidak dianjurkan, karena bisa dicuri orang.
Lah, terus bagaimana donk?
Sedangkan program kita berjalan di atas browser dan source code-nya dapat dilihat oleh semua orang.
Solusinya:
Google telah menydiakan limitasi (batasan) untuk penggunaan API Key. Misal, API Key hanya bisa digunakan pada domain dan alamat IP tertentu saja.
Untuk mengaktifkan proteksi ini, kita bisa atur di pengaturan Pembatasan API.
Pertama masuk dulu ke Kerdensial, lalu klik API Key yang sudah dibuat:
Lalu pilih Web Browser, karena kita hanya menggunakan API Key ini untuk web saja.
Jangan lupa untuk mengisi domain yang akan menggunakan API Key.
Lalu di bagian Pembatasan API, pilih Custom Search API.
Setelah itu, klik Simpan.
Dengan demikian, API Key kita hanya akan bisa digunakan pada domain yang sudah kita daftarkan saja.
Kenapa API Key harus diproteksi?
Karena layanan CSE yang versi gratis memiliki batasan penggunaan 100 query per hari. Apabila itu digunakan dibanyak tempat, maka CSE kita bisa jadi akan lebih cepat melampaui batas.
Implamentasi di Hugo
Untuk implementasi di Template Hugo, kita harus membuat halaman khusus untuk pencarian.
Caranya:
Pertama buat dulu konten dengan nama search.md
seperti ini:
Lalu buat layout untuk halaman pencarian layouts/search/single.html
seperti ini:
<!DOCTYPE html>
<html>
<head>
<!-- {{ partial "head.html" . }} -->
<!-- isi dengan partial milik anda -->
</head>
<body class="bg-light">
<!-- {{ partial "nav.html" . }} -->
<!-- isi dengan partial milik anda -->
<div id="search-app" class="container">
<!-- Search form section -->
<section class="my-5 row justify-content-center">
<div class="col-md-6">
<div class="input-group mb-3">
<input type="text" v-model="q" class="form-control" placeholder="Kata kunci..." aria-label="Recipient's username" aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-secondary" type="button" v-on:click="doSearch()">Cari</button>
</div>
</div>
</div>
</section>
<!-- Search result -->
<section class="row mb-5">
<post-card
v-for="post in searchResult.items"
v-bind:post="post">
</post-card>
</section>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.1/vue-router.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="/js/search.js"></script>
</body>
</html>
File search.js
dimasukkan ke dalam folder static/js/
.
Selesai!
Sekarang coba jalankan server, lalu buka http://localhost:1313/search/
.
Oya, jangan lupa matikan dulu proteksi API-nya agar bisa dicoba di localhost
.
Semoga berhasil!
Apa Selanjutnya?
Sejauh ini fitur pencarian sudah bisa bekerja seperti yang diharapkan. Namun, masih ada beberapa kekurangan yang harus ditambahkan seperti:
- Animasi saat loading;
- Proteksi dari web scraping dan bot dengan Captcha;
- Pagination;
- dll.
Selanjutnya, silahkan dilengkapi sendiri…
Selamat bereksperimen! 😉✌