Post-Mortem: Outbreak Database
Tags: vue, graphql, postmortem
Published: 2020-03-25 11:17:45

---
title: Post-Mortem: Outbreak Database
published: true
date: 2020-03-03 05:00:00 UTC
tags: vuejs,graphql,post-mortem
canonical_url: https://nystudio107.com/blog/post-mortem-outbreak-database
---

# Post-Mortem: Outbreak Database

### Mod­ern­iz­ing an aging cus­tom PHP web­site with Craft CMS for con­tent man­age­ment, and a hybrid Twig/Vue.js + Vuex + Axios + GraphQL on the frontend

Andrew Welch / [nystudio107](https://nystudio107.com)

![Disease outbreak database](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/disease-outbreak-database.jpg)

Relat­ed talk: [Solv­ing Prob­lems with Mod­ern Tooling](https://speakerdeck.com/nystudio107/solving-problems-with-modern-tooling)

I was con­tact­ed to do over­flow work for a free­lancer who found him­self in the envi­able posi­tion of hav­ing too much work booked.

The project was some­thing that will be famil­iar to most web devel­op­ers, which was to take an old web­site [Out​break​Data​base​.com](http://outbreakdatabase.com/) and mod­ern­ize it.

This arti­cle describes the high­er-lev­el deci­sions made while work­ing on the project; if you want to get into the tech­ni­cal imple­men­ta­tion, check out the [Using the Craft CMS ​“head­less” with the GraphQL API](https://dev.to/gaijinity/using-the-craft-cms-8220-headless-8221-with-the-graphql-api-54ah-temp-slug-130738) article.

**N.B.:** While my role on the project is fin­ished, the project may or may not be live at the time of this writing.

<aside>
                                The cus­tom-built Cake <span>PHP</span> web­site was start­ing to show its age, both visu­al­ly and technologically.
                            </aside>

The client want­ed a web­site that was eas­i­er for con­tent authors to main­tain the hygiene of the data in the out­break data­base, and the site just need­ed an over­all refresh to car­ry it for­ward for the next 10 years.

The web­site describes itself thusly:

<aside>
                                Out­break Data­base is a resource that pro­vides access to food poi­son­ing out­break data in one easy to search place, dat­ing back to <span>1993</span>.
                            </aside>

They just did­n’t want the web­site to _look_ like it dat­ed back to 1993.

![Original outbreak database website](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x777_crop_center-center_100_line/original-outbreak-database-website.png)

## The Ini­tial Handoff

The design for the web­site was already done, and the less inter­est­ing (to me any­way) work of data migra­tion to [Craft CMS](https://craftcms.com/) was done already as well.

Bonus for me.

I was giv­en access to the exist­ing site, a CSS file that was being used to style this project and sev­er­al oth­er ​“mini-site” projects for the client, and some Twig tem­plates that showed the mocked out design.

The clients goals were:

- Make the out­break data­base eas­i­er to main­tain for the con­tent authors
- Make the fron­tend eas­i­er to use by researchers and journalists
- Mod­ern­ize the web­site underpinnings
- Poten­tial­ly pro­vide an API to allow oth­er par­ties to access the data­base directly

Oth­er than that, I was giv­en pret­ty much free rein to do what­ev­er I thought was best. Which is a lev­el of trust I real­ly enjoy in my rela­tion­ship with the orig­i­nal free­lance developer.

Luck­i­ly for me, using Craft CMS as a back­end ensures that the first two bul­let points are already tak­en care of by Craft CMS’s excel­lent con­tent mod­el­ing & author­ing capabilities.

As I do for any project I work on, I spend a bit of time upfront learn­ing about the client, their goals, etc. The nor­mal stuff.

Then I sit down to think about what tech­nolo­gies and tech­niques I could apply to help them reach their goals.

## GraphQL as an API

While the actu­al design of the web­site was not in my con­trol, the tech­no­log­i­cal under­pin­nings of the web­site and the user expe­ri­ence def­i­nite­ly was.

I want­ed to use GraphQL over the Ele­ment API not just because it was less work, but because it pro­vid­ed a self-doc­u­ment­ed, strict­ly typed API for us auto­mat­i­cal­ly. GraphQL is a doc­u­ment­ed, wide­ly embraced stan­dard, so plen­ty of learn­ing mate­ri­als are available.

Since the client had a stat­ed inten­tion of want­i­ng to be able to pro­vide oth­ers access to the data­base, I imme­di­ate­ly thought of [GraphQL](https://graphql.org/).

It was a nice, clean, mod­ern way to present stan­dard­ized access to data, that allows researchers to query for just the data that they are look­ing for. Since Pix­el & Ton­ic had recent­ly released a first-par­ty GraphQL imple­men­ta­tion for [Craft CMS 3.3](https://craftcms.com/blog/craft-33), it seemed like a lock.

There was a rub, however.

At the time, the GraphQL imple­men­ta­tion did­n’t sup­port query­ing based on cus­tom fields, which we need­ed for the faceted search. So we were left with the prospect of:

- Writ­ing a cus­tom [Ele­ment API](https://github.com/craftcms/element-api) implementation
- Using Mark Huot’s [CraftQL plu­g­in](https://plugins.craftcms.com/craftql)
- ???

So like any respon­si­ble devel­op­er, I went with ???. Which in this case meant fil­ing some issues for the Craft CMS devel­op­ers to see if the con­cerns could be addressed.

For­tu­nate­ly, we weren’t the only devel­op­ers want­i­ng this func­tion­al­i­ty, so Andris rolled his sleeves up and got it imple­ment­ed in Craft CMS 3.4.

We were in business.

## Adopt­ing Vue + Vuex + Axios

Since we’d already decid­ed on GraphQL as an API, I thought the best way to ensure we were build­ing out an API oth­ers could access would be to con­sume that API ourselves.

So instead of using Craft’s built-in [Ele­ment Queries](https://docs.craftcms.com/v3/dev/element-queries/) for access­ing data via Twig, I adopt­ed [Vue.js](https://vuejs.org/) and [Axios](https://github.com/axios/axios).

We’d use Vue to help make writ­ing the inter­ac­tive UI eas­i­er to do, and Axios to send along our GraphQL queries to the Craft CMS backend.

[Vuex](https://vuex.vuejs.org/) is a glob­al data store that we’d lever­age to stash the data fetched via Axios, and make it avail­able to all of our Vue.js components.

Here’s what the orig­i­nal web­site UX was like for searching:

So pret­ty typ­i­cal for an old­er web­site design: a form where you blind­ly enter search cri­te­ria, click the Search but­ton, and a results page shows up.

If you make a mis­take, or don’t find what you want, you hit the back but­ton, and try again.

The new design and UX hand­ed off to me looked visu­al­ly nicer:

![Updated outbreak database design](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x689_crop_center-center_100_line/updated-outbreak-database-design.png)

While this looks bet­ter, it oper­at­ed much the same: enter your search cri­te­ria, click a but­ton, go to a search results page. Hit the back but­ton to try again if you don’t get what you want.

I thought we could do bet­ter, and Vue.js + Vuex + Axios + GraphQL would make doing that easier.

## Doing Bet­ter

A great part of my sat­is­fac­tion work­ing on ren­o­vat­ing old­er sites is the goal of mak­ing the world just a lit­tle bit bet­ter. We don’t always hit the mark dead-on, but striv­ing to improve things is what moti­vates me.

So here’s what we end­ed up with:

First I elim­i­nat­ed the ​“search results page”; instead, the search results would be dis­played inter­ac­tive­ly right below the query. As soon as you start typ­ing, it starts search­ing ([debounced](https://www.geeksforgeeks.org/debouncing-in-javascript/) of course), and a lit­tle spin­ner shows you so (thanks, [vue-sim­ple-spin­ner](https://www.npmjs.com/package/vue-simple-spinner)).

Click­ing on the **Search** but­ton or hit­ting the Return/​Enter key would smooth­ly auto­scroll (thanks, [vue2-smooth-scroll](https://www.npmjs.com/package/vue2-smooth-scroll)) to view the search results.

![Graphql debounced search](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_768x335_crop_center-center_100_line/graphql-debounced-search.png)

I think the UI should be reworked a bit to make this a lit­tle less bulky so we can see more of the search results, but already I think we have a nice improvement.

Peo­ple can inter­ac­tive­ly see the results of their search query, and make adjust­ments as need­ed with­out hop­ping back and forth between pages.

But we did­n’t want to lose the abil­i­ty of being able to copy a search result from the address bar, and send it to col­leagues. So a lit­tle mag­ic was done to update the address bar with a prop­er <kbd>search?keywords=</kbd> URL.

Next up was to elim­i­nate some of the ​“I don’t know what to search for” prob­lem. Instead of pro­vid­ing just an emp­ty box where you type what cri­te­ria you want, we’d pro­vide an auto-com­plete lookup of avail­able choic­es (thanks, [@trevoreyre/autocomplete-vue](https://www.npmjs.com/package/@trevoreyre/autocomplete-vue)):

![Graphql autocomplete search](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_768x414_crop_center-center_100_line/graphql-autocomplete-search.png)

I think this helps great­ly with the UX, because researchers can just start typ­ing, and they’ll see a list of pos­si­ble things they can choose from.

This also adds some trans­paren­cy to the data­base hygiene, and allows the con­tent authors to eas­i­ly see dupli­cat­ed data.

## The CSS Problem

When­ev­er I start on a new project, I great­ly look for­ward to refac­tor­ing the site to use [Tail­wind CSS](https://tailwindcss.com/). If you’re not on-board the Tail­wind express yet, do give it a look, I’ve yet to know of any­one who has used it, and moved back to a more tra­di­tion­al BEM approach.

I’d be will­ing to use some pro-bono hours to do the refac­tor­ing myself if it isn’t includ­ed in the project. But in this case, the CSS was being used on a num­ber of sites to give them all a sim­i­lar look.

So even if I did the CSS refac­tor­ing to Tail­wind CSS on my own time, it would­n’t mesh well with their goals of hav­ing one CSS file for mul­ti­ple sites.

So I decid­ed to roll their CSS in as <kbd>legacy/styles.css</kbd> and use my nor­mal Tail­wind CSS + [PurgeC­SS](https://purgecss.com/) set­up to to over­ride styles or add new styles:

```

/**
 * app.css
 *
 * The entry point for the css.
 *
 */

/**
 * This injects Tailwind's base styles, which is a combination of
 * Normalize.css and some additional base styles.
 */
 @import 'tailwindcss/base';

/**
 * This injects any component classes registered by plugins.
 *
 */
@import 'tailwindcss/components';

/**
 * Here we add custom component classes; stuff we want loaded
 * *before* the utilities so that the utilities can still
 * override them.
 *
 */
@import './components/global.pcss';
@import './components/typography.pcss';
@import './components/webfonts.pcss';

/**
 * Legacy CSS used for the project, rather than rewriting it in Tailwind
 */
@import './legacy/styles.css';

/**
 * Include styles for individual pages
 */
@import './pages/homepage.pcss';

/**
 * Include vendor css.
 */
@import './vendor.pcss';

/**
 * This injects all of Tailwind's utility classes, generated based on your
 * config file.
 */
@import 'tailwindcss/utilities';

/**
 * Forced overrides of the legacy CSS
 */
@import './components/overrides.pcss';

```

This gives me the best of both worlds:

- I can use Tail­wind CSS’s util­i­ty class­es for addi­tion­al styling or to over­ride the base CSS as needed
- The exist­ing lega­cy <kbd>styles.css</kbd> is import­ed whole­sale, so they can update it as they see fit

## Hybrid Web­site

This web­site is what I’d term a ​“hybrid” web­site, in that it uses both Twig and Vue to ren­der content. 

It was done this way for prac­ti­cal rea­sons. The project was already using Twig to ren­der pages, and the bud­get was­n’t there to redo the tool­ing to use [JAM­stack](https://jamstack.org/) with some­thing like [Grid­some](https://gridsome.org/). The ben­e­fits of doing so were also dubi­ous in this case.

So instead we dropped Vue.js into the mix just for the dynam­ic com­po­nents on the page. For exam­ple, this is what the home­page looks like:

```

{% extends "_layouts/generic-page-layout.twig" %}

{% block headLinks %}
    {{ parent() }}
{% endblock headLinks %}

{% block content %}
    <div class="section--grey-pattern section--grey-pattern-solid section--mobile-gutter-none"
         style="min-height: 648px;"
    >
        <div id="component-container">
        </div>
    </div><!-- /.section-/-grey-pattern -->
{% endblock %}

{% block subcontent %}
{% endblock %}

{# -- Any JavaScript that should be included before </body> -- #}
{% block bodyJs %}
    {{ parent() }}
    {{ craft.twigpack.includeJsModule("home.js", true) }}
{% endblock bodyJs %}

```

This is using the Twig tem­plate set­up described in the [An Effec­tive Twig Base Tem­plat­ing Set­up](https://dev.to/gaijinity/an-effective-twig-base-templating-setup-3mf9-temp-slug-3249301) arti­cle, and the <kbd>&lt;div id="component-container"&gt;</kbd> is where the Vue instance mounts:

```

// Home page
import { OutbreakMixins } from '../mixins/outbreak.js';
import { createStore } from '../store/store.js';
import '@trevoreyre/autocomplete-vue/dist/style.css';

// App main
const main = async() => {
    // Async load the vue module
    const [Vue, VueSmoothScroll] = await Promise.all([
        import(/* webpackChunkName: "vue" */ 'vue'),
        import(/* webpackChunkName: "vue" */ 'vue2-smooth-scroll'),
    ]);
    const store = await createStore(Vue.default);
    Vue.default.use(VueSmoothScroll.default);
    // Create our vue instance
    const vm = new Vue.default({
        render: (h) => {
            return h('search-form');
        },
        mixins: [OutbreakMixins],
        store,
        components: {
            'search-form': () => import(/* webpackChunkName: "searchform" */ '../../vue/SearchForm.vue'),
        },
    });

    return vm;
};

// Execute async function
main().then((vm) => {
});

// Accept HMR as per: https://webpack.js.org/api/hot-module-replacement#accept
if (module.hot) {
    module.hot.accept();
}

```

This means that our Vue com­po­nents are not ren­dered until Vue & our com­po­nents are loaded, exe­cut­ed, and mount­ed. How­ev­er the result­ing web­site still per­forms nicely:

![Outbreak database page speed](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x335_crop_center-center_100_line/outbreak-database-page-speed.png)

So it was done this way in a nod to prac­ti­cal­i­ty, but should the client wish to jump to a full JAM­stack set­up in the future, we’re more than halfway home already.

This tech­nique was described in the [Using Vue­JS 2.0 with Craft CMS](https://nystudio107.com/blog/using-vuejs-2-0-with-craft-cms) and [Using Vue­JS + GraphQL to make Prac­ti­cal Mag­ic](https://dev.to/gaijinity/using-vuejs-graphql-to-make-practical-magic-1o2o) arti­cles if you want to learn more.

## Final Thoughts

No project is ever per­fect, espe­cial­ly soft­ware devel­op­ment projects. But I feel like the high­er lev­el deci­sions made helped to improve this project overall.

It’s a good exam­ple of how pick­ing the right bits of tech­nol­o­gy can enable you to cre­ate an improved end result.

## Further Reading

_If you want to be notified about new articles, follow [nystudio107](https://twitter.com/nystudio107) on Twitter._

<small>Copyright ©2020 nystudio107. Designed by nystudio107</small>