Build a Basic CRUD App with Laravel and Vue

Build a Basic CRUD App with Laravel and Vue

In this tutorial, you'll learn how to build a basic CRUD App with Laravel and Vue.

In this tutorial, you'll learn how to build a basic CRUD App with Laravel and Vue.

Laravel is one of the most popular web frameworks today because of its elegance, simplicity, and readability. It also boasts one of the largest and most active developer communities. The Laravel community has produced a ton of valuable educational resources, including this one! In this tutorial, you’ll build a trivia game as two separate projects: a Laravel API and a Vue frontend (using vue-cli). This approach offers some important benefits:

  • It allows you to separate your backend and frontend and deploy them independently, using different strategies and schedules for testing and deployment
  • You can deploy your frontend as a static application to a CDN and achieve virtually unlimited scaling for a fraction of the cost of hosting it together with the backend
  • This structure allows developers to work on just the API or just the frontend without needing access to the source code of the other part of the system (this is still possible to achieve if the projects are integrated, but it’s a bit of a headache to set up) making it an ideal architecture for large teams

Before you start, you’ll need to set up a development environment with PHP 7 and Node.js 8+/npm. You will also need an Okta developer account so you can add user registration, user login, and all the other user related functionalities.

FUN FACT: Did you know that Vue owes much of its current popularity to Laravel? Vue comes pre-packaged with Laravel (along with Laravel Mix, an excellent build tool based on webpack) and allows developers to start building complex single-page applications without worrying about transpilers, code packaging, source maps, or any other ‘dirty’ aspects of modern frontend development.

Create an OpenID Connect App

Before we get into the code, let’s set up our Okta account so it’s ready when we need it. Okta is an API service that allows you to create, edit, and securely store user accounts and user account data, and connect them with one or more applications. You can register for a forever-free developer account here.

Once you’re signed up, log in and visit the Okta dashboard. Be sure to take note of the Org URL setting at the top-right portion of the dashboard, you’ll need this URL later when configuring your application.

Next, set up a new application, you’ll mostly use the default settings. Here are the step-by-step instructions:

  1. Go to the Applications menu item and click the Add Application button:

  1. Go to the Applications menu item and click the Add Application button:

  1. Go to the Applications menu item and click the Add Application button:
Build Your Laravel and Vue CRUD Application

Now it’s time to dig in and build a fun trivia game application! This app will be integrated with a free API for trivia quiz questions and will allow us to set up a list of players, load questions, and mark the players’ answers as right or wrong.

Here’s what your completed application will look like:

You can create your own rules, but here’s the general gist of the game:

  • It allows you to separate your backend and frontend and deploy them independently, using different strategies and schedules for testing and deployment
  • You can deploy your frontend as a static application to a CDN and achieve virtually unlimited scaling for a fraction of the cost of hosting it together with the backend
  • This structure allows developers to work on just the API or just the frontend without needing access to the source code of the other part of the system (this is still possible to achieve if the projects are integrated, but it’s a bit of a headache to set up) making it an ideal architecture for large teams
Install Laravel and Configure the Application

Once the laravel command is installed globally via composer, you’ll use it to create a new Laravel project, and start the development PHP server from its directory:

composer global require laravel/installer
laravel new trivia-web-service
cd trivia-web-service
php artisan serve


Next, you’ll set up a new MySQL database and user for your app (there’s nothing set in stone about MySQL, you can use a different database engine if you prefer):

mysql -uroot -p
CREATE DATABASE trivia CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'trivia'@'localhost' identified by 'trivia';
GRANT ALL on trivia.* to 'trivia'@'localhost';
quit


You now need to insert the database configuration variables into the .env file in your main project directory:

.env

DB_DATABASE=trivia
DB_USERNAME=trivia
DB_PASSWORD=trivia


Create a Simple Laravel API

Now that your database is configured, let’s build the API. Your Laravel API will be quite simple, it will contain just one entity (a Player). Let’s create a migration and a database model for it:

php artisan make:model Player -m
Model created successfully.
Created Migration: 2018_10_08_094351_create_players_table


Put the code that creates the database table in the up() method of the migration:

database/migrations/2018_10_08_094351_create_players_table.php

public function up()
{
    Schema::create('players', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->integer('answers')->default(0);
        $table->integer('points')->default(0);
        $table->timestamps();
    });
}


Next, run the migration to apply it to your database:

php artisan migrate


At this point, you may notice that you already have a model class, app/Player.php, but it’s empty. You need to tell Laravel which fields can be mass-assigned when creating or updating records. You’ll do this via the $fillable attribute of the model class:

app/Player.php

class Player extends Model
{
    protected $fillable = ['name', 'answers', 'points'];
}


Laravel 5.6 introduced the concept of API resources which greatly simplified the way REST APIs are created in Laravel. The API resource classes take care of the transformation of our data to a JSON representation. You’ll need two resources for the API: a Player (dealing with an individual player), and a PlayerCollection (dealing with a collection of players).

php artisan make:resource Player
php artisan make:resource PlayerCollection


The transformation is defined in the toArray() function of the resource class:

app/Http/Resources/Player.php

public function toArray($request)
{
    return [
        'id'         => $this->id,
        'name'       => $this->name,
        'answers'    => (int) $this->answers,
        'points'     => (int) $this->points,
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}


app/Http/Resources/PlayerCollection.php

public function toArray($request)
{
    return [
        'data' => $this->collection
    ];
}


With that out of the way, you can now create the routes and controller for the REST API.

php artisan make:controller PlayerController


routes/api.php

Route::get('/players', '[email protected]');
Route::get('/players/{id}', '[email protected]');
Route::post('/players', '[email protected]');
Route::post('/players/{id}/answers', '[email protected]');
Route::delete('/players/{id}', '[email protected]');
Route::delete('/players/{id}/answers', '[email protected]');


app/Http/Controllers/PlayerController.php

...
use App\Player;
use App\Http\Resources\Player as PlayerResource;
use App\Http\Resources\PlayerCollection;
...

class PlayerController extends Controller
{
    public function index()
    {
        return new PlayerCollection(Player::all());
    }

    public function show($id)
    {
        return new PlayerResource(Player::findOrFail($id));
    }

    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|max:255',
        ]);

        $player = Player::create($request->all());

        return (new PlayerResource($player))
                ->response()
                ->setStatusCode(201);
    }

    public function answer($id, Request $request)
    {
        $request->merge(['correct' => (bool) json_decode($request->get('correct'))]);
        $request->validate([
            'correct' => 'required|boolean'
        ]);

        $player = Player::findOrFail($id);
        $player->answers++;
        $player->points = ($request->get('correct')
                           ? $player->points + 1
                           : $player->points - 1);
        $player->save();

        return new PlayerResource($player);
    }

    public function delete($id)
    {
        $player = Player::findOrFail($id);
        $player->delete();

        return response()->json(null, 204);
    }

    public function resetAnswers($id)
    {
        $player = Player::findOrFail($id);
        $player->answers = 0;
        $player->points = 0;

        return new PlayerResource($player);
    }
}


You have to enable CORS so you can access your API from the frontend application:

composer require barryvdh/laravel-cors


app/Http/Kernel.php

protected $middlewareGroups = [
    'web' => [
		...
		\Barryvdh\Cors\HandleCors::class,
    ],

    'api' => [
        ...
		\Barryvdh\Cors\HandleCors::class,
    ],
];


Your API allows you to retrieve all players or a specific player, add/delete players, mark answers as right/wrong, and reset a player’s score. There’s also a validation of the requests and the code generates JSON responses with the appropriate HTTP status codes with small amount of code.

