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.

Silahkan pilih sendiri sesuai kebutuhan…

Saya memilih Google CSE, karena:

  1. Semua konten Petanikode sudah terindek 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 Petanikode 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 dikasi CSS sendiri agar mengikuti desain pada Petanikode.

Akhirnya saya menemukan cara dengan menggunakan API.

Langhka-langhak membuatnya seperti ini:

  1. Membuat CSE baru
  2. Membuat API Key untuk akeses API
  3. 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.

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 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.

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 silahkan 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, silahkan aktifkan layanan Custom Search API. Klik tombol Aktifkan.

Aktivasi layanan CSE

Setelah itu, buat kerdensial 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

Berikut ini adalah URL yang digunakan untuk mengakses CSE:

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…

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

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

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

Hasil pencarian dengan Google CSE

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:

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 dibanyak 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 serach 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 serach 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, silahkan dilengkapi sendiri…

Selamat bereksperimen! 😉✌