Scalable baseline website setup with authentification and VueJS, Amazon S3 and .net core 2.1

For my projects, I need a generic website setup that I can reuse for multiple projects.

I want to try out the following setup. A frontend build in Vue served as static files from Amazon S3. A backend built with .net core 2.1 as a REST API presented with Swagger. Finally, using Googles firebase authentification for login requirements.

Since I need a baseline platform for multiple projects, it needs to be generic enough to allow me to reuse the setup. Most of my projects need a similar setup with a frontend exposed to anonymous users and a backend dashboard which requires authentification.

In this article, I am going to cover how I set up the Vue frontend. In later articles, I will cover the authentification and the backend.

If you do not know VueJS or one of its friends you are missing out, It is a revolution for building reactive frontend websites. It is part of the suite of modern Javascript frontend frameworks like Angular and React; you can find more information and a comparison of them in this article by Jens Neshaus. All three frameworks are build to support reactive websites where a state in Javascript is synchronized with the DOM.

I do not have any specific reason for choosing VueJS except that I use it as part of my work, so it creates a synergy effect. And it seems to have the easiest learning curve of the bunch.

The cool thing about Vue is that it compiles into static Javascript files that can be executed in the browser. It means that nothing special is needed to run the frontend, it can be served as 100% static files.

It has the significant effect that it can easily be replicated to edge nodes all around the world to make sure that users around the globe can load the site fast. Getting data from the backend is another story for a later article 🙂

Finding a VueJS dashboard template

For most of my projects, I foresee that some kind of administration interface is needed. Which means a login page with some type of dashboard behind.

My design skills are not that good so I prefer to find a template to build from, there exist multiple templates, but I found Vue Paper Dashboard to strike the right balance between aesthetics and functionality. CoreUI is another alternative.

Vue Paper Dashboard

Setting up the build process

If you clone the git repository, you get a complete setup, including a build script. So we only need to have npm installed to build everything. Run the commands

  1. npm install
  2. npm run build

After they complete, the build files will be available in the dist folder. Uploading this folder to a web server and we have our own version of the dashboard running. Easy peasy.

Development with Vue

Except for the build command, the template also supports npm run dev which runs a development web server with hot-reload. It means that when the files are changed the website will automatically reload the changes, making development super neat.

Unit tests

One of the shortcomings of the paper dashboard template is that it does not have a setup ready for unit tests and linting. Vue is just javascript, so any test runner should work. I went with Jest to support the testing. It is one of the most complete testing frameworks for javascript.

Combined with vue-test-utils we can write tests like this:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import ForgotPassword from '../../src/pages/ForgotPassword.vue'

const localVue = createLocalVue()
localVue.use(Vuex)

describe('Login', () => {
  let actions
  let store
  let getters
  let mocks

  beforeEach(() => {
    mocks = {
      $t: (msg) => { return msg }
    }
    actions = {
      forgotPassword: jest.fn()
    }
    getters = {
      error: jest.fn()
    }

    store = new Vuex.Store({
      state: { error: undefined, loading: false },
      actions,
      getters
    })
  })

  it('sets the correct default data', () => {
    expect(typeof ForgotPassword.data).toBe('function')
    const defaultData = ForgotPassword.data()

    expect(defaultData.email).toBe('')
  })

  it('triggers forgotPassword action on submit button click with data', () => {
    const wrapper = shallowMount(ForgotPassword, { localVue, store, mocks, stubs: ['router-link'] })

    wrapper.setData({ email: 'e@mail.com' })
    wrapper.find('#ForgotPassword').trigger('submit')
    
    expect(actions.forgotPassword.mock.calls).toHaveLength(1)
    expect(actions.forgotPassword.mock.calls[0][1]).toEqual({email: 'e@mail.com'})
  })
})

It is a test of a forgot password Vue component. It depends on Vuex and Vue-i18n which we need to mock out. But as you can see in the bottom test it is quite easy to test that filling the email field and clicking the button triggers the call to Vuex.

It did take me some time to get the setup quite right. Vue components are not real javascript, so Jest needs to know about how to load them. But luckily it can all be set up in the package.json file.