To test the API, just add some dummy data to the database (use the Faker library to automate this process). After that, you can access these URLs and inspect the responses:

  • It allows you to separate your backend and frontend and deploy them independently, using different strategies and schedules for testing and deployment
  • You can deploy your frontend as a static application to a CDN and achieve virtually unlimited scaling for a fraction of the cost of hosting it together with the backend
  • This structure allows developers to work on just the API or just the frontend without needing access to the source code of the other part of the system (this is still possible to achieve if the projects are integrated, but it’s a bit of a headache to set up) making it an ideal architecture for large teams

Testing the POST/PUT/DELETE requests is a bit more involved and requires an external tool (for example, cURL or Postman). You also need to make sure that the following headers are sent with each request:

Accept: "application/json"

This header tells Laravel to return any validation errors in JSON format.

Install Vue and Set up the Frontend Application

You will install vue-cli and create a new Vue.js project using the default configuration. You’ll also add Vue Router, Axios, and the Okta authentication+authorization library to the project:

npm install -g @vue/cli
vue create trivia-web-client-vue
cd trivia-web-client-vue
yarn add --save vue-router axios @okta/okta-vue
yarn serve


Loading http://localhost:8080/ now shows the default VueJS app.

Create a Menu with Routing in the Vue Frontend

Remove the default content first so you will have a nice blank page: Delete src/components/HelloWorld.vue and src/App.vue, and modify src/main.js:

main.js

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.config.productionTip = false

Vue.use(VueRouter)

import Dashboard from './components/Dashboard.vue';

const routes = [
  { path: '/', component: Dashboard },
]

const router = new VueRouter({
  mode: 'history',
  routes
})

new Vue({
  router,
  render: h => h(Dashboard)
}).$mount('#app')


Create a new file components/Dashboard.vue:

components/Dashboard.vue

<template>
    <h1>This is the dashboard</h1>
</template>

<script>
</script>


It doesn’t look very nice with the default browser font. Let’s improve it by loading the Bulma CSS framework from a CDN:

public/index.html

...
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css">


Add Authentication to the Vue Frontend

Great! Now you can add your menu and routing, and implement a protected ‘Trivia Game’ route that requires authentication:

main.js

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.config.productionTip = false
Vue.use(VueRouter)

import Dashboard from './components/Dashboard.vue'
import Auth from '@okta/okta-vue'

Vue.use(Auth, {
  issuer: 'https://{yourOktaDomain}/oauth2/default',
  client_id: '{yourClientId}',
  redirect_uri: 'http://localhost:8080/implicit/callback',
  scope: 'openid profile email'
})

const routes = [
  { path: '/implicit/callback', component: Auth.handleCallback() },
  { path: '/', component: Dashboard},
]

const router = new VueRouter({
  mode: 'history',
  routes
})

new Vue({
  router,
  render: h => h(Dashboard)
}).$mount('#app')


Don’t forget to substitute your own Okta domain and Client ID! You also need to add a menu with a ‘Trivia Game’ link (only if authenticated) and Login/Logout buttons to the Dashboard.

main.js

...
import TriviaGame from './components/TriviaGame.vue'

const routes = [
  { path: '/implicit/callback', component: Auth.handleCallback() },
  { path: '/trivia', component: TriviaGame }
]
...


components/TriviaGame.vue

<template>
    <h1>This is the trivia game</h1>
</template>

<script>
</script>


components/Dashboard.vue

<template>
    <div style="text-align:center">
        <section class="section">
            <div class="container">
                <nav class="navbar" role="navigation" aria-label="main navigation">
                    <div class="navbar-menu">
                        <div class="navbar-item">
                            <router-link to="/" class="navbar-item">Home</router-link>
                            <router-link v-if='authenticated' to="/trivia" class="navbar-item">Trivia Game</router-link>
                            <a class="button is-light" v-if='authenticated' v-on:click='logout' id='logout-button'> Logout </a>
                            <a class="button is-light" v-else v-on:click='login' id='login-button'> Login </a>
                        </div>
                    </div>
                </nav>
                <router-view></router-view>
            </div>
        </section>
    </div>
</template>

<script>
export default {

    data: function () {
        return {
            authenticated: false
        }
    },

    created () {
        this.isAuthenticated()
    },

    watch: {
        // Everytime the route changes, check for auth status
        '$route': 'isAuthenticated'
    },

    methods: {
        async isAuthenticated () {
            this.authenticated = await this.$auth.isAuthenticated()
        },

        login () {
            this.$auth.loginRedirect('/')
        },

        async logout () {
            await this.$auth.logout()
            await this.isAuthenticated()

            // Navigate back to home
            this.$router.push({ path: '/' })
        }
    }
}
</script>


The app now contains a navbar with placeholder pages for Home, Trivia Game (only available when logged in), and the Login or Logout button (depending on the login state). The Login/Logout actions work through Okta. You can now proceed with the implementation of the Trivia Game and connecting the backend API.

Get the List of Players from the Laravel API

Next up you’ll be adding a new Vue component to load the list of players from the Laravel API.

You’ll create a new src/config.js file and define our base API url there:

src/config.js

export const API_BASE_URL = 'http://localhost:8000/api';


You can now modify your TriviaGame.vue component:

components/TriviaGame.vue

<template>
    <div>
        <div v-if="isLoading">Loading players...</div>
        <div v-else>
        <table class="table">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Answers</th>
                    <th>Points</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody>
                <template v-for="player in players">
                    <tr v-bind:key="player.id">
                        <td>{{ player.id }}</td>
                        <td>{{ player.name }}</td>
                        <td>{{ player.answers }}</td>
                        <td>{{ player.points }}</td>
                        <td>Action buttons</td>
                    </tr>
                </template>
            </tbody>
        </table>
        <a class="button is-primary">Add Player</a>
        </div>
    </div>
</template>

<script>
import axios from 'axios'
import { API_BASE_URL } from '../config'

export default {
    data() {
        return {
            isLoading: true,
            players: {}
        }
    },
    async created () {
        axios.defaults.headers.common['Authorization'] = `Bearer ${await this.$auth.getAccessToken()}`
        try {
            const response = await axios.get(API_BASE_URL + '/players')
            this.players = response.data.data
            this.isLoading = false
        } catch (e) {
            // handle the authentication error here
        }
    }
}
</script>


Add Authentication to the Laravel API

You need to secure your backend API so it only allows requests that include a valid Okta token. You will install the Okta JWT Verifier package and add a custom middleware for API authentication:

composer require okta/jwt-verifier spomky-labs/jose guzzlehttp/psr7
php artisan make:middleware AuthenticateWithOkta


app/Http/Middleware/AuthenticateWithOkta.php

<?php
namespace App\Http\Middleware;

use Closure;

class AuthenticateWithOkta
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($this->isAuthorized($request)) {
            return $next($request);
        } else {
            return response('Unauthorized.', 401);
        }
    }

    public function isAuthorized($request)
    {
        if (! $request->header('Authorization')) {
            return false;
        }

        $authType = null;
        $authData = null;

        // Extract the auth type and the data from the Authorization header.
        @list($authType, $authData) = explode(" ", $request->header('Authorization'), 2);

        // If the Authorization Header is not a bearer type, return a 401.
        if ($authType != 'Bearer') {
            return false;
        }

        // Attempt authorization with the provided token
        try {

            // Setup the JWT Verifier
            $jwtVerifier = (new \Okta\JwtVerifier\JwtVerifierBuilder())
                            ->setAdaptor(new \Okta\JwtVerifier\Adaptors\SpomkyLabsJose())
                            ->setAudience('api://default')
                            ->setClientId('{yourClientId}')
                            ->setIssuer('{yourIssuerUrl}')
                            ->build();

            // Verify the JWT from the Authorization Header.
            $jwt = $jwtVerifier->verify($authData);
        } catch (\Exception $e) {

            // You encountered an error, return a 401.
            return false;
        }

        return true;
    }

}


