Build SPAS with VUEJS on Google Cloud Platform

Giới thiệu

Google Cloud Platform (GCP) là một hệ thống các dịch vụ điện toán đám mây của Google. Google Cloud Platform gồm các dịch vụ lưu trữ phục vụ cho việc tính toán, lưu trữ, phát triển ứng dụng. Dịch vụ này được lưu trữ và chạy trên phần cứng của Google. Dịch vụ nền tảng Google Cloud Platform phục vụ cho các nhà phát triển phần mềm, quản trị viên điện toán đám mây, các chuyên gia công nghệ thông tin của doanh nghiệp, các nhà phát triển website thông qua mạng công cộng cũng như những mạng dành riêng.

GCP cung cấp cho người dùng một SERVICE "Hosting Static Website" được sử dụng để chạy các ứng dụng WEB tĩnh chỉ bao gồm HTML, CSS, Javascript . Chúng không thể chứa các nội dung động như PHP.

Ở bài viết này mình sẽ làm một DEMO về SPA (Single page Application) sử dụng Vuejs cùng với Firebase để tạo 1 ứng dụng CRUD trên "Hosting Static Website" của GCP.

Chuẩn bị

  • Tạo một domain free trên http://www.dot.tk .
  • Tạo một Bucket trên GCP để chứa source web app static(HTML, CSS, Javascript).
  • Tạo account trên Firebase dùng để lưu dữ liệu.

Thực hiện:

Sau khi đã tạo các account trên xong thì chúng ta tiến hành các bước sau:

  1. Xác thực domain với GCP

    Các bạn làm theo hướng dẫn này nhé https://cloud.google.com/storage/docs/domain-name-verification#verification

    Bạn login vào page quản lý domain và add record như sau

    Screenshot from 2019-04-21 21-29-42.png

    ở 2 record type A chúng ta điền IP 74.125.24.128 này là của google Record txt Target chính là thông tin khi thực hiện xác thực domain với google

    Đến đây là chúng ta đã config xong phần domain trỏ về IP của Google rồi .Ở bước này chúng ta đợi cho đến khi ping domain về đúng IP trên thì thành công nhé.

  2. Tạo Bucket chứa source Static website

    Vào link này https://console.cloud.google.com/storage/create-bucket nhập dữ liệu như sau

    • Name: trùng với tên domain của bạn đã tạo www.[YOUR_DOMAIN]
    • Storage class: Multi-Regional.
    • Location: United States.

    Đến đây chúng ta thử tạo 2 file index.html và 404.html upload lên bucket

    bucket-website-example.png

    Tiếp theo chúng ta config cho khi access vào domain sẽ thự động tìm đến file index.html và khi không có file sẽ tìm đến file 404.html

    Ở list Buckets https://console.cloud.google.com/storage/browser chúng ta click vào biểu tưởng tùy chinh ở cuối dòng bên phải và chọn Edit website configuration.

    Ở cửa sổ Configure website chúng ta chỉ đinh Main Pageindex.html404 (Not Found) Page404.html

    Đến đây bạn có thể mơ trình duet65 lên và thử ngay thành quả bằng cách nhập domain vào rồi.

  3. Tạo source VUEJS CRUD với firebase

  • Cài đặt Vue sử dụng Vue CLI bằng một trong 2 câu lệnh sau:

    npm install -g @vue/cli
    or
    yarn global add @vue/cli
  • Tạo Project

    vue create vuefirbase
    cd vuefirbase
  • Cài đặt Vuefire bằng 1 trong 2 câu lệnh sau:

    yarn add vuefire firebase
    or    
    npm install vuefire firebase --save
  • Cài đặt Bootstrap 4 bằng 1 trong 2 cách:

    yarn add bootstrap
    # or
    npm install bootstrap --save
  • Cài đặt gói vue-route

    npm install vue-router --save
  • Cài đặt gói nprogress cung cấp component UI cho việc hiển thị route

    npm install nprogress --save
  • Tạo 4 file component tương ứng CRUD

    // ListItem.vue
    
    <template>
      <div>
        <h1>List Item</h1>
        <table class="table table-striped">
          <thead>
            <tr>
              <th>Item Name</th>
              <th>Item Price</th>
              <th colspan="2">Action</th>
            </tr>
          </thead>
          <tbody>
              <tr v-for="item of items" :key="item['.key']">
                <td>{{ item.name }}</td>
                <td>{{ item.price }}</td>
                <td>
                    <router-link :to="{ name: 'Edit', params: {id: item['.key']} }" class="btn btn-warning">
                      Edit
                    </router-link>
                </td>
                <td>
                  <button @click="deleteItem(item['.key'])" class="btn btn-danger">Delete</button>
                </td>
              </tr>
          </tbody>
        </table>
      </div>
    </template>
    
    <script>
    
    import { db } from '../config/db';
    
    export default {
      components: {
          name: 'ListItem'
      },
      data() {
        return {
          items: []
        }
      },
      firebase: {
        items: db.ref('items')
      },
      methods: {
        deleteItem(key) {
          this.$firebaseRefs.items.child(key).remove();
        }
      }
    }
    </script>
    // AddItem.vue
    
     <template>
       <div class="container">
             <div class="card">
                 <div class="card-header">
                     <h3>Add Item</h3>
                 </div>
                 <div class="card-body">
                     <form v-on:submit.prevent="addItem">
                         <div class="form-group">
                             <label>Item Name:</label>
                             <input type="text" class="form-control" v-model="newItem.name"/>
                         </div>
                         <div class="form-group">
                             <label>Item Price:</label>
                             <input type="text" class="form-control" v-model="newItem.price"/>
                         </div>
                         <div class="form-group">
                             <input type="submit" class="btn btn-primary" value="Add Item"/>
                         </div>
                     </form>
                 </div>
             </div>
         </div>
     </template>
    
     <script>
    
     import { db } from '../config/db';
    
     export default {
       components: {
           name: 'AddItem'
       },
       firebase: {
         items: db.ref('items')
       },
       data () {
         return {
           newItem: {
               name: '',
               price: ''
           }
         }
       },
       methods: {
           addItem() {
             this.$firebaseRefs.items.push({
                 name: this.newItem.name,
                 price: this.newItem.price
             })
             this.newItem.name = '';
             this.newItem.price = '';
             this.$router.push('/index')
           }
         }
     }
     </script>
    // EditItem.vue
    
    <template>
      <div class="container">
            <div class="card">
                <div class="card-header">
                    <h3>Edit Item</h3>
                </div>
                <div class="card-body">
                    <form v-on:submit.prevent="updateItem">
                        <div class="form-group">
                            <label>Item Name:</label>
                            <input type="text" class="form-control" v-model="newItem.name"/>
                        </div>
                        <div class="form-group">
                            <label>Item Price:</label>
                            <input type="text" class="form-control" v-model="newItem.price" />
                        </div>
                        <div class="form-group">
                            <input type="submit" class="btn btn-primary" value="Update Item"/>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </template>
    
    <script>
    
    import { db } from '../config/db';
    
    export default {
      components: {
          name: 'EditItem'
      },
      firebase: {
        items: db.ref('items'),
        itemsObj: {
          source: db.ref('items'),
          asObject: true
        }
      },
      data () {
        return {
          newItem: {}
        }
      },
      created() {
         let item = this.itemsObj[this.$route.params.id]
         this.newItem = {
           name: item.name,
           price: item.price
         }
      },
      methods: {
        updateItem() {
          this.$firebaseRefs.items.child(this.$route.params.id).set(this.newItem);
          this.$router.push('/index')
        }
      }
    }
    </script>
    //Home.vue
    <template>
      <h1>Home</h1>
    </template>
    
    <script>
    export default {
      components: {
          name: 'Home'
      }
    }
    </script>
  • Cập nhật nội dung 2 file App.vue và main.js

    //App.vue
    <template>
      <div id="app" class="container">
        <nav class="navbar navbar-expand-sm bg-light">
          <ul class="navbar-nav">
            <li class="nav-item">
              <router-link :to="{ name: 'Home' }" class="nav-link">Home</router-link>
            </li>
            <li class="nav-item">
              <router-link :to="{ name: 'Add' }" class="nav-link">Add Item</router-link>
            </li>
            <li class="nav-item">
              <router-link :to="{ name: 'List' }" class="nav-link">All Item</router-link>
            </li>
          </ul>
        </nav>
        <div class="gap">
          <router-view></router-view>
        </div>
      </div>
    </template>
    
    <style lang="css">
      @import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
    </style>
    
    <style>
      .gap {
        margin-top: 50px;
      }
    </style>
    
    <script>
    
    export default {
      name: 'app'
    }
    </script>
    //main.js
    import Vue from 'vue'
    import VueFire from 'vuefire'
    import VueRouter from 'vue-router'
    import App from './App.vue'
    
    import AddItem from './components/AddItem.vue'
    import EditItem from './components/EditItem.vue'
    import ListItem from './components/ListItem.vue'
    import Home from './components/Home.vue'
    
    import NProgress from 'nprogress';
    import '../node_modules/nprogress/nprogress.css'
    
    Vue.use(VueRouter)
    Vue.use(VueFire)
    Vue.config.productionTip = false
    
    const routes = [
      {
        name: 'Home',
        path: '/',
        component: Home
      },
      {
            name: 'Add',
            path: '/add',
            component: AddItem
      },
      {
          name: 'Edit',
          path: '/edit/:id',
          component: EditItem
      },
      {
          name: 'List',
          path: '/index',
          component: ListItem
      },
    ];
    
    const router = new VueRouter({ mode: 'history', routes: routes });
    
    router.beforeResolve((to, from, next) => {
      if (to.name) {
          NProgress.start()
      }
      next()
    })
    
    router.afterEach(() => {
      NProgress.done()
    })
    
    new Vue({
      render: h => h(App),
      router
    }).$mount('#app')
    
  • Tạo file config/db.js trong thư mục src để cấu hình thông tin connect tới account firebas mà bạn đã tạo:

    // config/db.js
    
     import Firebase from 'firebase'
      let config = {
         apiKey: "",
         authDomain: "",
         databaseURL: "",
         projectId: "",
         storageBucket: "",
         messagingSenderId: ""
       };
     let app = Firebase.initializeApp(config)
     export const db = app.database()

*** Như vậy là chúng ta đã code xong chức năng CRUD bằng Vuejs connect với Firebase rồi. Việc tiếp theo chúng ta cần làm là build source để upload lên Bucket static website của GCP.

  1. Build source and deploy to Bucket GCP
  • Chạy lên sau để build source

    npm run build
  • Sau khi build xong chúng ta upload hết tất cả file trong folder dist lên bucket

    Screenshot from 2019-04-21 22-16-24.png

    Screenshot from 2019-04-21 22-17-28.png

  1. Access vào Domain tận hưởng thành quả nào
  • Page home

    Screenshot from 2019-04-21 22-18-58.png

  • Page Add item

    Screenshot from 2019-04-21 22-20-21.png

  • Page List

    Screenshot from 2019-04-21 22-21-20.png

Kết luận

Tới đây là chúng ta đã hoàn thành build SPA sử dụng Vue connect tới firebase được deploy lên Static Webstite của GCP rồi, tất cả để free các bạn hãy cùng TRY nào.

Happy coding!

Posted in GCP, Java, Vue js on Apr 21, 2019