{
  "name": "vue-paper-dashboard",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "vue-cli-service build",
    "e2e": "node test/e2e/runner.js",
    "lint": "vue-cli-service lint",
    "lint-fix": "vue-cli-service lint --fix",
    "dev": "vue-cli-service serve --open",
    "test": "jest"
  },
  "dependencies": {
    "bootstrap": "^4.0.0",
    "chartist": "^0.11.0",
    "es6-promise": "^4.2.4",
    "firebase": "^5.3.1",
    "vue": "^2.5.13",
    "vue-clickaway": "^2.1.0",
    "vue-notifyjs": "^0.3.0",
    "vue-router": "^3.0.1",
    "vuex": "^3.0.1",
    "axios": "0.18.0",
    "vue-i18n": "8.0.0",
    "vue-loader": "15.4.1",
    "@kazupon/vue-i18n-loader": "0.3.0",
    "moment": "2.22.2"
  },
  "devDependencies": {
    "babel-loader": "7.1.5",
    "babel-plugin-syntax-dynamic-import": "6.18.0",
    "@babel/core": "7.0.0-rc.3",
    "@vue/cli-plugin-babel": "^3.0.0-beta.9",
    "@vue/cli-plugin-eslint": "^3.0.0-beta.9",
    "@vue/cli-service": "^3.0.0-beta.9",
    "@vue/eslint-config-prettier": "^3.0.0-beta.9",
    "babel-jest": "^23.4.2",
    "babel-preset-env": "1.7.0",
    "jest": "^23.5.0",
    "jest-vue-preprocessor": "^1.4.0",
    "jsdom": "^12.0.0",
    "node-sass": "^4.8.3",
    "sass-loader": "^6.0.7",
    "vue-jest": "2.6.0",
    "vue-server-renderer": "^2.5.17",
    "@vue/test-utils": "1.0.0-beta.24"
  },
  "description": "A sample admin dashboard based on paper dashboard UI template",
  "author": "cristian.jora <joracristi@gmail.com>",
  "engines": {
    "node": ">= 8.1.4",
    "npm": ">= 5.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ],
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "vue"
    ],
    "transform": {
      ".*\\.(vue)$": "vue-jest",
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest"
    }
  }
}

The important part is in the bottom, where jest is told how to parse .vue files using the vue-jest package.

Using Amazon S3 as a web host for static files

The cool feature of this set up is that the complete frontend is compiled to static files. It means that we can use any web server to host them with no specific requirements to the features.

It allows us to use Amazon S3 since it has edge locations all over the world and is easy to set up.

When you have created a bucket, just set public read permissions and select the feature “Static website hosting.”

Putting everything together

I am a big fan of automation, so to deliver code changed I want a CI/CD set up. My tool of choice is bitbucket pipelines.

I want to have the following happen.

  1. When I commit and push to the master branch, a build should start
  2. The build starts by compiling the source and run all the defined tests
  3. If any test fails the build fails
  4. If all succeeds the pipeline pauses
  5. When I click a “deploy” button, the new version is pushed to S3 without any additional interaction.
image: node:8.11.3

pipelines:
  default:
    - step:
        caches:
          - node
        script: 
          - echo "VUE_APP_FIREBASE_APIKEY="$VUE_APP_FIREBASE_APIKEY >> .env.local
          - echo "VUE_APP_FIREBASE_AUTHDOMAIN="$VUE_APP_FIREBASE_AUTHDOMAIN >> .env.local
          - echo "VUE_APP_FIREBASE_DATABASEURL="$VUE_APP_FIREBASE_DATABASEURL >> .env.local
          - echo "VUE_APP_FIREBASE_PROJECTID="$VUE_APP_FIREBASE_PROJECTID >> .env.local
          - echo "VUE_APP_FIREBASE_STORAGEBUCKET="$VUE_APP_FIREBASE_STORAGEBUCKET >> .env.local
          - echo "VUE_APP_FIREBASE_SENDERID="$VUE_APP_FIREBASE_SENDERID >> .env.local
          - echo "VUE_APP_API_BASE_URL="$VUE_APP_API_BASE_URL >> .env.local
          - npm install
          - npm test
          - npm run build
        artifacts:
          - dist/**
    - step:
        # set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as environment variables
        name: Deploy to S3
        deployment: test   # set to test, staging or production
        trigger: manual 
        image: atlassian/pipelines-awscli
        script:
          - aws s3 sync --delete dist s3://xxx.xxx.xx

It is quite simple; we tell the pipeline to load a docker image with node installed. Then we extract different environmental variables to a file. They are compiled into the application.

Then npm install installs all the requirements needed to build the project. Then npm test runs the unit tests. And finally, the npm run build creates the minified and compiled javascript files.

Then the files in the dist folder are stored as an artifact. The manual step reveals a button in the bitbucket interface that can be clicked to run the final step.

If any of the steps fail, the pipeline will stop. So there is no danger that we could deploy code that does not complete the tests.

You can find the complete repository here it is part of a larger project, so it is a snapshot in time, but feel free to ask questions on any part of it.


Also published on Medium.