Of course, you need to replace the client ID and issuer URL with your own! It’s also preferable to extract these variables into the .env file. They are not secrets and they are visible in the frontend application so it’s not a security concern to keep them in the repo, but it’s not convenient if you have multiple environments.

app/Http/Kernel.php

    protected $middlewareGroups = [
        'web' => [
			...
        ],

        'api' => [
            ...
			\App\Http\Middleware\AuthenticateWithOkta::class,
        ],
    ];


If you did everything correctly, http://localhost:8000/api/players should now show you an ‘Unauthorized.’ message but loading the list of players in the Vue frontend should work fine (when you are logged in).

Create a New Player Component in Vue

Next, replace the ‘Add Player’ button placeholder with a form to add a new player.

components/TriviaGame.vue

Replace 
<a class="button is-primary">Add Player</a>
with:
<player-form @completed="addPlayer"></player-form>

Add to the <script> section:

import PlayerForm from './PlayerForm.vue'

export default {
    components: {
        PlayerForm
    },
...
    methods: {
        addPlayer(player) {
            this.players.push(player)
        }
    }


Create a new component PlayerForm.vue:

components/PlayerForm.vue

<template>
    <form @submit.prevent="onSubmit">
        <span class="help is-danger" v-text="errors"></span>

        <div class="field">
            <div class="control">
                <input class="input" type="name" placeholder="enter player name..." v-model="name" @keydown="errors = ''">
            </div>
        </div>

        <button class="button is-primary" v-bind:class="{ 'is-loading' : isLoading }">Add Player</button>
    </form>
</template>

<script>
import axios from 'axios'
import { API_BASE_URL } from '../config'

export default {
    data() {
        return {
            name: '',
            errors: '',
            isLoading: false
        }
    },
    methods: {
        onSubmit() {
            this.isLoading = true
            this.postPlayer()
        },
        async postPlayer() {
            axios.defaults.headers.common['Authorization'] = `Bearer ${await this.$auth.getAccessToken()}`
            axios.post(API_BASE_URL + '/players', this.$data)
                .then(response => {
                    this.name = ''
                    this.isLoading = false
                    this.$emit('completed', response.data.data)
                })
                .catch(error => {
                    // handle authentication and validation errors here
                    this.errors = error.response.data.errors
                    this.isLoading = false
                })
        }
    }
}
</script>


It’s now possible to add more players to our trivia game.

Add a ‘Delete Player’ Button to the Vue Application

Next you’ll replace the ‘Action Buttons’ placeholder with a button that actually deletes the player.

components/TriviaGame.vue

Replace
<td>Action buttons</td>
with:
<td>
<button class="button is-primary" v-bind:class="{ 'is-loading' : isDeleting(player.id) }" @click="deletePlayer(player.id)">Delete Player</button>
</td>

Modify the <script> section:

...
import Vue from 'vue'
...

export default {
    ...
    methods: {
        ...
        isDeleting(id) {
            let index = this.players.findIndex(player => player.id === id)
            return this.players[index].isDeleting
        },
        async deletePlayer(id) {
            let index = this.players.findIndex(player => player.id === id)
            Vue.set(this.players[index], 'isDeleting', true)
            await axios.delete(API_BASE_URL + '/players/' + id)
            this.players.splice(index, 1)
        }
    }
}


Connect a Trivia Question Service to Vue

In order to save time, you’ll use a public API for retrieving trivia questions instead of building out a question database from scratch. The service provides a URL that returns a different trivia question every time it’s requested. Define the URL in the config.js file and you’ll get an initial question when the Trivia Game page is loaded. Then you’ll modify the Trivia Game component to include a card with the question and answer:

src/config.js

...
export const TRIVIA_ENDPOINT = 'http://jservice.io/api/random?count=1';


components/TriviaGame.vue (pasting the full contents of the file)

<template>
    <div class="columns">
        <div class="column" v-if="isLoading">Loading players...</div>
        <div class="column" v-else>
        <table class="table">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Answers</th>
                    <th>Points</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody>
                <template v-for="player in players">
                    <tr v-bind:key="player.id">
                        <td></td>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td>
                        <button class="button is-primary" v-bind:class="{ 'is-loading' : isDeleting(player.id) }" @click="deletePlayer(player.id)">Delete Player</button>
                        </td>
                    </tr>
                </template>
            </tbody>
        </table>
        <player-form @completed="addPlayer"></player-form>
        </div>
        <div class="column">
            <div class="card" v-if="question">
                <div class="card-content">
                    <button class="button is-primary" @click="getQuestion()">Refresh Question</button>
                    <p class="title">

                    </p>
                    <p class="subtitle">

                    </p>
                </div>
                <footer class="card-footer">
                    <p class="card-footer-item">
                        <span>Correct answer: </span>
                    </p>
                </footer>
            </div>
        </div>
    </div>
</template>

<script>
import axios from 'axios'
import { API_BASE_URL, TRIVIA_ENDPOINT } from '../config'
import PlayerForm from './PlayerForm.vue'
import Vue from 'vue'

export default {
    components: {
        PlayerForm
    },
    data() {
        return {
            isLoading: true,
            question: null,
            players: {}
        }
    },
    async created () {
        this.getQuestion()
        axios.defaults.headers.common['Authorization'] = `Bearer ${await this.$auth.getAccessToken()}`
        try {
            const response = await axios.get(API_BASE_URL + '/players')
            this.players = response.data.data
            this.isLoading = false
        } catch (e) {
            // handle the authentication error here
        }
    },
    methods: {
        async getQuestion() {
            delete axios.defaults.headers.common.Authorization
            this.doGetQuestion()
            axios.defaults.headers.common['Authorization'] = `Bearer ${await this.$auth.getAccessToken()}`
        },
        async doGetQuestion() {
            try {
                const response = await axios.get(TRIVIA_ENDPOINT, {
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded' 
                    }
                })
                this.question = response.data[0]
            } catch (e) {
                // handle errors here
            }
        },
        addPlayer(player) {
            this.players.push(player)
        },
        isDeleting(id) {
            let index = this.players.findIndex(player => player.id === id)
            return this.players[index].isDeleting
        },
        async deletePlayer(id) {
            let index = this.players.findIndex(player => player.id === id)
            Vue.set(this.players[index], 'isDeleting', true)
            await axios.delete(API_BASE_URL + '/players/' + id)
            this.players.splice(index, 1)
        }
    }
}
</script>


Add Buttons in Vue to Indicate Right and Wrong Answers

Now, let’s add two more buttons next to the Delete Player button and implement the handlers:

components/TriviaGame.vue

...
<td>
    <button class="button is-primary" v-bind:class="{ 'is-loading' : isCountUpdating(player.id) }" @click="answer(player.id, true)">(+1) Right</button>&nbsp;
    <button class="button is-primary" v-bind:class="{ 'is-loading' : isCountUpdating(player.id) }" @click="answer(player.id, false)">(-1) Wrong</button>&nbsp;
    <button class="button is-primary" v-bind:class="{ 'is-loading' : isDeleting(player.id) }" @click="deletePlayer(player.id)">Delete Player</button>
