DEV Community

Andrew Welch
Andrew Welch

Posted on • Originally published at nystudio107.com on

1 1

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

Disease outbreak database

Relat­ed talk: Solv­ing Prob­lems with Mod­ern 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 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 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.

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:

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

Original outbreak database website

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 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.

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, 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:

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 for access­ing data via Twig, I adopt­ed Vue.js and 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 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

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 of course), and a lit­tle spin­ner shows you so (thanks, vue-sim­ple-spin­ner).

Click­ing on the Search but­ton or hit­ting the Return/​Enter key would smooth­ly auto­scroll (thanks, vue2-smooth-scroll) to view the search results.

Graphql debounced search

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 search?keywords= 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):

Graphql autocomplete search

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. 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 legacy/styles.css and use my nor­mal Tail­wind CSS + PurgeC­SS 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 styles.css 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 with some­thing like Grid­some. 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 arti­cle, and the <div id="component-container"> 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

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 and Using Vue­JS + GraphQL to make Prac­ti­cal Mag­ic 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 on Twitter.

Copyright ©2020 nystudio107. Designed by nystudio107

Heroku

This site is built on Heroku

Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

Get Started

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs