Intro

Saya sudah merencanakan membuat sebuah wiki yang memudahkan saya untuk mencari rekomendasi SP (Speciality Points) pada omni units dari situs wiki Brave Frontier Global Fandom di akhir tahun 2019.

Kenapa repot-repot membuat wiki kalau sudah wiki Brave Frontier Global Fandom?

  1. Terlalu banyak informasi yang tersedia di situs wiki tersebut.
  2. Yang saya perlukan saat itu adalah rekomendasi SP dari omni units.

Tapi, saya belum menemukan API resmi dari Brave Frontier Global Fandom. Jika ada mungkin tidak sesuai dengan kebutuhan saya. Jadi, saya putuskan untuk membuat API tidak resmi (unofficial) dan sekaligus kesempatan untuk belajar.

Apa itu Brave Frontier? Sebuah RPG (Role Playing Game) buatan GUMI yang tersedia di Android, iOS dan Amazon. Saya mencoba game ini pada tahun 2015 dan menyukainya. Saya berhenti di tahun 2016 dan mulai bermain lagi di pertengahan tahun 2019. Menurut saya game ini keren!

Prasyarat

Pembaca diharapkan pernah menggunakan NodeJS, memanggil dependencies dan file di NodeJS, menjalankan perintah NodeJS dengan npm, memanipulasi DOM dan melintasi DOM.

Data Apa yang Ingin Diambil?

Saya membutuhkan data seperti nama, elemen, URL thumbnail dan URL artwork dan rekomendasi SP. Hasil data yang saya inginkan disimpan dalam file json dengan format array yang di dalamnya ada beragam object seperti contoh di bawah.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[{
    "name": "Ignis Halcyon Vargas"
    "element" "Fire"
    "thumbnail": "https://vignette.wikia.nocookie.net/bravefrontierglobal/images/b/b6/Unit_ills_thum_10017.png/revision/latest/scale-to-width-down/42?cb=20160215172309",
    "artwork": "https://vignette.wikia.nocookie.net/bravefrontierglobal/images/f/f6/Unit_ills_full_10017.png/revision/latest/scale-to-width-down/330?cb=20160215172304",
    "spRecommendation": [
        {
            "title": "Standard",
            "analysis": "abcde",
            "total": 60,
            "list": [
                {
                    "cost": 15,
                    "option": "abcde"
                },
                {
                    "cost": 15,
                    "option": "fghijk"
                }
            ]
        }
    ]
}]

Teknik dan Perkakas yang digunakan

Untuk mengambil data saya menggunakan teknik scraping. Scraping yang dimaksud adalah menginspeksi DOM dan mengambil nilai-nilai yang diperlukan dari DOM.

Perkakas yang saya gunakan antara lain:

  1. Axios untuk mendapatkan respon dalam bentuk teks.
  2. Cheerio dan JSDOM untuk scraping.

Repositori Proyek

Lokasi repositori proyek berada di Github. Silakan berkunjung ke satyakresna/bravefrontier.

Mengambil Data Daftar Omni Unit

Dengan menjalankan perintah npm run omniunits:raw, NodeJS akan mengeksekusi file raw.js yang berada di direktori src/omniunits.

Di file raw.js menjalankan 2 tahapan:

  1. Mengumpulkan data omniunits di baris 15.
  2. Menyimpan hasil data dalam bentuk file json bernama raw.json di direktori src/omniunits di baris 17.

Mari kembali ke tahap 1. Di tahap ini memanggil modul getOmniUnits() dalam bentuk fungsi yang berada di file index.js di direktori src/omniunits.

Di file index.js menjalankan 5 tahapan:

  1. Mengumpulkan data daftar unit meliputi id, name, thumbnail dalam bentuk URL, link menuju profil unit dalam bentuk URL, rarity, cost di baris 8.
  2. Menyaring data daftar omni unit dan menghapus omni unit yang memiliki nama mengandung karakter Jepang dan Cina di baris 10. Tujuan saya adalah untuk mendapatkan data daftar omni unit global. Kemudian, saya juga menghapus property rarity dan cost karena tidak dibutuhkan.
  3. Mendapatkan profil dari setiap omni unit dari property link di baris 19. Profil yang diambil adalah URL artwork. Artwork adalah wujud utuh dari omni unit.
  4. Mendapatkan rekomendasi SP dari setiap omni unit dari property link di baris 21.
  5. Menghapus property link dari omni unit karena sudah tidak dibutuhkan di baris 23.

Lokasi Data Diambil

Data diambil dari 3 halaman antara lain halaman daftar omni unit, halaman profil omni unit dan halaman SP omni unit. Saya menggunakan Cheerio pada halaman daftar omni unit dan profil omni unit. Sedangkan JSDOM pada halaman SP omni unit.

Mengapa saya tidak menggunakan Cheerio untuk mengambil data di halaman SP omni unit? Karena saya mengalami kesulitan pribadi menggunakan Cheerio yang menggunakan sintaks jQuery.