</td>
...

    methods: {
    ...
        isCountUpdating(id) {
            let index = this.players.findIndex(player => player.id === id)
            return this.players[index].isCountUpdating
        },
        async answer(id, isCorrectAnswer) {
            let data = {
                correct: isCorrectAnswer
            }
            let index = this.players.findIndex(player => player.id === id)
            Vue.set(this.players[index], 'isCountUpdating', true)
            const response = await axios.post(API_BASE_URL + '/players/' + id + '/answers', data)
            this.players[index].answers = response.data.data.answers
            this.players[index].points = response.data.data.points
            this.players[index].isCountUpdating = false
        }
    }


The game is complete now! You now have a basic Laravel API that returns trivia questions to authenticated requests, and a Vue front-end that can log users in and make authenticated requests to the Laravel API.

This is a great start, but there is of course room for improvement. You can improve the code by extracting the common API boilerplate code (retrieving the access token, sending the Authorization header, sending a request and receiving a response) into a service class.

You can find the full code here: https://github.com/oktadeveloper/okta-php-laravel-vue-crud-example

Learn More

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

Nuxt.js - Vue.js on Steroids

Vue.js Fast Crash Course

The Complete JavaScript Course 2018: Build Real Projects!

PHP with Laravel for beginners - Become a Master in Laravel

Laravel 5 Starter Course

Laravel for RESTful: Build Your RESTful API with Laravel

How to create a live chat app using Laravel and Vue.js

How to create a live chat app using Laravel and Vue.js

In this tutorial, we will be looking at how to build a live chat using Laravel and Vue.js where people can log in and chat with each other. This is a feature we see in applications like YouTube or Facebook during a live event.

Introduction

In this article, we’ll be building an app where users can log in with their usernames and then join a live chat. Here is what our app will look like:

Requirements

To follow along with this article, you need the following:

If you have these requirements we can begin.

Creating our Laravel application

We will start by creating a new Laravel application. By now, you should have the Laravel CLI, so you can just run this command to create a new project:

$ laravel new larachat

This will create a new Laravel application in a larachat folder.

Setting up Pusher Chatkit in the app

Before we go ahead to enable Chatkit in our app, open the Chatkit app, go to the Console tab and create a new user named admin. Create a new room named live-chat with the admin.

Open the room you have just created and note the room ID. Copy it out as you will soon need it.

To enable Chatkit in our app, we will need to add the keys to our app environment. Add these new properties at the bottom of the .env file found in your project directory:

If you don’t have the .env file for some reason, rename the .env.example file to .env and run the command: php artisan key:generate

    # File: .env
    # [...]

    CHATKIT_INSTANCE_LOCATOR="INSTANCE_LOCATOR_HERE"
    CHATKIT_SECRET_KEY="SECRET_KEY_HERE"
    CHATKIT_GENERAL_ROOM_ID="GENERAL_ROOM_ID"

    MIX_APP_URL="${APP_URL}"
    MIX_CHATKIT_INSTANCE_LOCATOR="${CHATKIT_INSTANCE_LOCATOR}"

You need to replace the placeholder values with the credentials from your Chatkit app. The CHATKIT_INSTANCE_LOCATOR and CHATKIT_SECRET_KEY can be gotten from the Credentials tab of the Chatkit instance. Replace CHATKIT_GENERAL_ROOM_ID with the room ID you copied out earlier. MIX_APP_URL and MIX_CHATKIT_INSTANCE_LOCATOR references the APP_URL and CHATKIT_INSTANCE_LOCATOR respectively.

Still in the .env file, update the APP_URL like so:

    APP_URL=http://localhost:8000

To enable our application can use the environment credentials we added earlier, open the config/services.php file and in there add the snippet below to the array of third-party services:

    // File: config/services.php
    'chatkit' => [
        'secret' => env('CHATKIT_SECRET_KEY'),
        'locator' => env('CHATKIT_INSTANCE_LOCATOR'),
    ],

Next we will need to do is install the Chatkit PHP SDK. Run this command in the root directory of your project to install the Chatkit package:

$ composer require pusher/pusher-chatkit-server

Next, open app/providers/AppServiceProvider.php and add the following code inside the register method:

    // File: app/providers/AppServiceProvider.php
    $this->app->bind('ChatKit', function() {
        return new \Chatkit\Chatkit([
            'instance_locator' => config('services.chatkit.locator'),
            'key' => config('services.chatkit.secret'),
        ]);
    });

The above snippet will bind the Chatkit service within the register method into Laravel’s IoC container. We can now resolve it from anywhere within our app and it will return an instance of the configured Chatkit class.

Building the application logic

Now we have our Laravel application configured and Chatkit SDK setup, let’s write the core functionality of our chat app.

First, we will create and update some controllers. Still in your root directory, run this command to create a new controller:

$ php artisan make:controller ChatkitController

Open the app/Http/Controllers/ChatkitController.php file and replace the contents with this snippet:

    <?php
    // File: app/Http/Controllers/ChatkitController.php

    namespace App\Http\Controllers;

    use Illuminate\Http\Request;

    class ChatkitController extends Controller
    {
        private $chatkit;
        private $roomId;

        public function __construct()
        {
            $this->chatkit = app('ChatKit');
            $this->roomId = env('CHATKIT_GENERAL_ROOM_ID');
        }

        /**
         * Show the welcome page.
         *
         * @return \Illuminate\Contracts\Support\Renderable
         */
        public function index(Request $request)
        {
            $userId = $request->session()->get('chatkit_id')[0];

            if (!is_null($userId)) {
                // Redirect user to Chat Page
                return redirect(route('chat'));
            }

            return view('welcome');
        }

        /**
         * The user joins chat room.
         *
         * @param  \Illuminate\Http\Request $request
         * @return mixed
         */
        public function join(Request $request)
        {
            $chatkit_id = strtolower(str_random(5));

            // Create User account on Chatkit
            $this->chatkit->createUser([
                'id' =>  $chatkit_id,
                'name' => $request->username,
            ]);

            $this->chatkit->addUsersToRoom([
                'room_id' => $this->roomId,
                'user_ids' => [$chatkit_id],
            ]);

            // Add User details to session
            $request->session()->push('chatkit_id', $chatkit_id);

            // Redirect user to Chat Page
            return redirect(route('chat'));
        }

        /**
         * Show the application chat room.
         *
         * @return \Illuminate\Contracts\Support\Renderable
         */
        public function chat(Request $request)
        {
            $roomId = $this->roomId;

            $userId = $request->session()->get('chatkit_id')[0];

            if (is_null($userId)) {
                $request->session()->flash('status', 'Join to access chat room!');
                return redirect(url('/'));
            }

            // Get messages via Chatkit
            $fetchMessages = $this->chatkit->getRoomMessages([
                'room_id' => $roomId,
                'direction' => 'newer',
                'limit' => 100
            ]);

            $messages = collect($fetchMessages['body'])->map(function ($message) {
                return [
                    'id' => $message['id'],
                    'senderId' => $message['user_id'],
                    'text' => $message['text'],
                    'timestamp' => $message['created_at']
                ];
            });

            return view('chat')->with(compact('messages', 'roomId', 'userId'));
        }

        /**
         * Receives a client request and provides a new token.
         *
         * @param  \Illuminate\Http\Request  $request
         * @return mixed
         */
        public function authenticate(Request $request)
        {
            $response = $this->chatkit->authenticate([
                'user_id' => $request->user_id,
            ]);

            return response()
                ->json(
                    $response['body'],
                    $response['status']
                );
        }

         /**
         * Send user message.
         *
         * @param  \Illuminate\Http\Request  $request
         * @return mixed
         */
        public function sendMessage(Request $request)
        {
            $message = $this->chatkit->sendSimpleMessage([
                'sender_id' => $request->user,
                'room_id' => $this->roomId,
                'text' => $request->message
            ]);

            return response($message);
        }

        /**
         * Get all users.
         *
         * @param  \Illuminate\Http\Request  $request
         * @return mixed
         */
        public function getUsers()
        {
            $users = $this->chatkit->getUsers();

            return response($users);
        }

        /**
         * Get all users.
         *
         * @param  \Illuminate\Http\Request  $request
         * @return mixed
         */
        public function logout(Request $request)
        {
            $request->session()->flush();

            return redirect(url('/'));
        }
    }

In the ChatkitController above we have several methods, namely: index, join, chat, authenticate, sendMessage, getUsers and logout.

The index method returns the welcome page if a user ID doesn’t exist in the current session else it redirects to the chat page.

The join method uses the resolved Chatkit SDK instance, creates a new user on Chatkit and add the user to the live-chat room and then save the users Chatkit ID in the current session before redirecting the user to the chat page.

The chat method handles the chat page when loaded on the browser. Inside it, we get the current user Chatkit ID from the session. If a Chatkit ID is not found, we return the user to the welcome page. The Chatkit ID, room ID, and chat room messages properties are then passed to the chat page view.

The authenticate method will act as a token provider server that receives the client's request and returns a valid JWT to your Chatkit client.

In the sendMessage method, we use the SDK’s sendSimpleMessage method, which accepts the sender_id, room_id and text to send a message to the chat room. The getUsers method returns all the users created on our Chatkit instance. Our logout method flushes the current session and redirects to the welcome page.

Next, we will update the web routes. Open routes/web.php and paste the code below to override the default :

    // File: routes/web.php
    Route::get('/', '[email protected]');
    Route::post('/', '[email protected]');
    Route::get('chat', '[email protected]')->name('chat');
    Route::post('logout', '[email protected]')->name('logout');

Open routes/api.php and add the code below in the file:

    // File: routes/api.php
    Route::post('authenticate','[email protected]');
    Route::get('users', '[email protected]');
    Route::post('message','[email protected]');

The newly added routes will provide endpoints to authenticate a client request, show a chat and send a message.

Building the application frontend

First, we will update the welcome page of the app. Open a resources/views/welcome.blade.php file and paste the code below to update the file:

    
    
    getLocale()) }}">
    
        
        
        Live Chat
        
        
        
        
        
        
        
    
    
        
            
                
                    Join Live Chat
                
                @if(Session::has('status'))
                    
                        {{ Session::get('status') }}
                    
                @endif
                
                    
                        @csrf
                        
                            <input type="text" name="username" class="form-control" 
                              placeholder="Enter your username">
                            
                                
                                  Join
                                
                            
                        
                    
                
            
        
    
    

The welcome.blade.php view contains a Join Live Chat title and a simple form that submits the entered username in the input field to join the live chat room.

Next, we will build the chat page to read and send messages. A chatbox component will be created using Vue.js to handle the chat feature.

Open a terminal tab in your project directory and run the command below:

$ npm install

This command installs the required development dependencies in the package.json file. Next, we need to install Chatkit. Run this command to install it:

$ npm install moment @pusher/chatkit-client --save-dev

Now create a ChatBoxComponent.vue file in the resources/js/components/ directory and paste the code snippet below inside the file.

    
    
      
        
            
                * ({{ formatTime(message.timestamp) }}) * : **{{ findSender(message.senderId).name }}**
                {{ message.text }}
            
        
        
* * *

        
            
            
              Send
            
        
      
    

    
    

The ChatBoxComponent.vue single file component is divided into three sections:

  • template tag contains our HTML syntax for the chat box providing the information to be shown.
  • script tag holds the logic that keeps data and methods. It also performs various operations like authentication, sending and receiving messages.
  • style tag provides the simplest option to add CSS to a Vue.js component.

The script tag keeps the logic of our Vue component. Inside this tag, first, we imported the libraries we will make use of. The export default block exports an object literal as our component’s view model and is responsible for the behavior of our component. This block houses other sections.

The props section helps us to pass custom data to the component from its instances. The data section is used to set up the component state. Every property you define in data becomes reactive meaning that if there is a change, it will be reflected in the view.

The method section holds the methods just as the name implies. In our component, we have six methods for various actions.

  • connectToChatkit method handles the connection to Chatkit from the client (browser) interface. It defines a TokenProvider and then the ChatManager is created with the token provider, allowing the client to connect to the Chatkit servers. On a successful connection, the currentUser data state is updated and the subscribeToRoom method is called.
  • subscribeToRoom method handles the connection to a particular room and is able to hook in some actions to respond to certain events like when a new message is sent, a user joins the room or leaves the room.
  • getUser method fetches all users on the Chatkit instance and updates the users data state with the response body gotten.
  • sendMessage method gets data from the input field and makes a post request to send the message.
  • findSender method accepts a senderId parameter and uses this to find a user on the this.users data.
  • formatTime method using the imported moment library returns a human readable time.

The created section is one of the used life cycle hooks used for initialization. In our case, we called the connectToChatkit and getUsers method there.

Next, open resources/js/app.js and add the Vue instance like so:

    // resources/js/app.js
    Vue.component('chatbox', require('./components/ChatBoxComponent.vue').default); 

The above code line registers the ChatBoxComponent.vue using chatbox as its basename. Now, go back to the terminal and run the command below:

$ npm run dev

This command will run a development build process using webpack to compile all resources/js files into the public/js/app.js file.

The command npm run prod is used in the case you want to it in a production environment

Next, create a resources/views/chat.blade.php file and paste the code below:

    
    
    getLocale()) }}">
    
        
        
        
        
        Live Chat
        
        
        
        
        
        
        
    
    
        
            
                <a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault();
                document.getElementById('logout-form').submit();">
                    Leave Chat Room
                
                
                    @csrf
                
            
            
                
                    
                        
                             Live Chat Room 

                             
                        
                    
                
            
        
    
    

Inside the chat.blade.php, we added the chatbox tag that renders our ChatBoxComponent we created earlier and above it, we have Leave Chat Room link that logs the user out.

Finally, to test our app run the command below to serve the application:

$ php artisan serve

Now open http://localhost:8000 on two separate browser windows. Register two different accounts and try chatting between the two users. You should have results similar to this:

Conclusion

In this tutorial, we have seen how to add a live chatting experience to our app. The application was created using Laravel and Vue.js. The Chatkit API is very extensible and provides features not covered in this tutorial. You can leverage this knowledge to work with other features Chatkit provides in a Laravel app.

The source code is available on GitHub.

Top Vue.js Developers in USA

Top Vue.js Developers in USA

Vue.js is an extensively popular JavaScript framework with which you can create powerful as well as interactive interfaces. Vue.js is the best framework when it comes to building a single web and mobile apps.

We, at HireFullStackDeveloperIndia, implement the right strategic approach to offer a wide variety through customized Vue.js development services to suit your requirements at most competitive prices.

Vue.js is an open-source JavaScript framework that is incredibly progressive and adoptive and majorly used to build a breathtaking user interface. Vue.js is efficient to create advanced web page applications.

