Developer's Guide - ntgomes/vue-coffeemaker GitHub Wiki

Welcome to the VueCoffeeMaker wiki!
This wiki page will provide a high-level explanation of how VueCoffeeMaker works behinds the scenes.
A little knowledge in HTML, CSS, and JavaScript is recommended for reading this guide, but not required.

Table of Contents

Getting Started

Before looking at the code, it would be helpful to know how the project was initially set up. VueCoffeeMaker was created using vue-cli, which is a fast command-line program for easily creating a template for VueJS projects.

To get vue-cli, you must first have npm installed on your computer. npm stands for Node Package Manager, and is the standard for importing modules and libraries within your JavaScript code. With over hundreds of thousands of packages today, npm serves as a repository for functionality that you want in your program that someone else has already coded for you.
To install npm, you should ensure that Node.js is installed on your computer. The download link for that can be found here: https://nodejs.org/en/download/. Node.js will be covered later in this guide.

Once Node.js is installed, you can go into a directory of your choice and open Command Prompt/Terminal at that location. Then type in the following:
npm install -g @vue/cli
You can verify that vue is installed by typing in the following:
vue --version

This leads us back to how VueCoffeeMaker was made; these set of commands (along with the default values for vue init) were used:

# For the sake of this guide, the purpose of 'pwa' in the next line is to make the application work on any browser 
vue init pwa vue-coffeemaker
# Accept all of the default values for the prompts

# After creating the project, navigate to the project folder
cd ./vue-coffeemaker
# Install all packages stated in the package.json file present in the project folder
npm install
# Test run the website
npm run dev

And that is how VueCoffeeMaker was initialized!
As an aside, it's helpful to know how npm installed all of the required components for your program. As noted above, npm uses a file called 'package.json' in your project folder to figure out what it needs to install. In particular, there is a section, called dependencies, that the file contains to indicate installed packages. Below is the dependencies section for VueCoffeeMaker's package.json file:

   "dependencies": {
    "@okta/jwt-verifier": "^1.0.0",
    "@okta/okta-vue": "^1.3.0",
    "@vue/cli-service": "^4.2.2",
    "@vue/eslint-config-standard": "^5.1.1",
    "axios": "^0.19.2",
    "body-parser": "^1.19.0",
    "bootstrap": "^4.4.1",
    "bootstrap-vue": "^2.4.0",
    "cors": "^2.8.5",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.1",
    "eslint-plugin-vue": "^6.1.2",
    "finale-rest": "^1.1.1",
    "jwt-verifier": "^1.0.1",
    "mysql2": "^2.1.0",
    "properties-reader": "^1.0.0",
    "sequelize": "^5.21.4",
    "vue": "^2.5.2",
    "vue-router": "^3.0.1"
  }

Therefore, whenever you call npm install in your project folder, npm will look for what's in the dependencies section of your package.json file and download and install from a network the indicated packages into your project. There's also the dev-dependencies section of package.json, but that's mostly automatically created when running vue init like shown before.
What this does for you while you are implementing your web application is letting you know which packages/libraries you can import into your code. If you are familiar with Maven when developing in Java, then npm's 'package.json' is much like JavaScript's version of Maven's 'pom.xml'.

Now that you know how VueCoffeeMaker was set up, let's dive in to the source code in the repo and figure out how VueCoffeeMaker (and how Vue itself) works behind the scenes.

Frontend

The Gateway into Vue

Most webpages have metadata stored in their index.html file, and for Vue applications, this is no exception. Looking at vue-coffeemaker/index.html, everything inside the <head> tags seems to align with other webpage metadata setups.

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Welcome to the CSC326 CoffeeMaker</title>
    <link rel="icon" type="image/png" sizes="32x32" href="<%= htmlWebpackPlugin.files.publicPath %>static/img/icons/favicon-32x32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="<%= htmlWebpackPlugin.files.publicPath %>static/img/icons/favicon-16x16.png">
    <!--[if IE]><link rel="shortcut icon" href="/static/img/icons/favicon.ico"><![endif]-->

    <!-- The rest of the header tag is setting up cache storage for mobile devices -->

