Build a Quote Application Using Laravel and Vue: Part 2
Tags: vue, laravel, webdev, webpack
Published: 2018-04-13 07:00:49

---
title: "Build a Quote Application Using Laravel and Vue: Part 2"
published: true
tags: vue,laravel,web-development,webpack
canonical_url: 
---

![](https://cdn-images-1.medium.com/max/625/1*L9ZGzB55ZHhXYG-VV4Uz9A.jpeg)<figcaption>Quote Application Part 2</figcaption>

In the [first part](https://dev.to/yemiwebby/build-a-quote-application-using-laravel-and-vue-part-1-3apg) of this series, we were able to successfully build the backend of our Quote Application using Laravel. We went ahead to also set up endpoints to post, fetch, update, and delete quote from our database. And lastly, we tested the functionality of our API by using a tool called&nbsp;[Postman](https://www.getpostman.com/).

In this article, we will complete the Quote application, by building the frontend with Vue.js. Here, we want to be able to achieve the following&nbsp;:

1. Post a new quote to the&nbsp;server
2. Retrieve all quotes after they have been&nbsp;saved
3. And finally, edit and delete&nbsp;quotes.

Just before we proceed, open up the source code from the first part of the series in a code editor of your choice and run the application. It is important to keep this running as one of the objective of this tutorial is to ensure smooth communication between the Backend and Frontend on separate domains. The complete code for both application can be found on Github, scroll to the end of this tutorial to access the&nbsp;links.

### Let’s Build the&nbsp;Frontend

An HTTP call will be required to access all the resources from the backend. For this, we will make use of [Axios](https://github.com/axios/axios) which is a Promised based HTTP Client for the browser and node.js, but first, let’s install Vue. [Vue-cli](https://github.com/vuejs/vue-cli) will be used here, as it will help us to rapidly scaffold Single Page Application in no&nbsp;time.

```
# install vue-cli
$ npm install -g vue-cli
```

Next, we’ll set up our Vue app with the&nbsp;CLI.

```
# create a new project using the "webpack" template
$ vue init webpack-simple frontend-quote-app
```

You will be prompted to enter a project name, description, author and others. This should initialize our app, all we have to do now is, change directory into our project folder and install the required dependencies.

```
#change directory
$ cd frontend-quote-app

#install dependencies
$ npm install
```

Finally, to serve the application, run

```
# run the application
$ npm run dev
```

A similar page like the image below should open up in your browser by&nbsp;now

![](https://cdn-images-1.medium.com/max/1024/1*QMg6M5k869x8FMJ14yVEng.png)

### Components

Since Vue offers developers the ability to use components driven approach when building web apps, we will create more components for our quote application. Vue CLI already generated a main component that can be found in src/App.vue, this will be used as the top level component for our application.

### Creating a Component

Apart from the default component generated by Vue CLI, we will need more component, namely, 'new-quote.vue', quotes.vue', quote.vue'&nbsp;. These components will be used to add a new quote, display all quotes and moreso, be able to edit and delete&nbsp;quote.

Ready? Let’s get to&nbsp;work!.

Proceed to create a&nbsp;./src/components folder, which will hold all the components that we will create&nbsp;soon.

Create more component JS files such as quote.vue&nbsp;, quotes.vue&nbsp;, new-quote.vue within the components folder.

### Install NPM&nbsp;Modules

As we are required to make web requests (API calls) within all the components created above, install&nbsp;[Axios](https://github.com/axios/axios)).

```
npm install axios --save
```

And for routing purposes, let’s also install [Vue-router](https://github.com/vuejs/vue-router)

```
npm install vue-router --save
```

### Configure Components

The required tools and components files have just been created, next is to start configuring these files by creating individual template, logic and&nbsp;style.

First of all, clean up the default contents within&nbsp;./src/App.vue&nbsp;. This will be filled&nbsp;later.

```
<template>

<div id="app">
...
</div>

</template>

<script type="text/babel">

export default {

data () {

return {

}
  }
}
</script>

<style lang="scss">
...
</style>
```

### New-quote.vue

This component is responsible for adding new quote(s). Whenever the form for posting a new quote is submitted, a function 'onSubmitted will be called and executed.

```
<template>
   <div>
        <div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
            <form @submit.prevent="onSubmitted">
                <div class="form-group">
                    <label for="content">
                        <b>Quote</b>
                        </label>
                        <br>
                    <i> Write your quote</i>
                    
                    <textarea name="" id="content" class="form-control" v-model="quoteContent" cols="80" rows="6"></textarea>
                </div>

                <div class="form-group">
                    <button type="submit" class="btn btn-success">
                        Submit
                    </button>
                </div>
            </form>
        </div>
    </div>
</template>

<script type="text/babel">
    ...
</script>

<style scoped>
...
</style>
```

This function sends an HTTP request to the server (Laravel backend) with the quote data and stores it in the database. This pattern is similar to what we have in other components as will be revealed very&nbsp;soon.

```
<template>
...
</template>
<script type="text/babel">
    import axios from 'axios';
    export default {
        data() {
            return {
                quoteContent: ''
            }
        },
        methods: {
            onSubmitted() {
                axios.post('http://localhost:8000/api/quote',
                        {content: this.quoteContent})
                        .then((response) => {
                        window.location.href = "/";
                        })
            .catch ((error) => console.log(error)
            )}
        }
    }
</script>

<style scoped>
...
</style>
```

Notice the URL and endpoint being called by axios within the onSubmitted() method http://localhost:8000/api/quote? Remember the Laravel backend was started at the beginning of this tutorial, it is assumed that this application is running on localhost port 8000. Kindly change this URL if your backend is running on a different port.

And style

```
<style scoped>
#content {
    margin-top: 40px;
}
</style>
```

### Quotes.vue

This is the parent component for quote component. Props defined here are are used to passed down information to the child component.

```
<!-- quotes.vue -->
<template>
    <div>
        <div class="text-center">
            <button class="btn btn-success" @click="onGetQuotes">
            Get Quotes
             </button>
        </div>
        <hr>

<app-quote v-for="quote in quotes" :qt="quote" :key="quote.id" @quoteDeleted="onQuoteDeleted($event)"></app-quote>
    </div>
</template>

<script type="text/babel">
    ...
</script>
```

onGetQuotes() will initiate a call to the API backend and return all the posted quote(s) as response. This is being called after the instance has been&nbsp;mounted.

```
<script type="text/babel">
    import Quote from './quote.vue';
    import axios from 'axios';

export default {
        data() {
            return {
                quotes: []
            }
        },
        methods: {
            onGetQuotes() {
                axios.get('http://localhost:8000/api/quotes')
                        .then(
                                response => {
                    this.quotes = response.data.quotes;
                }
                        )
                .catch(
                        error => console.log(error)
                );
            },
            onQuoteDeleted(id) {
                const position = this.quotes.findIndex((element) => {
                            return element.id == id;
                        });
                this.quotes.splice(position, 1);
            }
        },
        mounted: function () {
           this.onGetQuotes();
        },
        components: {
            'app-quote':Quote
        }
    }
</script>
```

### Quote.vue

This is the child component of quotes component. Conditional declaration is used to toggle the editing mode, when the edit button is clicked, an onEdit() method is called and editing property from the vue instance is set to true. on the other hand, the onCancel() and onUpdtae() methods will set editing property to true after the specified login for this function are carried&nbsp;out.

```
<!-- quote.vue -->
<template>
<div>
    <div v-if="editing">
        <div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
            <div class="form-group" id="form__group">
                <label for="content"><b>Edit Quote</b></label><br>
                <textarea id="content" v-model="editValue" rows="10" cols="30" class="form-control"></textarea>
                <div class="control_1">
                    <button @click="onUpdate" class="btn btn-success">Save</button>
                    <button @click="onCancel" class="btn btn-danger">Cancel</button>
                </div>
            </div>
        </div>
    </div>

<div v-if="!editing">
        <div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
            <div class="quote-holder">
                <div class="quote">
                    {{ qt.content }}
                </div>

<div class="quote_control">
                    <div>
                        <div class="control_1">
                            <button @click="onEdit" class="btn btn-primary">
                                Edit
                            </button>
                            <button @click="onDelete" class="btn btn-danger">
                                Delete
                            </button>
                        </div>

<div class="control_2">
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

</template>

<script type="text/babel">
   ...
</script>

<style scoped>
   ...
</style>
```

The expected information, in this case, quote are declared and received using the props option&nbsp;:

```
<script type="text/babel">
    import axios from 'axios';

export default {
        props: ['qt'],
        data() {
            return {
                editing: false,
                editValue: this.qt.content
            }
        },
        methods: {
            onEdit() {
                this.editing = true;
                this.editValue = this.qt.content
            },
            onCancel() {
                this.editing = false;
            },
            onDelete() {
                this.$emit('quoteDeleted', this.qt.id);
                axios.delete('http://localhost:8000/api/quote/' + this.qt.id)
                        .then(
                                response => console.log(response)
            )
            .catch (
                        error => console.log(error)
            )
            },
            onUpdate() {
                this.editing = false;
                this.qt.content = this.editValue;
                axios.put('http://localhost:8000/api/quote/' + this.qt.id,
                        {content: this.editValue})
                        .then(
                                response => console.log(response)
            )
            .catch (
                        error => console.log(error)
            )
                ;
            }
        }
    }
</script>
```

Style

```
<style scoped>
a {
        cursor: pointer;
    }

.quote {
        display: block;
        margin-left: auto;
        margin-right: auto;
        /*min-height: 125px;*/
    }

.quote-holder {
        background: #ffffff;
        margin-bottom: 30px;
        position: relative;
        overflow: hidden;
        padding: 20px;
        min-height: 250px;
    }
    .quote_btn {
        border-radius: 0;
        width: 100%;
        display: block;
        cursor: pointer;
    }

.quote_control {
        width: 100%;
        display: flex;
        padding: 20px 20px 15px;
        background: #FFF;
    }

.control_1 {
        flex: 2;
    }
    .control_2 {
        flex: 1;
        /*display: flex;*/
        justify-content: flex-end;
        align-items: center;
        font-size: 20px;
        font-weight: bold;
        color: #51D2B7;
    }

#form__group{
        box-sizing: border-box;
        overflow: hidden;
    }

textarea {
        margin: 10px 0;
    }
</style>
```

### index.html

Bootstrap classes are used to improve the styling in this application. Don’t forget to include stylesheet in index.html file&nbsp;:

```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue + laravel</title>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

</head>
  <body>
    <div id="app"></div>
    <script src="/dist/build.js"></script>
  </body>
</html>
```

### App.vue

Earlier we cleaned up this file by getting rid of the default contents. Now, fill it with&nbsp;:

```
<!-- App.vue -->
<template>
  <div id="app">
    <div class="container">
      <div class="row">
        <div class="col-xs-12">
         <nav class="navbar navbar-default navbar-fixed-top">
            <div class="container">
              <ul class="nav navbar-nav navbar-center links">
                <li><router-link to="/">Quotes</router-link></li>
                <li><router-link to="/new-quote"> New Quotes</router-link></li>
              </ul>
            </div>
          </nav>
        </div>
      </div>
      <hr>
      <div class="row">
        <div class="col-xs-12">
          <div id="view">
            <router-view></router-view>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script type="text/babel">
export default {
  data () {
    return {

}
  }
}
</script>

<style lang="scss">
#app {
  margin: 30px 0 0 0;
  background: #F7F8FB;
  min-height: 800px;
}

#view {
    margin-top: 80px;
  }

.navbar {
    background: #333333;
    min-height: 70px;
    font-weight: bold;
  }
  .links {
   margin-top: 10px;
  }

.links li a {
    color: #ffffff !important;
    font-weight: bold;
    font-size: 20px;
  }
</style>
```

Don’t forget that we have made use of custom html tags within our vue' files. All these component tag and routes will be managed by src/main.js&nbsp;. So open the file and fill in the content below&nbsp;:

```
<!-- src/main.js -->
import Vue from 'vue'
import VueRouter from 'vue-router';

import App from './App.vue'
import Quotes from './components/quotes.vue';
import NewQuote from './components/new-quote.vue';

Vue.use(VueRouter);

const routes = [
  { path: '', component: Quotes},
  { path: '/new-quote', component: NewQuote },
];

const router = new VueRouter({
  node: 'history',
  routes: routes
});
new Vue({
  el: '#app',
      router: router,
  render: h => h(App)
})
```

### CORS (Cross-Origin Resource&nbsp;Sharing)

Now if we attempt to post a quote from our frontend-quote-app, we will be redirected to the homepage but the quote will not be saved. Inspecting the browser will reveal what the error&nbsp;is.

![](https://cdn-images-1.medium.com/max/1024/1*PWK-01xS5UJu3Jv9jON9Mg.gif)<figcaption>Check the console</figcaption>

Just before you get scared, it is interesting to affirm that we had this error coming. In this application, we are trying to create a direct connection between two different domains. Technically, It is generally not allowed to have two different applications, with different domain name exchanging data. This is by default a form of security measure, but since we are building an API backend, we will have to turn off this protection in order to allow our frontend communicate effectively with the&nbsp;backend.

### Back to the Backend Application

Earlier on, I stated that we need to keep our server on, just incase you haven’t done&nbsp;that.

### Mission

Our goal, is to be able to target all API route from a different domain. In order to achieve this, we must create a new middleware, register it and eventually attach this middleware to our routes. You can read more about Laravel middlewares [here](https://laravel.com/docs/5.5/middleware).

### Create Middleware

Creating a middleware in Laravel application is quite easy. A middleware named Cors will be created for the purpose of this article, and all we have to do is run this command. So open your terminal and go ahead&nbsp;:

```
php artisan make:middleware Cors
```

This will create a middleware in app/Http/middleware/Cors.php&nbsp;. Now is the convenient moment to open the newly created middleware and add the logic to accept incoming HTTP request from the frontend-quote-app'.

```
<?php

namespace App\Http\Middleware;

use Closure;

class Cors
{
    
    public function handle($request, Closure $next)
    {
        return $next($request)
            ->header('Access-Control-Allow-Origin', '*')
            ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
            ->header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    }
}
```

Here, HTTP headers were set to allow the frontend-application gain permission to access resources from the backend&nbsp;app.

Now that we have added the required Logic into the created middleware, Laravel needs to know that a new middleware exists and then be able to use it. Go to app/Http/Kernel.php and add to kernel class&nbsp;:

```
protected $middleware = [
       ...
        \App\Http\Middleware\Cors::class,
    ];
```

With this, we should be able to successfully communicate with our backend application.

![](https://cdn-images-1.medium.com/max/1024/1*FivIY_jsF9gMZahKTbkumw.gif)

Please feel free to try out the demo&nbsp;[here](http://demo.yemiwebby.com.ng/).

### Conclusion

So in this series, we have learnt to&nbsp;:  
\* Build API using Laravel  
\* Use a frontend library (VueJs) to consume the API built with Laravel  
\* Ensure smooth communication between two separate&nbsp;domains.

I hope you have seen how you can conveniently connect Vuejs with a Laravel API&nbsp;backend.

In a subsequent post, we will learn how to add authentication to this application and allow only user with the right access to be able to carry out certain actions like editing and deleting a particular quote.  
If you found this tutorial helpful, have any suggestions or encounter any issues, kindly leave a comment&nbsp;below.

Here are the links to the source code in both series can be found on github, [Backend](https://github.com/yemiwebby/backend-quote-app) and [Frontend](https://github.com/yemiwebby/frontend-quote-app).