This guide will go through the steps required to setup a vue-cli applicaiton that works with a Coldfusion/Lucee powered JSON backend.

It wouldn’t take much to swap out the backend for static JSON, PHP or anything other language. The point of the guide is to get a proxy setup so the local node development server on one port (e.g. npm server launching 127.0.0.1:4000) can happily consume the JSON data being supplied from another port (e.g. commandbox start launching 127.0.0.1:8080 )

This is going to allow fast application development in Vue.js with vue-cli, making use of hot module replacement that comes with the underlying webpack, alongisde my preferred method of back end scripting, which is all written in .cfc files and returning JSON data.

At the end we’ll go over getting this into production, on a DigitalOcean based Ubuntu server using Nginx. Again, it won’t take much tweaking to get this working on any provider, any OS, any webserver. The concepts should carry across all platforms, the key thing here is getting the local development proxy working.

Getting the back end started

I’m using Ortus Solutions’ CommandBox CLI on windows to easily launch a basic CFML site that returns some data in JSON format.

In the past I’ve generally worked with Adobe Coldfusion but for this project I’ll be using Lucee which is described as the following :

“Light-weight dynamic CFML scripting language with a solid foundation”

The main thing to note is it’s free and open source so you do not need to buy a license from Adobe to use CFML/CFScript. Only if you specifically want to use Adobe Coldfusion.

You can download CommandBox (with or without a JRE) as a zip, once you’ve unpacked it you can double click the Box.exe to launch the CommandBox window

CommandBox default window

I prefer to run this via Windows PowerShell and the built in terminal in VSCode, which can be achieved by adding CommandBox to your Windows path.

The Ortus Solutions site helpfully has a decent installation section with a link to another guide for setting up your Windows Path: CommandBox Installation

Once CommandBox is installed and running, you can create a very basic file to serve some JSON content. Put this api.cfc somewhere on your filesystem with the following content:

component {
    remote function getPeople() {
        return [
            "Pete",
            "Picard",
            "Sisko",
            "Janeway"
        ];
    }
}

This funciton simply allows “remote” access via the browser and returns the array of people. In CommandBox, move to the directory containing this file and run start port=8080 (or box start port=8080 if you’re not inside the Command Box program ).

Once that is succesfully running you should be able to access the CFC in the browser and see the JSON response:

CommandBox default window

Open this URL to view the JSON:

http://127.0.0.1:8080/api.cfc?method=getPeople&returnFormat=json

Creating a vue-cli app

Now the backend is serving JSON, we can start building the Vue app.

You’re going to need to have npm installed and then install vue-cli. I wont go into details on how to do all that as the Vue CLI documentation covers that for us, you can check it out here

Note: npm would error during installation unless I ran powershell as administrator. Once my packages are installed I closed and re-opened it as normal

Using the command line (in my case Powershell) go to the directory you want to create your project in and type vue create .. Alternatively you can make a new folder with vue create vue-cf-together

For this app we’ll assume you’re working with the folder /vue-cf-together

Once you’ve triggered the create command, you’ll need to select features the features you want to use. You’ll be asked to choose from a preset, or manually select features.

Choose to manually select, then ideally save this to your own preset so you don’t have to do this every time.

You’ll need to select the following features

  • Babel
  • CSS Pre-processors
  • Linter / Formatter
  • Unit Testing

Optionally add Router or Vuex if you know you’re planning to use them (this guide doesn’t) but any feature can be added later on with vue add plugin-name

For the “CSS Pre-processor” select Sass/SCSS (with dart-sass)

For the “linter / formatter config” select ESLint + Airbnb config for additional features choose Lint on save

For the “unit testing framework” choose “Jest”

Select In dedicated config files for plugin settings

vue-cli feature selection

Press y, hit enter, enter a name for your preset and hit enter again.

vue-cli should start building the project for you.

When it’s finished you should be able to run npm run serve and see the default app in your browser (you might need to cd vue-cf-together first depending on where you created your project)

Also not the command line will display the URL with the port number to access your Vue app.

vue-cli server started

Making a basic app

Modify the top level App.vue file which is in the vue-cf-together/src/ folder. Get rid of that default template and comment out the HelloWorld references for now, we’ll change them in a bit.

Something like this is fine for getting started:

<template>
    <div id="app">
        <h1>Awesome Apps.com</h1>
    </div>
</template>

<script>
// import HelloWorld from './components/HelloWorld.vue';

export default {
    name: 'app',
    components: {
    // HelloWorld,
    },
};
</script>

<style lang="scss">
    #app {
        background-color: salmon;
    }
</style>

With npm run serve still running you should see a quick compile in the console and the page should reload in the browser itself to show this wonderful page:

Basic vue app

If you are seeing that, great! If you’ve played around with the template (or copy and pasting hasn’t quite worked) then you might get a bunch of errors like this:

Airbnb Linting

This is because we selected the “Airbnb linting” which checks your code to make sure it follows the very popular Airbnb Javascript Style Guide.

Now you don’t have to do this but I recommend it as it helps to minimize mistakes as you work and you can customise the rules (2 vs 4 spaces, camelCase vs underscore_variables etc)

Just try and fix the errors that are listed and they’ll show up as you make changes (lint on save!) so you can deal with them as you go.

Adding a Coldfusion/Lucee API

We’re getting to the best part now, getting your locally served CFScript AJAX API pumped into your vue-cli app.

We need to create a proxy server and a project path for the vue-cli server to find the Coldfusion API, as they’re being served on different ports (by npm/node and commandbox/lucee or something similar)

Create a vue-cf-together/vue.config.js file to include the following proxy settings in a devServer block.

This will redirect API calls to the Lucee server and for development allow us to ignore https/CORS issues.