Vue.js gets its strength from the flexible JavaScript library to build an enthralling user interface. As the core of Vue.js is concentrated which provides a variety of interactive components for the web and gives real-time implementation. It gives freedom to developers by giving fluidity and eases the integration process with existing projects and other libraries that enables to structure of a highly customizable application.

Vue.js is a scalable framework with a robust in-build stack that can extend itself to operate apps of any proportion. Moreover, vue.js is the best framework to seamlessly create astonishing single-page applications.

Our Vue.js developers have gained tremendous expertise by delivering services to clients worldwide over multiple industries in the area of front-end development. Our adept developers are experts in Vue development and can provide the best value-added user interfaces and web apps.

We assure our clients to have a prime user interface that reaches end-users and target the audience with the exceptional user experience across a variety of devices and platforms. Our expert team of developers serves your business to move ahead on the path of success, where your enterprise can have an advantage over others.

Here are some key benefits that you can avail when you decide to hire vue.js developers in USA from HireFullStackDeveloperIndia:

  • A team of Vue.js developers of your choice
  • 100% guaranteed client satisfaction
  • Integrity and Transparency
  • Free no-obligation quote
  • Portal development solutions
  • Interactive Dashboards over a wide array of devices
  • Vue.js music and video streaming apps
  • Flexible engagement model
  • A free project manager with your team
  • 24*7 communication with your preferred means

If you are looking to hire React Native developers in USA, then choosing HireFullStackDeveloperIndia would be the best as we offer some of the best talents when it comes to Vue.js.

Creating a To-Do List App with Vue.js & Laravel

Creating a To-Do List App with Vue.js & Laravel

In this tutorial, we will build a to-do app with Vue.js and Laravel using Pusher to add the realtime functionality. Vue.js is such a great JS framework as it speeds up frontend development and helps achieve more with less code.

Introduction

In this tutorial, we will build a to-do app with Vue.js and Laravel using Pusher to add the realtime functionality. Vue.js is such a great JS framework as it speeds up frontend development and helps achieve more with less code. Laravel on its side is one the most popular and powerful backend frameworks; allied with Vue.js we can build awesome web apps. Pusher you may know is a collection of hosted APIs to build realtime apps with less code. Now let’s get started!

Demo

This is the result of our final working app:

Prerequisites

In order to follow this tutorial a basic or good understanding of Vue.js and Laravel is required, as we’ll be using these technologies throughout this tutorial. Also ensure you have npm or Yarn on your machine.

Initialize the project and install dependencies

To get started we’ll install a new Laravel application using the Laravel CLI. We’ll run the following command:

laravel new realtime_todo

Once the installation is finished run the following command to move to your app directory:

cd realtime_todo

Now we’ll install our node dependencies, first paste this in your package.json file:

{
      "private": true,
      "scripts": {
        "dev": "npm run development",
        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "npm run development -- --watch",
        "watch-poll": "npm run watch -- --watch-poll",
        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
      },
      "devDependencies": {
        "axios": "^0.18",
        "bootstrap": "^4.0.0",
        "cross-env": "^5.1",
        "jquery": "^3.2",
        "laravel-mix": "^2.0",
        "lodash": "^4.17.5",
        "popper.js": "^1.12",
        "vue": "^2.5.7",
        "vuex": "^3.0.1",
        "laravel-echo": "^1.4.0",
        "pusher-js": "^4.2.2"
      }
    }

Then run npm install or yarn to install the dependencies. It’s up to you.

After this step, add the following to your .env file in the root of your project directory. Ensure to replace the placeholders with your keys from Pusher.

PUSHER_APP_ID=YOUR_PUSHER_APP_ID 
PUSHER_APP_KEY=YOUR_PUSHER_APP_KEY 
PUSHER_APP_SECRET=YOUR_PUSHER_APP_SECRET 
PUSHER_APP_CLUSTER=YOUR_PUSHER_APP_CLUSTER

Database setup

In this tutorial, we’ll use SQLite as our database. Create a database.sqlite file in the database directory, and amend the .env file like this:

DB_CONNECTION=sqlite DB_DATABASE=/absolute/path/to/database.sqlite

Refer to this section on Laravel website for more relevant information.

Building models and seeding our database

Now, let’s build our database structure. We’ll use the Laravel CLI again for that. Run this command:

php artisan make:model Task -mc

The above command will generate the Task model as well as its migration and its controller TaskController.php for us.

Open your Task.php file and paste this:

 //realtime_todo/app/Task.php
&lt;?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
    //
    protected $fillable = ['title','completed'];
}

Next copy and paste this piece of code in your task migration file:

 //realtime_todo/database/migrations/*_create_tasks_table.php

&lt;?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTasksTable extends Migration
{
    /**
    * Run the migrations.
    *
    * @return void
    */

    public function up() {
        Schema::create('tasks', function (Blueprint $table) {
            $table-&gt;increments('id');
            $table-&gt;string('title');
            $table-&gt;boolean('completed')-&gt;default(false);
            $table-&gt;timestamps();
        }
        );
    }

    /**
    * Reverse the migrations.
    *
    * @return void
    */
      public function down(){
        Schema::dropIfExists('tasks');
    }
}

Then run php artisan migrate to run the migration.

Define routes and create the TaskController

In this section, we’ll define our app endpoints and define the logic behind our TaskController.php

This is a simple CRUD(create, read, update, delete) over our Task model. So we defined our routes with corresponding functions to handle our browser requests. Paste the following into api.php:

//realtime_todo/routes/api.php
<?php
use Illuminate\Http\Request;

/*
--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::get('todos','[email protected]tchAll');
Route::post('todos','[email protected]');
Route::delete('todos/{id}','[email protected]');

Now let’s define our controller logic. Our controller functions will be responsible for actions to handle when some requests reach our API endpoints.

Open your TaskController file and paste the following code:

 //realtime_todo/app/Http/Controllers/TaskController.php

&lt;?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Events\TaskCreated;
use App\Events\TaskRemoved;
use App\Task;
class TaskController extends Controller
{
    //
    public function fetchAll(){
        $tasks = Task::all();
        //return response()-&gt;json($tasks);
        return $tasks;
    }

    public function store(Request $request){
        $task = Task::create($request-&gt;all());
        broadcast(new TaskCreated($task));
        return response()-&gt;json("added");
    }

    public function delete($id){
        $task = Task::find($id);
        broadcast(new TaskRemoved($task));
        Task::destroy($id);
        return response()-&gt;json("deleted");
    }
}

In the above code we have three functions fetchAllstore , and delete:

  • fetchAll: queries our database to return all our tasks/to-dos
  • store: creates a new to-do with request params(title and task status)
  • delete: finds a task and deletes from the database.

Emit events

Well you may have noticed these lines: broadcast(new Taskcreated($task)) and broadcast(new TaskRemoved($task)) respectively in store and delete functions. What is their purpose? Through these lines we emit events with a task model instance.

You can get more relevant information about Laravel broadcasting here. In the next part of the tutorial, we’ll see how to create these events..

Create our events

In this part we’ll create events we talked about above: TaskCreated and TaskRemoved events.

TaskCreated event

Our TaskCreated event will be emitted whenever a new to-do or task is created. Enough talk, let’s focus on the code. Let’s create our TaskCreated by running the following command in your terminal: php artisan make:event TaskCreated.

Now open your TaskCreated file and paste the following:

 //realtime_todo/app/Events/TaskCreated.php
<?php

namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;


class TaskCreated implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
    * Create a new event instance.
    *
    * @param $task
    * @return void
    */

    public $task;
    public function __construct($task)
    {
        //
        $this-&gt;task = $task;
    }

    /**
    * Get the channels the event should broadcast on.
    *
    * @return \Illuminate\Broadcasting\Channel|array
    */

    public function broadcastOn()
    {
        return new Channel('newTask');
    }

    public function broadcastAs(){
        return 'task-created';
    }

}