But what's interesting is what is contained within the <body> tags.

    <noscript>
      This is your fallback content in case JavaScript fails to load.
    </noscript>
    <div id="app"></div> <!-- This the entrypoint for the HTML file to inject Vue files -->
    <%= htmlWebpackPlugin.options.serviceWorkerLoader %>

That's everything in the body tag. Just a <noscript> (in case your JavaScript fails for some reason) and a <div>. For traditional web development, the content within the <body> tags should be way more than just a single <div>. But for development with VueJS, that single <div> is all you really need. What's important is that you name the <div> element with the same name as the identifier that VueJS will use. As shown above, that name is "app".

So how does Vue connect to the index.html, and what's up with the <%=htmlWebpackPlugin.options.serviceWorkerLoader %>? How is that handled?
To understand how index.html's <div id="app"> acts as a gateway for VueJS, recall the command that you used earlier to test run the initial set up page for Vue.

npm run dev

When running this command, npm looks for a file in the vue-coffeemaker project folder called build/dev_server.js, and executes its contents using NodeJS syntax. How NodeJS works will be covered later in this guide, but one of the statements in build/dev_server.js is the following:

const webpackConfig = process.env.NODE_ENV === 'testing'
  ? require('./webpack.prod.conf')
  : require('./webpack.dev.conf')

Since we're running npm run dev, the webpackConfig constant will be evaluated to require('./webpack.dev.conf), which points to build/webpack.dev.conf.js.
build/webpack.dev.conf.js has one particular line of interest that will further explain how Vue connects to index.html.

const baseWebpackConfig = require('./webpack.base.conf')

This will point us to the build/webpack.base.conf.js file. It is in the file that we find the final link to the connecting with VueJS.

  entry: {
    app: './src/main.js'
  },

This points us to src/main.js. Inside that file, objects and functions pertaining to Vue will be instantiated and linked to the "app" from <div id="app">. Through the instantiation of the Vue object shown in the below code block, Vue will be connected to index.html.

new Vue({
  el: '#app', // Let Vue verify the div entrypoint by wait of id
  router, // Set up link paths and authentication service
  template: '<App/>',
  components: { App } // The primary component (webpage content) to generate is from App.vue
}); 

Backtracking to build/webpack.dev.conf.js, another purpose of that script is that it has one particular code block of interest that will explain the weird "htmlWebpackConfig" tag from index.html:

   new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true,
      serviceWorkerLoader: `<script>${fs.readFileSync(path.join(__dirname,
        './service-worker-dev.js'), 'utf-8')}</script>`
    }), 

At a high level, this block of code will replace index.html's <%= htmlWebpackPlugin.options.serviceWorkerLoader %> with <script>${fs.readFileSync(path.join(__dirname, './service-worker-dev.js'), 'utf-8')}</script>. The file that is mentioned in that injected <script> tag is build/service-worker-dev.js. The purpose of that file is to reset the web application cache every time you make an edit to the source files while the development server is live. This is a pretty handy supplementary feature to have for web developers.

App.vue : The Centerpiece of Structure in VueCoffeeMaker

Now that you know how VueJS connects to the base index.html file, the next step is to understand how the frontend for VueCoffeeMaker is structured in terms of the .vue files. .vue files are files that serve as an extension to regular HTML/CSS/JS files. In VueJS, these types of files are known as component files.
App.vue, which is located in src/App.vue, is a component file whose structure is somewhat different compared to the rest of the component files that you will view in this project. App.vue serves as a HUB that establishes the connections between the other component files. In most VueJS projects, App.vue serves as the component comprising the home/default webpage, and this is also true for VueCoffeeMaker.

But how does Vue know that App.vue is the default page to load? Recall that in src/main.js, the Vue instance was instantiated like so:

new Vue({
  el: '#app', // Let Vue verify the div entrypoint by wait of id
  router, // Set up link paths and authentication service
  template: '<App/>',
  components: { App } // The primary component (webpage content) to generate is from App.vue
}); 

components: { App } tells Vue that the only component file to load is a .vue file call App, which translates to App.vue.

But there are multiple component files in this project, so why aren't they considered in that instantiation as well? This is because in a single-page application (SPA), Vue only needs one component file, and will assume that same component file will handle the rendering of the rest of the components. As you will see, App.vue will handle the loading of the other Vue components, so for now, components: { App } is sufficient.

