Vue.js and Node.js tutorial: a realtime collaboration app hosted in Azure Static Web Apps
Tags: javascript, webdev, programming, azure
Published: 2022-01-14 11:44:11

## Realtime collaboration

This post describes how I built a realtime collaboration app that scrum teams can use for planning poker. Planning poker, also known as Scrum poker, is a gamified technique for estimating the size of user stories. This helps in deciding how many stories can be put into a sprint. Usually story points in the Fibonacci scale is used as a metric, but T-shirt sizing, like small, medium, and large, is also used.

I've worked in many different development teams that used planning poker. Each team had their own solution for doing this online while we're all working from home. These solutions ranged from chat messaging, plugins for their project management tools, to dedicated web apps. For me the chat messaging option was the least preferred since it was too easy to become biased once the first team member submits their vote. To have a good planning poker session it is important to be able to vote without being influenced by others. Realtime collaboration tools are essential these days, and I love using tools that make collaboration easy and intuitive.

## Onboarding @ Ably

This project is part of my onboarding at Ably, where I recently started as Sr Developer Advocate. I was asked to build something with Ably, Azure and a JavaScript front-end framework to learn the Ably Realtime SDK. I wanted to build something fun but still practical, and that's when I came up with Agile Flush, an online planning poker app. This is mostly intended as a learning exercise. One with a useful end result though, that can be used here: [agileflush.ably.dev](https://agileflush.ably.dev/). The source code can be found at [GitHub](https://github.com/ably-labs/agile-flush-vue-app) and I've also recorded a YouTube video about this project that you can [watch here](https://youtu.be/59BZCQuRRkM). 

## Agile Flush

The best products are those that are very low friction to use. For Agile Flush this means no signup and login, and not asking users to provide any information about themselves or the planning session. The idea is that team members join their regular online planning call in their communication tool of choice, and in addition visit the Agile Flush website, and are able to start voting immediately.

![Figure 1](https://ik.imagekit.io/ably/ghost/prod/2022/01/agileflush_screenshot.png)
_Figure 1: The Agile Flush Application_

In terms of functionality this application is quite basic:

* A new session can be started.
* A card can be selected & unselected.
* The visibility of the voting can be toggled on/off.
* The voting can be reset. 

The flow diagram in Figure 2 shows the steps and conditions.

![Figure 2](https://ik.imagekit.io/ably/ghost/prod/2022/01/agile-flush-functionality-v1.3.png)

_Figure 2: Agile Flush functionality_

All actions, except starting a new session, will synchronise data across all the participants of a session. The _Show/hide votes_ and _Reset votes_ actions will trigger the same functionality  for the other participants. Selecting a card will increase the (initially hidden) card count for the other participants, but the selection of the card is only visible for the participant who performs the action.

## The Tech Stack: Vue.js, Node.js, Ably & Azure Static Web Apps

![Figure 3](https://ik.imagekit.io/ably/ghost/prod/2022/01/aglile-flush-main-technical-components-v2.3.png)

_Figure 3: Main technical components_

Agile Flush is built with [Vue.js](https://v3.vuejs.org/) as the front-end framework, a [Node.js Azure Function](https://docs.microsoft.com/en-us/azure/developer/javascript/how-to/develop-serverless-apps) to do the authentication with Ably, [Ably](https://ably.com/) as the realtime messaging component, and [Azure Static Web Apps](https://docs.microsoft.com/en-us/azure/static-web-apps/overview) to host the Vue.js app and the Node function.

### Vue.js application

The front-end is built using Vue.js v3. In addition it uses Vue Router and Vuex. The Vue.js application consists of the following components:

* HomePage.vue; the main layout for the application.
* SessionSection.vue, the layout that is shown once a voting session has started.
* CardItem.vue; this component is used for each voting card in the Vuex store cards collection.
* FooterSection; one paragraph showing social links.

Figure 4 shows the placement of the components. A detailed description for the components is provided in the _Building the application_ section.

![Figure 4](https://ik.imagekit.io/ably/ghost/prod/2022/01/Frame-1Agile_Flush_Vue_Compoonents.png)

_Figure 4: Layout of the Vue Components_

### Node.js Function

The Vue.js application needs to communicate safely with Ably. An [authentication token](https://ably.com/documentation/core-features/authentication#token-authentication) is required when creating a new Ably Realtime instance, but that token shouldn’t be hardcoded in the front-end since it could be read and misused by others. Instead, the authentication token is requested from Ably via the createTokenRequest function that is written in Node.js and is running as an Azure Function in the backend. That function uses an Ably API key that is retrieved from the application settings that no-one can see. The function creates an instance of the Ably Realtime client, and that instance is used to get a new authentication token from Ably. When the Vue.js app creates a new Ably Realtime instance, the url of the Node.js Azure Function is provided in the authUrl parameter to safely obtain the token.

### Pub/Sub with Ably

Agile Flush is a realtime collaboration tool. Each participant submits their vote (or undo their vote) on their client application and all these actions are synchronised to the other participants applications in realtime, so everyone can see the same results and discuss it. A [pub/sub (publish/subscribe)](https://ably.com/topic/pub-sub) mechanism is ideal for this type of communication. Ably is a [realtime messaging platform](https://ably.com/pub-sub-messaging) that does pub/sub at scale. Each client application is both a publisher and subscriber in this case. A client triggers the publication of a vote to a specific Ably channel (bound to the session) and all clients are subscribed to this channel and handle the incoming vote message. Each client has their own Vuex store and the state of this store is kept in sync by the messages that Ably broadcasts to the subscribers.

### Azure Static Web Apps

The Vue application and the Node Function need to be hosted somewhere. I’ve chosen [Azure Static Web Apps](https://docs.microsoft.com/en-us/azure/static-web-apps/overview) since I’m very familiar with the Azure platform in general, and Static Web Apps is a good offering for websites that require some backend functionality in the form of Azure Functions. When an Azure Static Web App service is created it requires a connection to theGitHub repository where the code is. During the creation process a GitHub workflow is automatically created and added to the repository. This workflow includes steps for building & deploying of the application & Azure Function to the Azure cloud.

## Building the application

In this section I’ll cover the steps I took to build Agile Flush. I’ll provide enough instructions for you to follow along and create the application from a template repository. However, if you want to see (and run) the final solution immediately, please have a look at the [GitHub repository](https://github.com/ably-labs/agile-flush-vue-app), and follow the instructions in the README.

### Using the GitHub template repository

Azure Static Web Apps is a great service to host static websites and functions. I’ve been working with Azure for years so I’m most familiar with it. The Azure docs do contain several quick starts on using Azure Static Web Apps with several popular front-end frameworks but I found their Vue.js example a bit outdated. I created a new GitHub repo template instead, _staticwebapp-vue-vite_, which can be found [here](https://github.com/marcduiker/staticwebapp-vue-vite).


This _staticwebapp-vue-vite_ template contains a fully working Azure Static Web App that is preconfigured with:
* _Vue.js v3_; a well known progressive JavaScript framework.
* _Vue Router v4_; a routing plugin for Vue.
* _Vuex v4_; a state management pattern and library. Recommended by my colleague [Srushtika](https://twitter.com/Srushtika).
* _Node.js API_; a plain JavaScript based Azure Function.
* _Vite.js_; a fast JavaScript build tool. Recommended by my colleague [Jo](https://twitter.com/ThisIsJoFrank), who had been using this for our biggest project so far: [FFS Chat App](https://github.com/ably-labs/fully-featured-scalable-chat-app). 

I recommend using the [template](https://github.com/marcduiker/staticwebapp-vue-vite) to create your own GitHub repository for the Static Web App to avoid writing boilerplate code.

I also recommend using [VS Code](https://code.visualstudio.com/) as the editor since there are several extensions available for working with Azure Static Web Apps and Azure Functions straight from VS Code. When you’ve used the template repository and open the folder with VS Code you’ll be asked to install these extensions.

### Folder structure

The _staticwebapp-vue-vite_ template contains the following folders:
* _api_; containing the Node.js API based on a vanilla JavaScript Azure Function.
* _public_; for static files, such as the favicon.ico.
* _src_; containing the Vue.js v3 application. This includes these subfolders:
* _components_; for the individual .vue components.
* _router_; for the VueRouter configuration.
* _store_; for the Vuex configuration.

To install the dependencies of both the Vue.js app and the Node.js function run this in the root: 

```shell
npm run init
```

Now that we have the basics in place, let’s start updating the Node.js function.

### Updating the Node.js function
The Node.js function needs to communicate with Ably in order to retrieve an authentication token. The [Ably JavaScript client library](https://github.com/ably/ably-js) will be used for this.

* Inside the _api_ folder, run:

```shell
npm install ably
```

This installs the Ably client library to be used with the Node function.

* Rename the folder from _getTitleFromApi_ to _createTokenRequest_ so the purpose of the function is clear.
* Now that the Ably library is available, the HTTP function can be updated to use the Ably Realtime client. Replace the existing code in _createTokenRequest/index.js_ with the following implementation:

```javascript
const Ably = require('ably/promises');
 
module.exports = async function (context) {
    const id = `id- + ${Math.random().toString(36).substr(2, 16)}`;
    const client = new Ably.Realtime(process.env.ABLY_API_KEY);
    const tokenRequestData = await client.auth.createTokenRequest({ clientId: id });
    context.res = {
        headers: { "content-type": "application/json" },
        body: JSON.stringify(tokenRequestData)
    };
};
```

Notice that an environment variable, `ABLY_API_KEY`, is used when creating a new instance of the Ably.Realtime client. We haven’t specified any API key yet, so let’s do that first.

### Creating an Ably app and using the API key

The Node.js Azure Function is going to connect to Ably and requires an API key to do this. If you don’t have an Ably account yet, [sign up](https://ably.com/signup) for one. 

* If you have an account, [log in](https://ably.com/login) to ably.com, and [create a new app and copy the API key](https://faqs.ably.com/setting-up-and-managing-api-keys).
* The API key should go in the _local.settings.json_ file located in the _api_ folder of the repository. Since this file is .gitignored, you need to create the file yourself and add the following content:

```json
{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "",
        "FUNCTIONS_WORKER_RUNTIME": "node",
        "ABLY_API_KEY": "YOUR_API_KEY"
    }
}
```

* Replace `YOUR_API_KEY` with the actual Ably API key.

This _local.settings.json_ file is only used on your local machine and should not be checked into git because it usually contains keys that are meant to be kept secret. Once the application is ready to be deployed, the Ably API key needs to be added to application settings of the Azure Static Web App resource. This will be covered in the Deploying to Azure Static Web Apps section of this post.

### Creating the Vue.js components

This section lists the changes and additions to the Vue files that are already present in the template repository.

#### 1. App.vue

The _App.vue_ component located in the _src_ folder is the root component of the application. At the moment it only contains an import of the HomePage.vue component.

* Replace the content of App.vue with [this implementation](https://github.com/ably-labs/agile-flush-vue-app/blob/main/src/App.vue).

The `template` section in this file is still the same and uses HomePage component. The `script` section now contains getters and actions from the Vuex store that are shown in the Vuex store section of this post. The `style` section contains the css regarding fonts and colours that are applicable for the entire application.

#### 2. HomePage.vue

The _HomePage.vue_ component located in the_ src/components _folder contains the template for the home page.

Replace the content of the file with [this implementation](https://github.com/ably-labs/agile-flush-vue-app/blob/main/src/components/HomePage.vue).

This component imports the `SessionSection` and `FooterSection` components. The script section contains the start method which starts a new voting session and initiates a new connection with Ably by calling the Vuex actions `startSession` and `instantiateAblyConnection` respectively. In addition this component contains buttons to toggle the visibility of the voting results and resetting the voting results using the `toggleShowResults` and `resetVoting` actions from the Vuex store.

#### 3. SessionSection.vue

In the _src/components folder_ create a new file named _SessionSection.vue_. This component will contain the template for the layout and behaviour once a session has been started.

Replace the content of the file with [this implementation](https://github.com/ably-labs/agile-flush-vue-app/blob/main/src/components/SessionSection.vue).

This is a very small component that only retrieves data from the Vuex store using these two getters: `getNrOfParticipantsVoted` and `getNrOfParticipantsJoined`.

#### 4. CardItem.vue

In the _src/components_ folder create a new file named _CardItem.vue_. This component will contain the template for a single voting card. This component will be used for all the cards present in the `cards` collection in the Vuex store.

Replace the content of the file with [this implementation](https://github.com/ably-labs/agile-flush-vue-app/blob/main/src/components/CardItem.vue).

The `script` section contains the `selectCard` method that is used for both selecting and deselecting a card. This method calls either the `doVote` or `undoVote` action in the Vuex store.

#### 5. FooterSection.vue

In the _src/components_ folder create a new file named _FooterSection.vue_. This component will contain the template for the footer of the page. It will show the social links that are present as static information.

Replace the content of the file with [this implementation](https://github.com/ably-labs/agile-flush-vue-app/blob/main/src/components/FooterSection.vue).

#### 6. Vuex store

The store is the heart of the Vue application since it manages the state of the application and will handle the synchronisation of the state between the connected clients through an Ably channel.  The main implementation of the Vuex store is located in _src/store/index.js_.

Replace the content of the file with [this implementation](https://github.com/ably-labs/agile-flush-vue-app/blob/main/src/store/index.js).

Add two extra files in the _src/store_ location:

* _cardModule.js_; The cardModule will handle the state for the voting cards. Replace the content of the file with [this implementation](https://github.com/ably-labs/agile-flush-vue-app/blob/main/src/store/cardModule.js).
* _realtimeModule.js_; The realtimeModule will handle the state for anything related to Ably Realtime. Replace the content of the file with [this implementation](https://github.com/ably-labs/agile-flush-vue-app/blob/main/src/store/realtimeModule.js).

The realtimeModule uses the Able Realtime client to communicate with Ably. To install the client library, run this in the root of the repository:

```shell
npm install ably
```

The Vuex store contains the following parts:

* `state`; a collection of properties that describe the state. For example, the `cards` property in the cardModule that contains the definitions for all of the voting cards:

```javascript
cards: [
    {
        number: '0',
        count: [],
        style: 'card0',
    },
    {
        number: '0.5',
        count: [],
        style: 'card05',
     },
     ...
```

* `getters`; a collection of methods to query the state. For example, the `voteCountForCard` getter that retrieves the vote count for the specified card number:

```javascript
voteCountForCard: (state) => (cardNumber) => 
    state.cards.filter((card) => card.number === cardNumber)[0].count.length
```

* `mutations`; a collection of methods to alter the state properties. For example, the `addParticipantVoted` mutation that adds a client vote to the cards state:

```javascript
addParticipantVoted(state, clientVote) {
    const index = this.getters.cardIndex(clientVote.cardNumber);
    if (!state.cards[index].count.includes(clientVote.clientId)) {
        state.cards[index].count.push(clientVote.clientId);
    }
}
```

* `actions`; a collection of methods that combine mutations and the Ably API to manage and synchronise the state across the clients. For example, the `doVote` action that calls the `addParticipantVoted` mutation and the `publishVoteToAbly` action:

```javascript
doVote({ dispatch, commit, getters }, cardNumber) {
    const clientVote = {
        clientId: getters.clientId,
        cardNumber,
    };
    commit('addParticipantVoted', clientVote);
    dispatch('publishVoteToAbly', clientVote);
}
```

The `startSession` action in _store/index.js_ depends on a `generateSessionId` method which is not yet available.

* Inside the _src_ folder create a new folder called _util_.
* Create a new file here and name it _sessionIdGenerator.js_.
* Replace the content of the file with [this implementation](https://github.com/ably-labs/agile-flush-vue-app/blob/main/src/util/sessionIdGenerator.js). 

The `generateSessionId` method is used to create a randomised session ID based on two adjectives and a noun. This ID is put in the query string of the application so it can be easily shared with other participants.

For more information on how the components interact with the Vuex store have a look at the sequence diagrams located in the [GitHub repository](https://github.com/ably-labs/agile-flush-vue-app/tree/main/diagrams).

Now everything is in place and you can start the application locally by running:

```shell
npm run all
```

### Deploying the realtime collaboration app to Azure Static Web Apps

Deployment of your application to Azure Static Web Apps is done via the Azure portal or the Azure CLI:

* [Portal instructions](https://docs.microsoft.com/azure/static-web-apps/get-started-portal?tabs=vue)
* [CLI instructions](https://docs.microsoft.com/azure/static-web-apps/get-started-cli?tabs=vue)

In both cases you can skip the repository creation step since you’re using the _staticwebapp-vue-vite_ GitHub repository template.
For creating Azure Static Web Apps from scratch or managing existing apps from VSCode I highly recommend using the [Azure Static Web Apps extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurestaticwebapps) for VS Code. This extension should be a recommended install when you’re using the _staticwebapp-vue-vite_ template repository.

## Summarizing

Realtime collaboration tools are becoming increasingly more important on an everyday basis. Luckily creating realtime experiences is getting easier for developers thanks to modular front-end frameworks like Vue.js, realtime pub/sub services such as Ably, and an abundance of various cloud services.

Agile Flush is open source, you can have a look at the [GitHub repository](https://github.com/ably-labs/agile-flush-vue-app). Feel free to fork it, and extend it to make it your own (update the cards to use T-shirt sizing anyone?). The README explains how to build & run it locally or in GitHub Codespaces. I’ve also added CodeTours that explain all the important parts in the repository. If you plan to fork Agile Flush and to use it just for your team, you can use the free tier of Azure Static Web Apps, and the free tier of Ably, so you can host & run this app at zero cost!