Our class constructor initializes a task that is created. We have two additional functions that may seem strange to you:

  • broadcastAs: customizes the broadcast name because by default Laravel uses the event’s class name.
  • broadcastOn: defines the channel task-created (which we’ll set up further on the tutorial) on which our event should be broadcast.

TaskRemoved event

This event is broadcast when a task is deleted and we want to notify other users of that.

As you may guess, you can run: php artisan make:event TaskRemoved to create the event. Now head up to your TaskRemoved file:

 //realtime_todo/app/Events/TaskRemoved.php

&lt;?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class TaskRemoved implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
    * Create a new event instance.
    *
    *  @param $task
    * @return void
    *
    */
        public $task;
        public function __construct($task)
        {
        //
        $this-&gt;task = $task;
    }

    /**
    * Get the channels the event should broadcast on.
    *
    * @return \Illuminate\Broadcasting\Channel|array
    */

    public function broadcastOn()
    {
        return new Channel('taskRemoved');
    }

    public function broadcastAs(){
        return 'task-removed';
    }

}

This class structure is pretty similar to the previous one, so we won't spend further time explaining its functions.

Don’t forget to implement ShouldBroadcastNow to enable Pusher broadcasting events as soon as they occur.

Broadcast configuration

According to Laravel documentation about event broadcasting, before broadcasting any events, you will first need to register the App\Providers\BroadcastServiceProvider. In fresh Laravel applications, you only need to uncomment this provider in the providers array of your ../config/app.php configuration file. This provider will allow you to register the broadcast authorization routes and callbacks.

You may also set the encrypted value to false in order to avoid a 500 error while broadcasting events with Pusher.

If this is done, you have to tell Laravel to use Pusher to broadcast events. Open your .env file and ensure you have this line: BROADCAST_DRIVER=pusher

As we are broadcasting our events over Pusher, we should install the Pusher PHP SDK using the Composer package manager:

composer require pusher/pusher-php-server "~3.0"

Setting up the broadcast channel

Laravel broadcasts events on well defined channels. As said above our events should be broadcast on particular channels. It’s time to set them up. Paste the following code in your channels.php file:

 //realtime_todo/routes/channels.php
<?php

/*
------------------------------------------------------------------------
| Broadcast Channels
|--------------------------------------------------------------------------
|
| Here you may register all of the event broadcasting channels that your
| application supports. The given channel authorization callbacks are
| used to check if an authenticated user can listen to the channel.
|
*/
Broadcast::channel('newTask', function(){
    return true;
});
Broadcast::channel('taskRemoved', function(){
    return true;
});

As we aren’t using Laravel auth, we return true in the function callback so that everybody can use this channel to broadcast events.

Set up Laravel Echo

We’ll use Laravel Echo to consume our events on the client-side.

Open your resources/js/bootstrap.js file and uncomment this section of the code:

import Echo from 'laravel-echo'

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    encrypted: false
});

While uncommenting, you may also set the encrypted property to false to avoid a 500 error while trying to broadcast events with Pusher channels.

The above code sets up Laravel Echo with Pusher. This will make our app aware of events broadcast, and Laravel Echo will consume our events

Our app is ready to broadcast and consume events in realtime using Pusher channels. Let’s focus now on the frontend of your app.

Set up Vuex store

We’ll be using the Vuex library to centralize our data and control the way it is mutated throughout our application.

Create our state

Vuex state is a single object that contains all our application data. So let’s create../resources/js/store/state.js and paste this code inside:

 let state = {
todos: [],
toRemove: null,
newTodo: {
title: '',
completed: false
}
}
export default state

Our state objects holds :

  • todos: holds our to-dos got from the backend
  • toRemove: holds temporarily the to-do we intend to remove
  • newTodo: holds details about a new to-do we are about to add

Create our getters

With help of getters we can compute derived state based on our data store state. Create../resources/js/store/getters.js and paste this code inside

 let getters = {
newTodo: state => {
return state.newTodo
},
todos: state => {
return state.todos
},
toRemove: state => {
return state.toRemove
}
}
export default getters

Create our mutations

Mutations allow us to perform some changes on our data. Create ../resources/js/store/mutations.jsand paste this piece of code inside:

let mutations = {
ADD_TODO(state, todo) {
state.todos.unshift(todo)
},
CACHE_REMOVED(state, todo) {
state.toRemove = todo;
},
GET_TODOS(state, todos) {
state.todos = todos
},
DELETE_TODO(state, todo) {
state.todos.splice(state.todos.indexOf(todo), 1)
state.toRemove = null;
}
}
export default mutations

Here we have three mutations:

  • ADD_TODO: adds a new to-do to the top our to-dos list
  • CACHE_REMOVED: keeps track temporarily of the to-do to remove
  • GET_TODOS: sets our to-dos list given some data
  • DELETE_TODO: responsible for deleting a to-do from our to-dos list

Create our actions

Vuex actions allow us to perform asynchronous operations over our data. Create the file ../resources/js/store/actions.js and paste the following code:

let actions = {
ADD_TODO({commit}, todo) {
axios.post('/api/todos', todo).then(res => {
if (res.data === "added")
console.log('ok')
}).catch(err => {
console.log(err)
})
},
DELETE_TODO({commit}, todo) {
axios.delete(/api/todos/${todo.id})
.then(res => {
if (res.data === 'deleted')
console.log('deleted')
}).catch(err => {
console.log(err)
})
},
GET_TODOS({commit}) {
axios.get('/api/todos')
.then(res => {
{ console.log(res.data)
commit('GET_TODOS', res.data)
}
}).catch(err => {
console.log(err)
})
}
}
export default actions

We have defined two actions and each of them responsible of a single operation. They perform asynchronous calls to our API routes.

  • ADD_TODO makes a POST request to our api/todos endpoint to add a new task. This action is dispatched whenever the user is submitting a task to add.
  • GET_TODOS sends a GET request to our api/todos endpoint to get our database to-dos/tasks and commits the response with GET_TODOS mutation.
  • DELETE_TODO performs a DELETE a request to our api/todos/{id} endpoint in order to remove a to-do from our to-dos list.

Set up our store with Vue

Create the file ../resources/js/store/index.js and paste this code inside:

 import Vue from 'vue'
import Vuex from 'vuex'
import actions from './actions'
import mutations from './mutations'
import getters from './getters'
import state from "./state";

Vue.use(Vuex);

export default new Vuex.Store({
    state,
    mutations,
    getters,
    actions
})

Then, we export our store and add it to the Vue instance. Replace the existing code in your ../resouces/js/app.js file with the following:

 require('./bootstrap');
window.Vue = require('vue');

Vue.component('todo', require('./components/Todo'))
Vue.component('new-todo', require('./components/NewTodo.vue'))
Vue.component('todo-list', require('./components/TodoList'))
Vue.component('todo-app', require('./components/TodoApp'))
import store from '../js/store'

const app = new Vue({
    el: '#app',
    store
});

The previous code also globally registers four Vue components, Todo.vue ,NewTodo.vue,TodoList.vueand TodoApp.vue that we’ll build in the next part of this tutorial.

Building Vue components

We’ll build four Vue components for our app as we said above, so let’s do it.

Create the Todo.vue component