Now let's dive into App.vue and see how that works. As aforementioned, .vue files are just extensions of .html files and are the essential structures for Vue performs. Unlike .html files, .vue wraps <html> and <body> tags within a <template> tag. This allows for flexible design for copying over designs, and reduces the boilerplate in the code, as everything you were going to write in a <template> was going to go into <html> and <body> tags anyways.

Let's first consider everything in App.vue's <template> tags:

<template>
  <div id="app">
    <b-row>
      <b-col>
        <h1 class='cm-home-header'>Coffee Maker</h1>
        <h4>Available Options</h4>
        <ul>
          <li>
            <router-link to="/about">About CoffeeMaker</router-link>
          </li>
          <li>
            <router-link to="/editInventory">Update Inventory</router-link>
          </li>
          <li>
            <router-link to="/addRecipe">Add a Recipe</router-link>
          </li>
          <li>
            <router-link to="/deleteRecipe">Delete Recipe</router-link>
          </li>
          <li>
            <router-link to="/editRecipe">Edit a Recipe</router-link>
          </li>
          <li>
            <router-link to="/makeCoffee">Make Coffee</router-link>
          </li>
        </ul>
        <hr />
      </b-col>
      <b-col cols="8">
        <template v-if="$route.matched.length">
          <router-view></router-view>
        </template>
      </b-col>
    </b-row>
  </div>
</template>

Notice the pattern of <router-link> list items.
What's <router-link> in this context? Basically, <router-link> is Vue's version of HTML's <a> tag. It's set up in a very different way, but once that setup is done, <router-link> essentially looks and performs like a very fast <a> tag.

<router-link> will play a key part into rendering the <router-view> tag above, but before we cover <router-view>, let's go over how the setup for <router-link> is done.
For a given <router-link> tag, note the attribute that follows it:

<router-link to="/about">About CoffeeMaker</router-link>

Where is the to attribute's "/about" string coming from? To answer that, we must backtrack to the Vue instance in main.js:

import router from './router';

/*
  ... Code for main.js
*/