Dengan JSDOM saya bisa mengetes kode manipulasi DOM saya di browser development tools karena sintaks JSDOM adalah sintaks JavaScript. Jika tes berhasil maka saya menempelkan kode manipulasi DOM tersebut ke dalam file proyek saya.

Di halaman daftar omni unit, saya mengambil property ID, Name, Element, Rarity, Cost, Thumbnail dan Link untuk menuju ke halaman detail unit.

Gambar ambil data omni unit

Di halaman ini pula terdapat dua seri unit yakni main series dan global exclusive series seperti gambar di bawah.

Halaman daftar unit

Saya membagi menjadi dua scraper. Scraper pertama untuk Main Series dan scraper kedua untuk Global Exclusive Series.

Kemudian, saya menggunakan fungsi next().attr('href') di Cheerio dan teknik rekursif untuk membuat scraper berjalan ke URL berikutnya hingga URL terakhir. Saya terinspirasi dari tutorial polyglotdeveloper tentang scraping paginated list menggunakan Cheerio.

Berikutnya, di halaman profil omni unit saya mengambil artwork dengan menggunakan property link yang saya ambil di halaman daftar omni unit. Saya menggunakan Ignis Halcyon Vargas sebagai contoh.

Ignis Vargas profile

Terakhir, saya mengakses halaman SP milik Vargas dengan menambahkan path /Builds pada atribut link dan hasilnya seperti gambar di bawah.

SP omni unit Vargas

Dari gambar di atas, saya mulai mengambil bagian yang ada di tanda panah yakni Standard. Dari total SP, daftar rekomendasi SP Enhancement, SP Cost dan analysis.

Jadwal Scraping

Dengan bantuan Github Actions, saya bisa membuat jadwal kapan saya harus melakukan scraping agar data file raw.json selalu diperbaharui. Perhatikan kode di bawah.

1
2
3
4
5
6
7
on:
  push:
    branches:
      - master
  schedule:
    # Run at 23:59 on every 3rd day-of-month
    - cron: '59 23 */3 * *'

Kode di atas, menjelaskan bahwa saya akan menjalankan scraping setiap kali saya melakukan git push ke branch master dan setiap 3 hari sekali di jam 23:59 waktu Github.

Silakan menuju ke file workflow.yml untuk mengetahui detailnya.

Jika mengalami kesulitan untuk mengatur cron, silakan menuju ke crontab.guru.

Serverless API menggunakan Vercel

Sebenarnya, saya bisa saja menggunakan data raw.json file yang ada di Github sebagai API untuk digunakan di sisi frontend. Namun ada beberapa kendala, di antaranya:

  1. Ukuran file sekitar 90 KB (salin URL rawgithub, buka tab baru, inspect element, cari tab network, perhatikan nilai di bagian transfered).
  2. Tidak bisa mengendalikan cache respons.
  3. Respon header adalah application/text dan hasilnya berupa String sehingga mesti di urai (parsing) dengan JSON.parse(data).
  4. Ada dua properti yang ingin saya hilangkan yakni artwork dan spRecommendation.

Alhasil saya mencari cara untuk membuat sebuah API tetapi tidak membuat saya kerepotan untuk mengurus server, scaling, setup ini itu yang bikin cenat cenut.

Saya memutuskan menggunakan Vercel sebagai Serverless API karena mudah digunakan di sisi pengembangan maupun produksi.

Vercel mendukung JavaScript, TypeScript, Python dan Golang untuk serverless functions. Silakan baca dokumentasi Vercel tentang Serverless functions.

Boleh kan saya bilang kalau Serverless API ini istilah lainnya adalah Serverless functions?

Untuk membuat Serverless API dengan Vercel ini cukup mudah. Tinggal buat folder api di root project dan kita sudah bisa membuat sebuah fungsi yang akan mengembalikan response json.

Di dalam folder api saya membuat direktori bernama v1/omniunits dan file bernama index.js. Tujuannya untuk menampilkan daftar omni units di file raw.json tanpa property artwork dan spRecommendation.

Salah satu keuntungan menggunakan Vercel adalah saya bisa mengakses file raw.json di proyek saya dan tidak perlu melakukan request ke URL rawgithub.

Mengapa? Karena saya pikir akan menambah round-trip melakukan request ke web rawgithub.

Round-trip adalah durasi dalam milisekon dari browser mengirim permintaan sampai menerima respon dari server.

Untuk mengakses file raw.json di Vercel, file ini harus berada di dalam direktori src. Ini adalah standar yang sudah ditetapkan oleh Vercel. Silakan baca Including Additional Files di Vercel.

Jika kalian ingin tahu sumber kodenya bisa langsung menuju file index.js yang berada di direktori api/v1/omniunits.

Berikutnya, saya ingin membuat API untuk mengakses detail omni unit berdasarkan namanya. Vercel menyediakan fitur Path Segments dengan cara kita membuat nama file dibungkus dengan kotak siku seperti ini: [name].js.

Ketika ingin mendapatkan nilai dari inputan, gunakan perintah req.query.name yang mana name sesuai dengan file [name].js. Kalau misalnya mengakses id jadinya req.query.id dengan nama file [id].js.

