Embracing VueJs: Scaling Front-End At Everwell

Everwell Health
Pulse by Everwell
Published in
9 min readSep 1, 2022

Back in the Day

Everwell Hub, just like any other high paced start-up, started off with a very standard Monolith application, which meant that backend and front-end code were tightly coupled together in one huge application. Our partner in crime at that time was Microsoft . NetMVC. It had all the necessary ingredients to create the perfect Monolith, from having great support for stateful or stateless authentication, to having beautiful integration support of backend with front-end using Razor Engine, etc.

Major focus here is that the front-end was able to access data models directly passed from the backend whenever the view was being rendered. It was cool initially and made everything much easier and faster, but 2 years down the line, we had this huge mess of html files filled with C# code, making it much harder for anyone to figure out what was going on.

Turning Point — A Decision

With the rapid growth of the product and addition of new features, managing front-end code also became difficult. Lot of C# got intertwined with HTML / CSS code, making it hard for engineers to do proper development. After observing how the front-end code was only deteriorating in quality with time, engineering decided to use a front-end framework, which would decouple the backend from the front-end entirely, and would interact with the backend using APIs.

Senior leadership had great exposure to VueJs, so that was chosen as the choice of framework to build the UI. A simple, yet powerful decision was made — Any new page that engineering builds, or any big modification to an existing page that engineering does, would all be done using the new front-end framework — VueJs. This practice that we adopted helped us a lot in keeping track of what our migration goals were, proper timeline estimations since we knew we had to migrate the entire thing to VueJs beforehand if required, and finally acted as a stepping stone towards decoupling front-end from backend.

Above goal of creating pages in VueJs application was achieved by creating VueJs projects, building javascript & CSS files out of it, and just placing those in our Monolith. Here X.css & X.js are files that have been built from VueJs.

<link rel=”stylesheet” href=”~/Style/X.css”>
<script language=”javascript” type=”text/javascript” src=”~/Scripts/X.js”/>

This approach did solve our use case to migrate away from RazorTemplates, but it also brought in other challenges like:

  • Impossible to debug the front end unless we run VueJs app locally and point our script URL to it.
<script src=”http://localhost:8080/js/app.js"></script>
  • Maintain all the VueJs apps as folders in a separate repository. All of them were independent VueJs apps, so in order to even connect to multiple apps, we had to run them separately (on different ports), and point our backend accordingly.
  • Any change to the front-end required us to do a couple of things:
  • Update VueJs repository and get it merged.
  • Create production ready build files from it.
  • Copy them over to our Platform code and point our script files to that.
  • Deploy the backend for any front-end changes to get reflected.

But we knew that this was the right thing to do. We knew that eventually we would move to a single app where all these pages would be used. So finally, last year, when we had the bandwidth and mockups ready for the new website, we decided to completely migrate to VueJs. This marked the turning point of all front-end development in Everwell, deprecating all existing RazorEngine front-end, and doing all new development only in VueJs.

Migrations & Advancements ft. VueJs

Migrations are hard, be it data migration, code migration or front-end migration. We were starting afresh, so we wanted to do things right by thinking about the future use cases, plan ahead for re-usability & modularity, introduce testing, etc. For starters, migrations included building consistent theme support, website header, managing authentication, consistent components look across the website, migrating existing pages which were not built till that time in VueJs, etc. Basically re-inventing a lot of things using VueJs (the entire point of migrations). To put our ideas into practice, below are a few things that we’ve done-.

I. Component Library

Once the designs for the new website were finalized, it was evident that we needed a fail-proof design library which can support multiple customizations and look and behave consistent across all pages. We did not want our engineers to reinvent the wheel every time they needed a Everwell-themed button (by copying code or creating it themselves). VueJs has an inbuilt concept of components, which allows us to split the UI into independent and reusable pieces. Here, reusable pieces could be our Everwell-themed Buttons, Input Boxes, Check Boxes, Tables, etc. Below we’ve shown an Everwell-themed check-box, created by our custom component.

<Checkbox
:optionsWithLabel=”[{ Value: 1, Label: ‘English’ }, { Value: 2, Label: ‘Hindi’ }]”
name=”Language”
label=”Select languages”/>

By using our custom checkbox, we get a consistent layout, theme and behaviour wherever it is used.. This also simplifies the addition / removal of options, labels, etc.. Moreover, this architecture gives us the flexibility of modifying our components at only one place and seeing it reflect across all its use cases.

II. Build Flavors

Everwell Hub can support multiple build flavors using VueJs environments, which essentially means that if we ever want to change the look and feel of it, we can easily do so. Currently, we’ve already configured 2 build flavors of our platform. We provide a `.scss` file with all the required theme specific things, which include but not limited to — colors, fonts, images/icons, etc. which are referenced in css throughout our platform and by simply creating a new `.scss` file, we are able to change the entire look and feel of the platform. Below we’ve shown how we’re selecting a theme based on the current environment.

css: {
loaderOptions: {
scss: {
implementation: require(‘sass’),
additionalData: `@import “@/scss/_${process.env.VUE_APP_THEME}variables.scss”`
},
scss: {
additionalData: `@import “@/scss/_${process.env.VUE_APP_THEME}variables.scss”;`
}
}
}

III. Unit Testing

