Simple front-end setup using Vue.js and Poi

In the previous post we configured Suave to serve data from Azure table storage. Now that there is data, I would like to add some front-end code and serve it through the same web server. With Suave configured, that is a piece of cake.

See Combining Suave and Azure Storage

Serving static content

From previous we have a Suave web server running, returning the data from Azure on a specific route.
In addition to the data-part, Sauve should return static files forming our front end application. First we'll add a webpart that matches on the root path, and returns a basic index.html, this can then be extended with a more advanced app later.
In addition to the default index.html-file, the web server needs to deliver other static files, mainly javascript. For this we need to add a web part that matches the desired types of files and browses them from the home folder. The pathRegex-webpart does the job.


let start connectionString =
  let app = 
    choose [
      GET >=> choose [
        path "/" >=> Files.browseFileHome "index.html"
        pathRegex @"/(.*)\.(css|png|gif|jpg|js|map)" >=> Files.browseHome
        path "/api/wines" >=> Wines.getAllWines connectionString
      ]
    ]
    let appPath = Path.Combine("..", "App") 
    let config =
      { defaultConfig with 
          homeFolder = Some appPath
          bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 8083 ]
      }
    startWebServer config app

The home folder to use when serving files is added to the configuration, and a new path added to the GET-webpart matching on request to the root uri.

Setting up Vue and Poi

In addition to the api and the entrypoint index.html, we need some actual front-end code. Since the application mainly will focus on displaying data, with little to no state and interaction, Vue.js is a good choice. Vue has gotten a lot of praise lately for being lightweight and easy to learn. Compared to React, Vue has a dedicated "view part" in the form of a template section in addition to a script section containing javascript. This is closer to Angular, but Vue is still fundamentally different in that you are not forced into a certain way of doing things.

To get started with Vue, we simply import Vue, and create the simplest possible component. The template uses the handlebar syntax to access data made available through the data-property in the component definition.

<html>
  <script src="https://unpkg.com/vue"></script>
  <div id="app">
    {{ message }}
  </div>
  <script>
    new Vue({
        el: '#app',
        data: {
            message: 'This is a WineVue!'
        }
    });
    </script>
</html>

In addition to this way of creating components, Vue supports single file components with the .vue-filetype. To get this working the app will have to be built and bundled before serving, and we would also like some kind of development server to make working with the app quicker and more pleasant. Setting up a module bundler to achieve this is a configuration hell I would like to avoid. Poi is a very neat library which provides build and bundling in addition to a dev server with built in hotloading etc. Poi has Vue built in, but also works with other frameworks such as react or riot. Poi can be added to an existing project, and with minimal configuration provide build and bundling.

Instead of having the app directly in the html, it is moved to a index.js-file. Giving us the following:

//index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>WineVue</title>

  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
//index.js
import Vue from 'vue'
import App from './App'

new Vue({
    el: '#app',
    render(h){
        return h('App')
    },
    components: { App }
})

This root Vue component imports the App-component and renders it to bootstrap the application. From now most work will be initiated from the App-component and children of that.

//app.vue
<template>
  <div id="app">
      <span>This is WineVue!! </span>
  </div>
</template>

<script>
export default {
  name: 'app',
}
</script>

Poi is installed with yarn yarn global add poi, after which running poi will start up the dev server, serving the app with hot reloading.

Calling the api

Now that the app is running it will need to fetch data from the wine api and render it using Vue-components. This could be done using different approaches, I Axios being the most fluent library for the task. Axios is a HTTP-client library for web and node development, providing a simple api for making HTTP calls. In comparison to e.g. Fetch, Axios automatically handles json transformation and handles errors in a more fluent manner.

We will create a simple service class for accessing the wine api.

//wines.js
import axios from 'axios';

export default axios.create({
    baseURL: '/api/wines'
})

Vue exposes lifecycle hooks, which can be used to make the request when the component is loaded. We'll extend the app component, introducing a "data"-function and calling the api in the "created"-lifecycle hook.
The data function returns "props", that will be available in the template part of the component. We will populate a "wines" property with the data from the HTTP-request.

//app.vue
import wineService from './services/wines';

export default {
  name: 'app',
  data() {
    return {
      wines: [],
      error: ''
    }
  },

  created() {
    wineService.get('inventory')
    .then(response => {
        this.wines = response.data.Wines
    })
    .catch(e => {
        this.error = e
    });
  },
}

Going back the browser the app will have been updated by poi, and a call has been made, which will fail since the web server isn't running on the same origin as the devserver. To be able to work with the app locally we can use the "proxy"-feature in webpack/poi. This setting will hook into all http requests and proxy failing requests to where our Suave webserver is actually running.

//poi.config.js
module.exports = (options, req) => ({
    devServer: {
        proxy: 'http://localhost:8083/api'
    }
})

The request now returns successfully, populating the wine data property. All left is to update the "template"-part of the component to render the data. We will use the v-for-directive to render the array of wines.

<template>
  <div id="app">
    <div class="view-container">
      <div class="wine-listing" v-for="wine in wines" :key="wine.Name">
        <div>{{wine.Name}}</div>
        <div>{{wine.Price}}</div>
      </div>
    </div>
  </div>
</template>

We now have a basic app running, consuming data from the api, all served through the Sauve webserver. After adding some basic styling we get the following result.

The complete code sample can be found at github. I will continue to work on it, adding more functionality, and writing about it! Next I would like to set up build and deploy using FAKE, and implement simple authentication using JWTs.