Menghindari CORS saat memakai Spring Boot bersama dengan Webpack

Pada artikel Menyatukan Frontend dan Backend Pada Satu Proyek Yang Sama, saya membuat proyek baru yang menggabungkan back end dan front end. Untuk menjalankan back end, saya memberikan perintah ./gradlew bootRun yang akan membuat instance server Tomcat baru. Saya bisa meng-eksekusi end points milik back end di http://localhost:8080/api. Setiap kali kode back end berubah, server ini akan diperbaharui. Sementara itu, untuk menjalankan front end, saya memberikan perintah yarn dev yang juga akan menjalankan server baru di http://localhost:9090. Setiap kali kode front end berubah, server ini akan diperbaharui.

Apa yang salah bila server front end berbeda dengan back end? Hampir semua browser modern sudah mendukung fasilitas Cross-origin Resource Sharing (CORS) yang akan memblokir akses end points milik front end dari domain berbeda. Tentunya saya tidak ingin setiap API milik back end saya bisa diakses oleh front end milik orang lain sesuka hati. Akan tetapi, saat mengembangkan kode program, server front end dan back end adalah dua server berbeda. Fasilitas CORS akan membuat front end saya gagal memanggil back end. Memang Spring Boot memiliki annotation @CrossOrigin yang bisa membuat back end saya dipakai semua orang sesuka hati sehingga front end saya yang berada di server berbeda bisa memanggilnya. Akan tetapi, ini membuat saya merasa kurang aman.

Apakah ada solusi lain? Yup! Saya beruntung karena template webpack yang dipakai oleh vue-cli sudah memperhitungkan hal ini. Bila saya memperhatikan isi file dev-server.js yang dijalankan oleh yarn dev, terlihat bahwa http-proxy-middleware sudah disertakan. Proyek ini pada dasarnya adalah sebuah HTTP proxy (memakai http-proxy) berbasis JavaScript. Dengan http-proxy-middleware, saya ingin pada saat front end mengakses http://localhost:9090/api, secara transparan, ia akan memanggil http://localhost:8080/api. Karena proxy ini transparan dari sisi browser, saya tidak akan menemukan masalah CORS lagi.

Untuk itu, saya akan mengubah file config/index.js pada bagian dev.proxyTable sehingga terlihat seperti berikut ini:

module.exports = {
  ...
  dev: {
    ...
    proxyTable: {
      '/api': 'http://localhost:8080'
    }
  }
}

Pada konfigurasi di atas, setiap kali saya mengakses url yang diawali oleh /api di server front end seperti http://localhost:9090/api/snakes, akses tersebut akan diteruskan ke server back end di http://localhost:8080/api/snakes secara transparan. Dengan demikian, saya tidak akan mendapatkan kesalahan CORS lagi.

Memakai Bootstrap bersama dengan Vue.js dan Webpack

Pada artikel Menyatukan Frontend dan Backend Pada Satu Proyek Yang Sama, saya membuat sebuah website kosong yang menggunakan Vue.js dan berbasis Webpack. Salah satu hal menarik yang saya sukai dari Vue.js adalah setiap komponen (single file component) memiliki deklarasi CSS-nya masing-masing. Agar CSS hanya berlaku untuk sebuah komponen (dan juga komponen lain yang dikandunginya), saya tinggal menambahkan <style scoped>. Selain itu, saya juga bisa menggunakan CSS Module dengan menggunakan <style module>.

Walaupun deklarasi CSS per komponen adalah hal yang baik, akan tetapi, ada saatnya saya juga membutuhkan CSS yang global untuk seluruh bagian di website. Sebagai contoh, akan sulit sekali bagi saya untuk menulis ulang CSS seperti Bootstrap grid di setiap komponen. Bagaimana cara menambahkan Bootstrap pada proyek Vue.js yang berbasis Webpack?

Satu hal yang perlu saya pertimbangkan adalah komponen JavaScript di Bootstrap masih menggunakan jQuery sementara filosofi Vue.js yang reaktif membuat saya ingin menghindari jQuery sebisa mungkin. Beruntungnya, saya menemukan https://bootstrap-vue.js.org yang menyediakan Bootstrap 4 dimana komponen-nya telah ditulis ulang sebagai komponen Vue.js yang tidak menggunakan jQuery. Untuk menggunakan library tersebut, saya segera menambahkan baris berikut ini pada package.json:

"dependencies": {
    ...
    "bootstrap-vue": "^0.18.0"
},

Library ini akan menyertakan bootstrap@^4.0.0-alpha.6 yang berisi Bootstrap 4. Mengapa tidak mengubah file index.html dan menambahkan baris seperti <link rel="stylesheet" href="..."> dan <script src="...">? Salah satu alasannya adalah akan lebih konsisten bila seluruh dependensi proyek diletakkan pada file package.json. Bila saya ingin memakai versi Bootstrap yang berbeda, misalnya, saya hanya perlu memperbaharui package.json tanpa mengubah index.html.

Berikutnya, saya menambahkan baris berikut ini pada awal file main.js:

import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Proyek Webpack yang dihasilkan oleh vue-cli sudah dilengkapi konfigurasi yang memakai css-loader (lihat file buildScripts\util.js) sehingga saya bisa men-import file css di dalam kode program JavaScript.

Berikutnya, saya perlu mengaktifkan bootstrap-vue dengan menambahkan kode program berikut ini di main.js:

Vue.use(BootstrapVue)

Sekarang, bila saya menjalankan website ini dengan yarn run dev, saya akan menemukan bahwa CSS Bootstrap (dan juga CSS lain yang saya import dikemudian hari) akan diletakkan secara inline ke dalam tag <style> seperti yang terlihat pada gambar berikut ini:

Inline CSS pada saat `run dev`

Inline CSS pada saat `run dev`

Walaupun CSS inline lebih sederhana, ia tidak disarankan untuk dipakai di lingkungan produksi. Beruntungnya, proyek Webpack yang dihasilkan oleh vue-cli sudah memperhitungkan hal ini. Pada file buildScripts\util.js, terlihat bahwa tersedia option extract yang akan menggunakan plugin extract-text-webpack-plugin untuk menghasilkan file CSS terpisah. Seperti yang bisa ditebak, extract akan bernilai true bila perintah ini dikerjakan pada lingkungan produksi (lihat file buildScripts/vue-loader.conf.js). Untuk membuktikannya, saya akan menjalankan perintah yarn run build. Output dari perintah ini dapat dijumpai di folder dist. Kali ini, CSS akan diletakkan pada file terpisah seperti yang terlihat pada gambar berikut ini:

File CSS terpisah saat memakai `yarn run build`

File CSS terpisah saat memakai `yarn run build`

Memperbaharui Output Webpack Di Browser Secara Otomatis

Pada artikel Menyatukan Frontend dan Backend Pada Satu Proyek Yang Sama, saya membuat proyek Spring Boot yang menggunakan Webpack di front end. Webpack adalah bundler yang juga berfungsi melakukan transformasi konten. Sebagai contoh, Webpack akan melakukan transpiling kode program ES2016 saya ke dalam kode program JavaScript ‘lama’ yang bisa dimengerti semua browser berkat babel-loader. Selain itu, saya bisa menulis style dalam format Scss (Sassy CSS) yang kemudian akan diterjemahkan oleh sass-loader menjadi CSS biasa yang bisa dimengerti oleh semua browser. Semua hasil dari Webpack ini akan disimpan kedalam sebuah Jar.

Apa yang terjadi bila saya menjalankan Spring Boot dengan gradlew bootRun lalu mengubah kode program HTML atau JavaScript yang ada? Hasil perubahan ini tidak akan terlihat pada browser! Mengapa demikian? Karena gradlew bootRun hanya memanggil Webpack pada saat pertama kali dijalankan. Saat saya mengubah kode program di HTML atau JavaScript, Webpack tidak akan dipanggil. Hal ini tentu akan sangat merepotkan karena saya perlu menjalankan gradlew bootRun berkali-kali setiap kali melakukan perubahan di front end.

Beruntungnya, konfigurasi Webpack yang dihasilkan vue-cli sudah mendukung live reload. Template yang saya pakai memiliki script buildScripts/dev-server.js yang menggunakan fasilitas dari webpack-dev-middleware untuk menghasilkan live reload. Ini hampir sama seperti apa yang dilakukan oleh webpack-dev-server. Bila saya membuka file package.json, terlihat bahwa scripts dev atau start akan melakukan hal yang sama yaitu mengerjakan buildScripts/dev-server.js.

Agar bisa memanggil script ini secara mudah, saya bisa menambahkan kode program berikut ini pada build.gradle (milik proyek front end):

...
task runDev(dependsOn: yarn, type: YarnTask) {
    args = ['run', 'dev']
}
...

Sekarang, saya bisa menjalankan proyek front end (tanpa harus menjalankan Spring Boot) dengan menggunakan perintah berikut ini:

$ ./gradlew runDev
:frontend:nodeSetup UP-TO-DATE
:frontend:yarnSetup UP-TO-DATE
:frontend:yarn UP-TO-DATE
:frontend:runDev
yarn run v0.27.5
$ node buildScripts/dev-server.js
> Starting dev server...
 DONE  Compiled successfully in 1904ms00:08:40

> Listening at http://localhost:8080

 75% EXECUTING
> :frontend:runDev

Perintah di atas akan membuka browser yang menampilkan halaman front end saya. Sekarang, bila saya melakukan perubahan pada HTML atau JavaScript, halaman tersebut akan langsung diperbaharui secara otomatis. Saya juga bisa menemukan output seperti berikut ini di console:

 WAIT  Compiling...00:00:12

 DONE  Compiled successfully in 154ms00:00:13

Sekarang, saya bisa mengerjakan front end dan melihat hasilnya secara cepat.

Bagaimana bila saya ingin menjalankan Spring Boot dan dev-server.js secara bersamaan? Karena keduanya sama-sama menggunakan port 8080, salah satu akan gagal. Untuk itu, saya akan menggubah port yang dipakai oleh dev-server.js menjadi 9090 dengan mengubah port di config/index.js.