Another important aspect of moving to a framework like VueJs was to use its capabilities of being able to test individual UI components. With this, we are able to test UI logic, validations and even mock our store / API responses to have a robust platform. We’re currently using @vue/test-utils for all of our tests. Below we’re doing a simple validation of whether a component exists or not, making sure that it never accidentally gets deleted.

import { shallowMount } from ‘@vue/test-utils’describe(‘App.vue’, () => {
it(‘Check Child Component Visibility’, () => {
const wrapper = shallowMount(App, {
mocks: {
$t: (key) => key
}
})
expect(wrapper.findComponent(GregorianCalendar).isVisible()).toBe(true)
})
})

VueJs: A Feature Rich Framework

VueJs brings in a plethora of features which would’ve been extremely hard if we had to implement it ourselves. We’re in love with its reactive nature and it still feels magical to see how the UI just updates wherever required. So, in this section, we’d just like to talk about all the VueJs features Everwell is using.

I. Vue-Router: Building Single Page Application

With the introduction of Vue, along with its out of the box support for single page applications, it was inevitable that we would use it. Our entire application is a “Single Page Application (SPA)”. SPAs are faster than traditional multi-page applications. SPAs download the required assets during the initial first load, and then only update the user content as required, thus improving performance and overall UX. To achieve this, we’ve made use of an existing powerful routing library called Vue-Router. Below we have shown how we are using vue-router for our Tabs navigation.

Tabs.vue
<ul class=”tabBlock-tabs”>
<li
v-for=”(tab, index) in tabs”
:key=”index”
:aria-setsize=”tabs.length”
:aria-posinet=”index + 1"
>
<! — use the router-link component for navigation. →<! — specify the link by passing the `to` prop. →
<router-link :id=getTabId(tab.tab_title)
:to= tab.to_link
tag=”a”
class=”tabBlock-tab tab_title”
active-class=”active”
:class=”active_tab === index ? ‘is-active’ : ‘’”
:aria-selected=”active_tab === index”
@click.native=”changeTab(index)”>
{{$t((tab.tab_title).toLowerCase()) }}
</router-link>
</li>
</ul>
<div class=”tabBlock-content”>
<! — route outlet →<! — component matched by the route will render here →
<router-view/>
</div>
<any>-routes.js{
path: ‘/dashboard/patient/:id’,
component: Patient,
children: [
{
path: ‘adherence’,
component: Adherence
},
{
path: ‘support-actions’,
component: SupportActions
}
]
}

Here in the route.js file, we are simply defining our route strings to route component mapping and in the Tabs.vue file, we are defining our “router-link” and “router-view” parameters which will listen and render based on our routes.js file.

We found this as an interesting use case where the goal was to dynamically change the Tabs content. We had also thought about using slots or dynamic components, but this worked out really well for us.

II. VueX: State Management

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralised store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.

We are using states extensively throughout our Vue app. States make it easy to communicate across components which makes them a very powerful yet simple tool. As the scale is high and it’s a big application, maintaining a “States” file at a global level is neither feasible nor recommended. VueJs solves this problem by introducing a concept called “modules”, where each module can have its own state file and then in the grand scheme of things, it gets merged into one. Below we have shown an example of the “Overview.vue” file’s state being created, imported and merged into a global state using VueJs modules.

Overview >> index.jsexport default {
namespaced: true, // important to indicate this
state: {
stats: [],
charts: [],

}

}
app-store.jsimport Vue from ‘vue’
import Vuex from ‘vuex’
// importing store files from different modules
import Overview from ‘../app/shared/store/modules/Overview/index.js’
import Header from ‘../app/shared/store/modules/Header/index.js’
Vue.use(Vuex)
// add modules to store
const store = new Vuex.Store({
modules: {
Overview,
Header
}
})
export default storemain.jsimport store from ‘./app/app-store’// add the complete store to VueApp
new Vue({
…,
store,
render: h => h(App)
}).$mount(‘#app’)

III. Miscellaneous

Other important things that made development extremely easy with VueJs was its out of the box, well thought out features. Few of them which we are using extensively are mentioned below.

A. Communication

It was extremely easy to send data to children components using props, also it was extremely easy to listen to children’s events using the inbuilt event bus.

B. Reactive-ness

Apart from that, everything is reactive in nature, so things like updating a value in store would bring about a change in DOM at all places where that value is being referenced. This is quite powerful and enables us to make highly reactive components which the the user usually desires.

Below, we simply click a button which in turn updates the color map state variable, and instantly we are able to see DOM updating and apply new colors at all places where that state variable was being referenced.

C. CSS Scopes

Another important feature that we use is scoped styles which makes sure that our styles are only applicable to the particular component. This helps in preventing global styles being applied to other components, which can cause confusion.

Possibilities — The Beginning After The End

Exploring the endless possibilities, in the next few parts, we’ll talk more about how we’ve leveraged these features and introduced a few interesting features like:

Dynamic Forms — Forms which are rendered based on backend configuration (from components to their dependent validations). We’ll talk about how reactive-ness plays a huge role in this module along with other interesting discussion and key takeaways.

Dark Theme & Accessibility Support — How after migration we had the best opportunity to introduce a theme switcher in our app and how we’ve done it. Along with that, opportunities to introduce accessibility features to support a much wider audience.

Stay tuned for what’s coming next.

Credit: Darvesh Punia, Senior Software Engineer II

--

--