Jika kalian ingin tahu sumber kodenya bisa langsung menuju file [name].js yang berada di direktori api/v1/omniunits.

Saat mengakses detail unit berdasarkan namanya, saya meminta parameter name yang diinputkan harus ada garis bawah (underscore) jika ada spasi. Contoh:

  1. Ignis Halcyon Vargas menjadi Ignis_Halcyon_Vargas
  2. Estia, Regalia of Elysia menjadi Estia,_Regalia_of_Elysia

Jika tidak sesuai maka omni unit tidak ditemukan dan server akan memberikan respon 404.

Cache-Control dan CORS

Keuntungan berikutnya dari Vercel adalah mengendalikan cache dari respons API dan CORS (Cross Origin Resource Sharing).

Untuk mengendalikan cache dari respons API dan CORS, buat file vercel.json di direktori utama proyek dengan konfigurasi di bawah.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
    "headers": [
        {
            "source": "/(.*)",
            "headers": [
                {
                    "key": "Cache-Control",
                    "value": "public, s-maxage=60, stale-while-revalidate"
                },
                {
                    "key": "Access-Control-Allow-Origin",
                    "value": "*"
                }
            ]
        }
    ]
}

Terdapat key source dan headers di dalam array headers. source ini artinya pola yang sesuai dengan pathname yang masuk. Pada kasus ini, source mengarah ke path utama (/) dan turunannya.

Di array headers terdapat dua obyek yakni Cache-Control dan Access-Control-Allow-Origin.

Cache-Control berisi nilai public, s-maxage=60, stale-while-revalidate.

public artinya cache bisa diakses secara publik.

s-maxage=60 artinya berapa lama respon ingin di cache. Ini akan memberitahukan kepada browser untuk tidak melakukan cache dan membiarkan Vercel untuk melakukan cache dan membatalkan cache ketika ada pembaharuan deployment. Sehingga kita tidak perlu khawatir terhadap konten basi (stale).

stale-while-revalidate artinya Vercel bisa menyediakan sumber daya dari cache CDN sekaligus memperbaharui cache dari balik layar dengan respons dari serverless functions.

Terdapat dua kondisi di mana stale-while-revalidate memiliki nilai lebih:

  1. Konten sering berubah tetapi membutuhkan sejumlah waktu yang signifikan untuk regenerasi. Contoh, query database atau upstream API request.
  2. Konten jarang berubah tetapi kita ingin memiliki fleksibilitas untuk memperbaharuinya (misalnya, membereskan salah ketik) dan tidak menunggu sampai cache kadaluwarsa.

Untuk dua kasus di atas, Vercel merekomendasikan menggunakan:

Cache-Control: s-maxage=1, stale-while-revalidate

Yang artinya sajikan dari cache, tetapi perbaharui lagi jika diminta setelah satu detik.

Dengan memasang nilai * di Access-Control-Allow-Origin, server mengijinkan siapa (origin) yang boleh mengakses aset atau data di server.

Untuk penjelasan lebih lanjut silakan baca Edge Cache for Serverless Functions.

Vercel beriringan dengan Github Actions

Setiap saya melakukan git push, Vercel akan menjalankan deployment sendiri dan terpisah dari Github Actions.

Saya tidak ingin ini terjadi karena di dalam Github Actions terdapat proses men-scraping data omni units dan saya ingin deploy ke Vercel setelah selesai men-scraping data omni units.

Untungnya ada repo amondnet/vercel-action yang menyediakan dukungan untuk melakukan deployment ke Vercel menggunakan Github Actions. Silakan baca dokumentasi Vercel Action.

Jika kalian ingin tahu implementasi kodenya, silakan baca kode workflow.yml di repo bravefrontier.

Batas Penggunaan Vercel Serverless API

Saya menggunakan Vercel sebagai hobby alias gratis. Tentunya gratis ini ada batasannya, antara lain:

  1. Jumlah serverless functions untuk setiap kali deployment sebanyak 12.
  2. Maksimum file ketika membuat deployment adalah 10.000 untuk sumber file dan 16.000 untuk build output files.

Untuk mengatasi urusan nomor 2, kita bisa membuat file .vercelignore layaknya .gitignore untuk mencegah file-file yang tidak boleh di deploy ke Vercel. Silakan lihat konfigurasi .vercelignore milik saya.

Penutup

Membuat API dari scraping DOM adalah pengalaman pertama bagi saya. Dari sini saya menemukan perkakas yang memudahkan saya seperti Axios, Cheerio, JSDOM, Github Actions untuk mengatur jadwal scraping, dan Vercel sebagai serverless functions.

Bila ada waktu luang, saya ingin mengganti Cheerio dengan JSDOM karena sintaks untuk memanipulasi dan melintasi DOM pada JSDOM adalah JavaScript sehingga saya bisa mengetesnya di developer tools browser.

Terima kasih kepada pembaca yang sudah meluangkan waktu membaca tulisan saya dan mas Odi yang sudah mendemonstrasikan penggunaan Vercel di acara Livecamp WWWID.

Semoga bermanfaat dan dukung saya di platform Trakteer, Karya Karsa dan Saweria.