author Ahmad Muhardian

Membuat Fitur Pencarian dengan Vuejs dan Google Custom Search API


Vuejs Search dengan 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.

Silakan pilih sendiri sesuai kebutuhan…

Saya memilih Google CSE, karena:

  1. Semua konten Petani Kode sudah terindeks di Google

    Indeks petanikode di Google

  2. Tidak perlu membuat atau submit indeks manual ke back-end seperti Algolia, karena Google Bot akan yang melakukannya

  3. Cukup Cepat

  4. Hasil pencariannya lebih akurat meskipun ada typo

Sebelumnya Petani Kode memang sudah menggunakan CSE dengan metode embed form.

Pencarian dengan Google CSE

Kekurangan dari metode ini… hasil pencariannya tidak bisa kita kustom sendiri.

Saya mengharapkan, hasil pencariannya bisa dikasih CSS sendiri agar mengikuti desain pada Petani Kode.

Akhirnya saya menemukan cara dengan menggunakan API.

Langkah-langkah membuatnya seperti ini:

  1. Membuat CSE baru
  2. Membuat API Key untuk akses API
  3. Implementasi API ke website kita

Untuk implementasi, kita akan menggunakan Vuejs. Karena menurut saya lebih sederhana dibandingkan menggunakan JQuery.

Oke, kalau begitu, mari kita mulai…

1. Membuat Custom Search Engine (CSE)

Silakan buka: https://cse.google.com/cse/create/new untuk membuat CSE baru.

Isi nama domain dan pilih bahasanya, lalu klik create.

Membuat Custom Search Engine

Setelah itu, masuk ke menu Edit Search Engine->Setup. Lalu, klik tombol Search Engine ID untuk mendapatkan ID CSE.

ID Custom Search Engine

Simpan ID tersebut, karena akan kita gunakan untuk mengaksesnya dari API.

2. Membuat API Key

Setelah kita membuat custom search engine, selanjutnya kita membutuhkan 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.

Silakan buat project baru, buka link ini https://console.developers.google.com.

Membuat Project Baru

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.

Membuat project

Berikutnya silakan masuk ke Dashboard, lalu klik Aktifkan API dan Layanan.

Aktivasi layanan Google

Pada kotak pencarian, cari dengan kata kunci “Custom Search”.

Cari layanan CSE

Berikutnya, silakan aktifkan layanan Custom Search API. Klik tombol Aktifkan.

Aktivasi layanan CSE

Setelah itu, buat kredensial untuk mengakses layanan ini. Klik tombol Buat Kredensial.

Buat API Key untuk layanan CSE

Berikutnya klik tombol Kredensial apa yang saya butuhkan?

Buat API Key untuk layanan CSE

Maka akan muncul API Key yang kita butuhkan.

Buat API Key untuk layanan CSE

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:

  1. https://www.googleapis.com/customsearch/v1 (punya batasan 10.000 query/hari)
  2. 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:

Versi CSE

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 dibuat;
  • 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…

Percobaan Akses API

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 silakan 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:

  1. vuejs script inti dari vuejs;
  2. vue-router.min.js untuk routing, nanti kita akan pakai untuk mengambil query string dari URL;
  3. axios.min.js untuk membuat HTTP Request ke API Google;
  4. lodash.min.js kita butuhkan di dalam script search.js;
  5. search.js adalah script aplikasi dari fitur pencarian yang akan kita buat.

Lalu di dalam kode HTML di atas, kita membagi menjadi dua section:

  1. Section pertama untuk menampilkan form untuk pencarian
  2. 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:

Form pencarian dengan Vuejs

Oya, kode HTML di atas menggunakan Bootstrap 4.

Berikutnya, kita akan membuat script pencariannya. Silakan 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);
    }
});

Silakan 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 memberikan sebuah objek post seperti ini:

Bind parameter ke komponen Vuejs

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:

  1. q adalah sebuah string yang berisi kata kunci pencarian;
  2. searchResult adalah sebuah objek JSON dari hasil yang didapatkan dari API.

Lalu metode 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 di setiap 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 silakan dicoba…

Inilah hasilnya, pencarian dengan katakunci nodejs:

Hasil pencarian dengan Google CSE

5. Proteksi API Key CSE

Sebenarnya, menulis kredensial 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 menyediakan 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 Kredensial, lalu klik API Key yang sudah dibuat:

Pengaturan batasan penggunaan API Key

Lalu pilih Web Browser, karena kita hanya menggunakan API Key ini untuk web saja.

Pengaturan batasan penggunaan API Key

Jangan lupa untuk mengisi domain yang akan menggunakan API Key.

Lalu di bagian Pembatasan API, pilih Custom Search API.

Pengaturan batasan penggunaan API Key

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 di banyak tempat, maka CSE kita bisa jadi akan lebih cepat melampaui batas.

Batasan penggunaan CSE per Hari

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:

Konten untuk search page

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>

Layout Hugo untuk search page

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, silakan dilengkapi sendiri…

Selamat bereksperimen! 😉✌