module.exports = {
    devServer: {
        proxy: {
            '/api.cfc': {
                target: 'http://127.0.0.1:8080/',
                secure: false,
            }
        }
    }
};

If you get that setup properly, then we’ll be able to visit this in the browser: http://127.0.0.1:8081/api.cfc?method=getPeople&returnFormat=json

This URL is almost exactly the same as the Lucee server itself apart from:

  • port 8081 is serving the vue-cli app via node/npm
  • port 8080 is serving the Lucee backend.

So we’ve told the vue app to send certain requests to a different port, in this case the api.cfc requests.

Calling the API from the application

We’ll make use of a second vue file to make calls to the API using https://github.com/axios/axios

Rename vue-cf-together/src/components/HelloWorld.vue to People.vue then update the App.vue so it’s referencing the new component.

    <template>
        <div id="app">
            <h1>Awesome Apps.com</h1>
            <!-- include our people component -->
            <People />
        </div>
    </template>
    
    <script>
    // import the file
    import People from './components/People.vue';
    
    export default {
        name: 'app',
        components: {
            // list the component in the app definition
            People,
        },
    };
    </script>
    
    <style lang="scss">
    #app {
      background-color: salmon;
    }
    </style>

Then update People.vue to contain the code below:

    <template>
        <div>
            <h2>People</h2>
            <div v-if="!arr_people.length">There is nobody here</div>
            <ul>
                <li :key="person" v-for="person in arr_people"></li>
            </ul>
        </div>
    </template>

    <script>
    export default {
    name: 'People',
    data: () => ({
        arr_people: [],
    }),
    };
    </script>

Make sure npm run serve is running and the browser should show you this even more amazing app, with the People.vue component rendered:

Vue empty People component rendered

Stop the npm server and run npm install axios axios-cancel --save to install axios and the cancel library.

axios will make calls to the api, axios-cancel is used used to stop race conditions with ajax calls.

Then update the People.vue file to be like this, which will add buttons to add/remove people and make an AJAX call to get the CF file’s getPeople method

    <template>
        <div>
            <h2>People</h2>
    				<div class="warning" v-if="!arr_people.length">There is nobody here</div>
            <ul>
                <li :key="person" v-for="person in arr_people"></li>
            </ul>
            <button @click="getPeople">Get People!</button>
            <button @click="removePeople">Remove People!</button>
        </div>
    </template>
    
    <script>
    import axios from 'axios';
    import axiosCancel from 'axios-cancel';
    
    axiosCancel(axios, {
        debug: false, // default
    });
    
    export default {
        name: 'People',
        data: () => ({
            arr_people: [],
        }),
        methods: {
            async getPeople() {
                // name the request
                const requestId = 'get_people';
                // cancel existing request
                axios.cancel(requestId);
                // try and load people
                try {
                    const response = await axios({
                        url: '/api.cfc?method=getPeople&returnFormat=json',
                        method: 'GET',
                        requestId,
                    });
                    // set people to the data property
                    this.arr_people = response.data;
                } catch (err) {
                    // handle errors in the real app
                }
            },
            removePeople() {
                this.arr_people = [];
            },
        },
    };
    </script>

Clicking “Get People!” and “Remove People!” should toggle the app between these 2 states (making an AJAX call visible in the dev console with each “Get People!” click)

Vue app making Lucee API calls via axios

Now we have a perfect app, with hot module reloading and a proxy server to our Coldfusion API, we’re almost ready to build for production and get this app embedded inside our main website!

Unit testing

So ideally, you should have designed your tests before hand. You want to be writing them first, so you know what you want, watching them fail, then fixing your code till they all pass.

Maybe not quite as strict as that, but in general, make sure your writing tests and if you find a bug, write a test, watch it fail, fix the code, watch it pass.

We’re going to test that our People.vue loads, renders an empty list and shows a warning,.

Edit the tests/unit/example.spec.js file to look like this:

import { shallowMount } from '@vue/test-utils';
import People from '@/components/People.vue';

describe('People.vue', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = shallowMount(People);
  });

  it('renders a People header', () => {
    expect(wrapper.find('h2').text()).toMatch('People');
  });

  it('renders an empty list and shows warning', () => {
    expect(wrapper.find('.warning').text()).toMatch('There is nobody here');
    expect(wrapper.findAll('li').length).toEqual(0);
  });

});

Then run npm run test:unit (you might have to stop your npm run serve command) and hopefully you’ll see an output like this, which shows:

  • 2 tests have passed (the it functions)
  • one of the tests had 2 conditions (expect) that both passed

Unit tests passing

I dont’ want to spend too long on unit tests here, but these can and probably should get much bigger and more complex. I’m still not great at testing, don’t add enough, but I’m doing more than I used to and it’s massively helpful.

In short, the above tests do the following:

  • we mount the People component into wrapper
  • we want to know the “People header” is rendered
    • expect(wrapper.find('h2').text()).toMatch('People');
    • we’re expecting to find an h2 with the text “People”
  • we want to know an empty list is shown with a warning
    • expect(wrapper.find('.warning').text()).toMatch('There is nobody here');
    • we’re expecting to find an elemenet with class “warning” that contains the text “There is nobody here”
    • expect(wrapper.findAll('li').length).toEqual(0);
    • we’re expecting a search for all li to come back empty

Building for production

To make sure our tests are always run when building for production, add "prebuild": "npm run test:unit", to the scripts section of the package.json file to make sure our unit tests always get run before we make a production build.

Then you can run npm run build which should build a deployable version of our app into the dist/ folder, also making an index.html file for us.

The contents of dist can be served alongside your api.cfc on your CFML/Lucee/Coldfusion server in production, as the compiled .html/.js files don’t need Node, just a normal webserver (Apache/Nginx etc).