new Vue({
  el: '#app', // Let Vue verify the div entrypoint by wait of id
  router, // Set up link paths and authentication service

In short, the router object that was imported from "./router" that will establish the connections that App.vue will use for the <router-link> tags.

"./router" points to src/router/index.js, and inside that file, we see a constant variable called router being instantiated.

Vue.use(Router);

const router = new Router({
  mode: 'history',
  base: __dirname,
  routes: [
    { path: '/about', component: About },
    {
      path: '/editInventory',
      component: EditInventory
    },
    {
      path: '/addRecipe',
      component: AddRecipe
    },
    {
      path: '/deleteRecipe',
      component: DeleteRecipe
    },
    {
      path: '/editRecipe',
      component: EditRecipe
    },
    {
      path: '/makeCoffee',
      component: MakeCoffee
    }
  ]
});

export default router;

This router is a JavaScript object, and one of its properties are routes, which are a list of objects with one of their properties being a path string. This path string matches up the string that was from <router-link>'s to attribute. One of its other properties is the component property, which links to a corresponding component file. For example, About.vue would be connected to the router by having a path called '/about' and the component be About.

So in short, src/router/index.js sets up the paths that App.vue's <router-link> uses to create proper navigation to the other component files for VueCoffeeMaker.

Now that we went over the majority of what src/router/index.js does, let's go over the last element of importance in App.vue's <template> tags: <router-view>. The way it's used in App.vue is as follows:

<template v-if="$route.matched.length">
  <router-view></router-view>
</template>

The way the nested <template> tags are being used here is much like how HTML handles rendering with <iframe>. The exception though is the additional attribute to these <template> tags, v-if. VueJS, much like other frontend frameworks like Angular and React, does conditional rendering. Given one of the conditional attributes (such as v-if, v-else, v-if-else, etc.), VueJS will check to see if the tag it's attached to will render based on the boolean on that conditional attribute.
With how it's used above, Vue will check if the reserved variable $route has a length greater than 0. <router-link> will set the $route variable automatically if its to attribute is correctly setup.

With that explained, <router-view> will simply render the component that $route points to with the space that the CSS allows for it. This is how most SPA applications will work when working with routers.

Speaking of CSS, you may have noticed that App.vue's HTML portion also uses some other special tags, such as <b-row> and <b-col>. These come from BootstrapVue. BootstrapVue (or BV) is much like Bootstrap; it is a CSS library for VueJS offering plenty of elements that can be injected into HTML to make your website have a modernized look. The way it's being used here is to space out a good portion of the screen to hold the <router-view> content.

Finally, let's cover the <script> portion of App.vue. Almost all component files require some form of JavaScript to piece together local variables. The <script> tags contain the following:

export default {
  name: 'app'
}

Vue component files usually require their scripts to be in a format similar to the format above. What is wrapped in the export default is an object with some of its properties (called options) defined. For the extent of VueCoffeeMaker, Vue keeps track of the following options:

  • name: the name of the Vue component that the script is derived from; usually used for recursive component files (like components that hold menu/tabular items) and debugging.
  • data(): a function that generally returns what variables the template has access to for conditional rendering and other variable behaviors. Without this, you are losing the point of using Vue in the first place.
  • created(): a function that runs once a component file has been loaded
  • computed: a class holding some methods that are used for form validation and for conditional rendering
  • watch: an object whose properties will run a function once they have been changed or accessed
  • methods: a class that holds methods that can be invoked by the rest of the script

For App.vue, the only data that needs to be accessed by the HTML portion is the loggedIn variable, which the template will use to indicate whether or not a user is logged onto VueCoffeeMaker. There is also a created() function that is set, so everytime a user looks the SPA, they will be asked to log on.

There is also the watch option being used, and so App.vue will return a check for the login status of the user every time they navigate to a different component file. There is the computed option, which holds functions that the HTML can use; this is contrasted by the methods option, which can be only used inside the <script> tags.

And that's how App.vue is structured! Next, we will cover how all of the VueCoffeeMaker components work in the backend.

The Components of VueCoffeeMaker

From the user's perspective, App.vue doesn't do much. It serves to hold the links to the important parts of the SPA; specifically, the links to edit the inventory, add a recipe, edit a recipe, delete a recipe, and make a coffee order. Thus, there are component files that correspond to each of these features for the SPA.

Note: For a user to access the content of any component file other than About.vue, they must be logged in using Okta Authentication services. Okta Authentication will be covered later in this guide.

Many of the above component files have very similar design patterns when rendering to the user, so instead of going over each component file, covering each of the few common design patterns would be more effective.

BootstrapVue Forms
Since VueCoffeeMaker is all about gathering a group of inputs from the user when they click "Submit", forms are a very easy way of accomplishing this functionality. Using AddRecipe.vue as an example, here are how forms are usually formatted in BV:
     <b-form @submit="onSubmit" @reset="onReset" v-if="show">
        <b-form-group id="input-group-1" label="Name:" label-for="input-1">
          <b-form-input
            id="input-1"
            v-model="model.name"
            type="text"
            required
            placeholder="Enter recipe name"
          ></b-form-input>
        </b-form-group>
        <b-form-group id="input-group-2" label="Price:" label-for="input-2">
          <b-form-input
            id="input-2"
            v-model="model.price"
            type="number"
            required
            placeholder="Enter recipe price"
          ></b-form-input>
          <b-form-invalid-feedback :state="validatePrice">
            Amount must be greater than 0.
          </b-form-invalid-feedback>
        </b-form-group>
        <b-form-group id="input-group-3" label="Coffee:" label-for="input-3">
          <b-form-input
            id="input-3"
            v-model="model.coffee"
            type="number"
            required
            placeholder="Enter amount of coffee"
          ></b-form-input>
          <b-form-invalid-feedback :state="validateCoffee">
            Amount must be greater than 0.
          </b-form-invalid-feedback>
        </b-form-group>
        <b-form-group id="input-group-4" label="Milk:" label-for="input-4">
          <b-form-input
            id="input-4"
            v-model="model.milk"
            type="number"
            required
            placeholder="Enter amount of milk"
          ></b-form-input>
          <b-form-invalid-feedback :state="validateMilk">
            Amount must be greater than 0.
          </b-form-invalid-feedback>
        </b-form-group>
        <b-form-group id="input-group-5" label="Sugar:" label-for="input-5">
          <b-form-input
            id="input-5"
            v-model="model.sugar"
            type="number"
            required
            placeholder="Enter amount of sugar"
          ></b-form-input>
          <b-form-invalid-feedback :state="validateSugar">
            Amount must be greater than 0.
          </b-form-invalid-feedback>
        </b-form-group>
        <b-form-group id="input-group-6" label="Chocolate:" label-for="input-6">
          <b-form-input
            id="input-6"
            v-model="model.chocolate"
            type="number"
            required
            placeholder="Enter amount of chocolate"
          ></b-form-input>
          <b-form-invalid-feedback :state="validateChocolate">
            Amount must be greater than 0.
          </b-form-invalid-feedback>
        </b-form-group>
        <b-button type="submit" variant="warning">Submit</b-button>
        <b-button type="reset" variant="danger">Reset</b-button>
      </b-form>

<!-- In the scripts... -->
  data () {
    return {
      model: {},
      show: true,
      submitted: false,
      success: false
    }
  },
  computed: {
    validatePrice () {
      return this.model.price > 0;
    },
    validateCoffee () {
      return this.model.coffee > 0;
    },
    validateMilk () {
      return this.model.milk > 0;
    },
    validateSugar () {
      return this.model.sugar > 0;
    },
    validateChocolate () {
      return this.model.chocolate > 0;
    }
  },
  methods: {
    onSubmit (evt) {
      evt.preventDefault();
      this.createRecipe();
    },
    onReset (evt) {
      evt.preventDefault();
      // Reset our form values
      this.model.name = '';
      this.model.price = '';
      this.model.coffee = '';
      this.model.milk = '';
      this.model.sugar = '';
      this.model.chocolate = '';
      // Trick to reset/clear native browser form validation state
      this.show = false;
      this.$nextTick(() => {
        this.show = true;
      });
      this.submitted = false;
    }
  }
Loading Card
We want the user to be able to know when the system is still processing information from database/backend operations. Thus, a simple loading indicator using BV's b-alert along with some conditional rendering will do the trick.
<b-alert :show="loading" variant="info">Loading existing recipes...</b-alert>

<!-- In scripts -->
  data () {
    return {
      loading: true,
  //...
  methods: {
    async refreshRecipeList () {
      this.loading = true;
      this.recipes = await api.getRecipes();
      this.loading = false;
      this.selected = '';
    }
    //...
  }
Conditional Loading
It would be a pain for the developer to hardcode a way to render a list of similar objects without knowing the size of the list. Luckily, Vue's conditional rendering takes care of that for us with v-for and v-bind.
        <b-form-radio 
          :show="!loading" 
          v-for="recipe in recipes" 
          v-bind:key="recipe.id" 
          v-model="selected" 
          :value="recipe.id">
            {{recipe.name}}
        </b-form-radio>
        <b-card-text v-if="recipes.length==0 && !loading">No recipes found.</b-card-text>

And that is basically all of the design patterns used to essentially construct every component of VueCoffeeMaker.

But so far, this only covers the rendering of the content to the screen and, for a web application, handling operations at the level of the client infrastructure.
So where is VueCoffeeMaker handling recipe and inventory operations from? Where is that information stored? If it's not a part of the client infrastructure, how does the SPA communicate with it?

To answer this question, we'll need to go into backend.

Backend

server.js : Working with Node.js

At a high level, VueCoffeeMaker starts up and runs its backend separately (that is, on a separate port of the same IP address). It does this by executing the contents of src/server.js.

server.js utilizes Node, Express, Sequelize, and Finale to carry out its functionality. Let's briefly go over what these technologies do at a high level:

  • Node: According to EloquentJavaScript, Node is "a program that allows you to apply your JavaScript skills outside of the browser. With it, you can build anything from small command line tools to HTTP servers that power dynamic websites.... Node was initially conceived for the purpose of making asynchronous programming easy and convenient." Node, or Node.js, is what we will use to make part of our computer run as a server for VueCoffeeMaker.
  • Express: Because Node is set up to create regular JavaScript programs, its general use case means that it's not suited to make HTTP servers without a lot of additional boilerplate code. Express was made to solve that issue and make setting up a server with Node very easy. By importing and using the Express framework, Node will automatically be given the tools that most modern web applications use for startup, maintenance, and shutdown.
  • Sequelize: Sequelize is a database framework for JavaScript web applications, and allows the developer to easily connect and perform SQL operations on an existing database. It also supports a plethora of SQL dialects, such as MySQL, SQLite, Teradata, Oracle, etc..
  • Finale: Finale is a REST API framework often used in conjunction with the latest versions of Sequelize and axios. A very helpful explanation for REST API can be found in this video. Essentially, Finale helps the developer to create clean code to manage REST API and HTTP operations for Create-Read-Update-Delete (CRUD) applications.

With that all being explained, let's go over what server.js does:

  1. Import all necessary dependencies:
    We'll first need to import all of the necessary packages to make the server function properly. As you may have seen, many of our .js files were doing this, but were using import XXX from './XXX'. But .js using Node need to import dependencies a bit differently using require():
const express = require('express');
const cors = require('cors');   // For sending object data
const bodyParser = require('body-parser');   // For parsing object data
const PropertiesReader = require('properties-reader');  // For reading secure information
const Sequelize = require('sequelize');
const finale = require('finale-rest');
  1. Set up authentication in the database:
    The server will then start up its authentication services for database connections. It does this with PropertiesReader, which were imported from the last step.
const pwProp = PropertiesReader(`${__dirname}/props/secureInfo.properties`);
  1. Set up Express:
    When you instantiate the express framework, you usually have to specify the Cross-Origin Resource Sharing (CORS) protocol and the resource parsing method. Nowadays, we generally use cors and body-parser libraries since they use a standard setup for these protocols and frees us from the worry of properly setting these up for web applications.
const app = express();
app.use(cors());
app.use(bodyParser.json());
  1. Processing HTTP requests:
    At a high level, HTTP is a famous protocol for sending requests over a network. It utilizes GET, PUT, POST, and DELETE types of requests to fetch, update, add, and delete objects from a database table respectively. The following block of code will allow us to run a request once the server receives it.
app.use((req, res, next) => {
  next();
});
  1. Connect to the cm-vue database using Sequelize:
    The cm-vue database was instantiated in MySQL Workbench, and so all we have to do is let Sequelize know about its general properties, as well as the username and password to the MySQL instance. We also test this connection with the authenticate() method. Note that the password is in a properties file that is not located in this repo.
const database = new Sequelize('cm-vue', 'root', pwProp.get('sequelize.db.password').trim(), {
  host: 'localhost',
  port: 3306,
  dialect: 'mysql'
});

// Confirm the connection
database.authenticate().then(() => {
  console.log('Successfully connected to cm-vue database');
}).catch((err) => {
  console.log(`Connection to database failed: ${err}`);
  throw err;
});
  1. Set up models for use in the database:
    Once we have successfully authenticated the connection to the cm-vue database, we'll need to initialize the two models/objects that are used in VueCoffeeMaker: Inventory and Recipe. For ease of access and readability, these models are defined in src/models/Inventory.js and src/models/Recipe.js respectively, and the way their structured is pretty self-explanatory.
const Inventory = require('./models/Inventory');
const Recipe = require('./models/Recipe');

Inventory.init(database, Sequelize);
Recipe.init(database, Sequelize);
  1. Set up Finale to allow REST endpoints to work with Inventory and Recipe:
    Now that the Inventory and Recipe models are set up in the database, Finale should now be set up to work with these models through HTTP protocols. Keep in mind the names of the endpoints that are part of this initialization:
finale.initialize({
  app,
  sequelize: database
});

// Create the dynamic REST resource for our Inventory model
finale.resource({
  model: Inventory,
  endpoints: ['/inventory']
});
finale.resource({
  model: Recipe,
  endpoints: ['/recipes', '/recipes/:id']
});
  1. Ready the database and start the server:
    Now that everything in this script has been initialized, it's time to ready the database and start the server. First, we ready the database by running the sync() command, which forces to database to save and refresh with all recent information. Then, we use Node+Express to begin listening to requests from the REST API. Afterwards, we set up the default values in the Inventory for VueCoffeeMaker and inform the user that everything is ready.
database
  .sync({ force: true })
  .then(() => {
    app.listen(8081, () => {
      console.log('Listening to port localhost:8081');
    });

    // Set up the initial database environment; reset inventory ingredients
    Inventory.create({
      chocolate: 15,
      coffee: 15,
      milk: 15,
      sugar: 15
    }).then((inventory) => {
      console.log('Set default values to Inventory');
      console.log('Server successfully started');
    }).catch((err) => {
      console.log(`Could not set default values: ${err}`);
    });
  });

And that's all that server.js does. To run the server, we go to the project folder and run:

node ./src/server

As aforementioned, this will start the server (backend) at localhost:8081, while the frontend will run in localhost:8080. It is extremely important that both ports are not the same.

But where is the logic for sending HTTP requests to the server? This leads us to our next section of the guide.

api.js : Working with REST API

The remaining .js file of importance is src/api.js, which is located here.
api.js doesn't utilize a Node.js architecture, but its job is to be provide a class for component file scripts to make HTTP requests. In the overall SPA architecture, api.js would serve as the "middleware" script.

Let's go over how it works:

import Vue from 'vue';
import axios from 'axios';

const client = axios.create({
  baseURL: 'http://localhost:8081/',
  json: true
});

The above code imports Vue and axios to set up the middleware between the frontend and the backend. axios is the de facto package for handling the creation of an HTTP request. Note that baseURL points to the server's port and that json is set to true so that data being encrypted and decrypted is of JSON format.

export default {
  async execute (method, resource, data) {
    return client({
      method,
      url: resource,
      data
    }).then(req => req.data)
  },

The next code block above is to set up the behavior for transforming and sending a JSON into a JWT anytime the execute() method is called. execute() requires a method (the HTTP request type), the resource to affect (the endpoint), and (possibly) the data (which is dependent on the HTTP request type). The way its described now may be a bit confusing, but as we cover the next block of code, it will be clear.

  // API calls for Inventory
  getInventory () {
    return this.execute('get', '/inventory');
  },
  updateInventory (inventoryData) {
    return this.execute('put', '/inventory/', inventoryData);
  },
  // API calls for Recipe
  getRecipes () {
    return this.execute('get', '/recipes');
  },
  getRecipe (id) {
    return this.execute('get', `/recipes/${id}`);
  },
  createRecipe (recipe) {
    return this.execute('post', '/recipes', recipe);
  },
  deleteRecipe (id) {
    return this.execute('delete', `/recipes/${id}`);
  },
  editRecipe (id, newRecipe) {
    return this.execute('put', `/recipes/${id}`, newRecipe);
  }

The above code block is describes the different HTTP that component file scripts can call to execute the corresponding API call for axios:

  • getInventory(): An HTTP GET request; returns the single inventory object from the database
  • updateInventory(): An HTTP PUT request; given an inventory object, the inventory's fields in the database will be updated with the given inventory's fields.
  • getRecipes(): An HTTP GET request; returns a list of all recipe objects in the database
  • getRecipe(id): An HTTP GET request; given an integer ID, the method will return a recipe object of the corresponding ID
  • createRecipe(recipe): An HTTP POST request; given a recipe, it will add that recipe to the Recipes table in the database
  • deleteRecipe(id): An HTTP DELETE request; given an integer ID, the recipe that has a matching ID in the database will be deleted from the database
  • editRecipe(id, newRecipe): An HTTP PUT request; given an integer ID and a given recipe object, the recipe with the matching ID in the database will be updated based on the fields of the given recipe.

Note that the resource parameter in the execute() call looks very similar to the endpoints created in server.js's Finale initialization. And that's the point; that's how the HTTP request will properly target the right endpoint and make MySQL calls on it using Sequelize.

And that's how it's all connected; the frontend component file scripts will import api.js and make REST API calls when necessary. api.js will interpret that request and set up the JSON and JWT to send to the server. Once the server authenticates the JWT and converts it back to JSON, it will use Sequelize to make the corresponding operations to the database models.

That basically explains how VueCoffeeMaker works behind the scenes. The only other topics are covering how Okta Authentication works and to go over what is currently missing/incomplete in the current version of VueCoffeeMaker.

⚠️ **GitHub.com Fallback** ⚠️