The Todo.vue component is responsible for encapsulating details about a single Task instance from the database and rendering it in a proper and styled way. Create a Todo.vue file in your ../resources/js/components directory and paste the following inside:

//../resources/js/components/Todo.vue
<template>
<li class="todo" :class="{ completed: todo.completed }">
<div class="view">
<input type="checkbox" v-model="todo.completed" class="toggle">
<label>{{todo.title}}</label>
<button @click="removeTodo(todo)" class="destroy"></button>
</div>
</li>
</template>
<script>
export default {
name: "Todo",
props: ["todo"],

  methods: {
    removeTodo(todo) {
      this.$store.commit("CACHE_REMOVED", todo)
      this.$store.dispatch("DELETE_TODO", todo);
    }
  }
};
&lt;/script&gt;

Our Todo.vue component takes a todo property whose details we render in the component body using the HTML <li></li> tag. The component has the removeTodo function that takes the to-do we intend to remove as argument, caches it temporarily (via the CACHE_REMOVED mutation) and dispatches the DELETE_TODO action to remove it.

Create the NewTodo.vue component

We’ll use this component to add a new task to our to-dos list. This component should be very simple to code. I promise you . First create the NewTodo.vue file inside the same directory as above and paste this inside:

//../resources/js/components/NewTodo.vue
<template>
<input type="text" v-model="newTodo.title"
@keyup.enter="addTodo" autofocus="autofocus"
placeholder="What are you trying to get done?" class="new-todo">
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "NewTodo",
methods: {
addTodo() {
this.$store.dispatch("ADD_TODO", this.newTodo);
}
},
computed: {
...mapGetters(["newTodo"])
}
};
</script>

This component is composed of a simple input field to enter our to-do title. We append a @keyup.enter event so we can execute the addTodo function whenever the user hits the Enter key of his keyboard. Nice play isn’t it ?! We get the newTodo state object from our getters using Vue.js mapGetters helper and bind it to our input. As I said above the newTodo object should contain information of a new todo we want to add to our to-dos list. The addTodo function dispatches the ADD_TODO action having our newTodo as a parameter.

Create the TodoList.vue component

This component will render to-dos items from database. It’s that simple. So create your TodoList.vuecomponent and paste this code inside:

 //../resources/js/components/TodoList.vue
<template>
<ul class="todo-list">
<todo v-for="todo in todos" :todo="todo" :key="todo.id" />
</ul>
</template>

&lt;script&gt;
import { mapGetters } from "vuex";
import todo from "../components/Todo";
export default {
  components: {
    todo
  },
  name: "TodoList",
  mounted() {
    this.$store.dispatch("GET_TODOS");
  },
  computed: {
    ...mapGetters(["todos"]),
  }
};
&lt;/script&gt;

In the mounted hook function we dispatch the GET_TODOS action to get our to-dos list item, and we use Vuex helper function …mapGetters() to access our todos state. We loop over our to-dos list and render a todo component (imported from Todo.vue component) which takes the current loop item as a property.

Create the TodoApp.vue component

In this component we simply merge the first three components we created and listen to Pusher realtime events. Create your TodoApp.vue component and paste the following inside:

 //../resources/js/components/TodoApp.vue

 &lt;template&gt;
     &lt;section class="todoapp"&gt;
            &lt;header class="header"&gt;
              &lt;h1&gt;todos&lt;/h1&gt;
            &lt;/header&gt;
            &lt;new-todo&gt;&lt;/new-todo&gt;
            &lt;todo-list&gt;&lt;/todo-list&gt;
     &lt;/section&gt;
 &lt;/template&gt;
 &lt;script&gt;
import newTodo from "../components/NewTodo.vue";
import todoList from "../components/TodoList.vue";
import { mapGetters } from "vuex";

export default {
  components: {
   newTodo,
   todoList
  },
  name: "TodoApp",
  mounted() {
    window.Echo.channel("newTask").listen(".task-created", e =&gt; {
      this.$store.commit("ADD_TODO", e.task);
      this.newTodo.title = "";
    });
    window.Echo.channel("taskRemoved").listen(".task-removed", e =&gt; {
        this.$store.commit("DELETE_TODO", this.toRemove);
    });
  },
  computed: {
    ...mapGetters(["newTodo", "toRemove"])
  }
};
&lt;/script&gt;

In the mounted function of our component, we are subscribing to two channels:

  • newTask channel: we listen the task-created event triggered when a new to-do is added to the list. Then we commit the ADD_TODO mutation with the task sent carried by the event, in order to add it to our to-dos list. Finally we reset our newTodo we import from our Vuex store.
  • taskRemoved channel, this channel enables to listen to the task-removed event triggered when a task/to-do is removed from our list. When the event is emit, we assign the task deleted to our toRemove object we set up in our Vuex store, and we commit the DELETE_TODO mutation to finally remove it from to-dos list.
Finalize the app

Now, let’s replace of our welcome.blade.php with the following containing our TodoApp component:

//realtime_todo/resources/views/welcome.blade.php

&lt;!doctype html&gt;
&lt;html lang="{{ str_replace('_', '-', app()-&gt;getLocale()) }}"&gt;
    &lt;head&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
        &lt;meta name="csrf-token" content="{{ csrf_token() }}" /&gt;
        &lt;title&gt;Realtime to-do app&lt;/title&gt;
        &lt;!-- Fonts --&gt;
        &lt;link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css"&gt;
        {{--  Style  --}}
        &lt;link href="{{ mix('css/app.css') }}" rel="stylesheet" type="text/css"&gt;
        &lt;!-- Styles --&gt;
        &lt;style&gt;
            html, body {
                background-color: #fff;
                color: #636b6f;
                font-family: 'Nunito', sans-serif;
                font-weight: 200;
                height: 100vh;
                margin: 0;
            }
            .full-height {
                height: 100vh;
            }
            .flex-center {
                align-items: center;
                display: flex;
                justify-content: center;
            }
            .position-ref {
                position: relative;
            }
            .top-right {
                position: absolute;
                right: 10px;
                top: 18px;
            }
            .content {
                text-align: center;
            }
            .title {
                font-size: 84px;
            }
            .links &gt; a {
                color: #636b6f;
                padding: 0 25px;
                font-size: 12px;
                font-weight: 600;
                letter-spacing: .1rem;
                text-decoration: none;
                text-transform: uppercase;
            }
            .m-b-md {
                margin-bottom: 30px;
            }
        &lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
          &lt;div id="app"&gt;
                    &lt;todo-app&gt;&lt;/todo-app&gt;
          &lt;/div&gt;

          &lt;script src="{{mix('js/app.js')}}"&gt;&lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;

To style our app, get this file and replace the content inside your ../resources/sass/app.scss file with it.

Now open your terminal and run npm run dev to build your app in a proper way. This can take a few seconds. After this step run php artisan serve and open your browser at localhost:8000 to see your app working fine. You can try to add a new to-do to your list and see things working in realtime if you carefully followed steps above. You are now a boss

Note: If you encounter a 500 error when trying to add or delete to-dos, it’s sure that you have to disable Pusher encryption as I suggested you. Open these files../config/broadcasting.php and ../resources/js/bootstrap.js and make sure you disable Pusher encryption encrypted: false in both of them.
Conclusion

In this tutorial we’ve created a realtime to-do app using Laravel, Vue.js, and Pusher to provide realtime functionality. You can think up new ideas to extend the application. It’ll be fun to see what you come up with. The source code for this tutorial is available on GitHub here.

Thanks for reading. If you liked this post, share it with all of your programming buddies!

Originally published on pusher.com