How to build a collaborative note app using Laravel 
Tags: laravel, vue, pusher
Published: 2018-01-15 11:57:13

---
title: How to build a collaborative note app using Laravel
published: true
description: 
tags: Laravel, Vue.js, Pusher
---

In this tutorial, we'll build an online collaborative note app using Laravel and Pusher. We'll be using Vue.js as our JavaScript framework. The app is going to be basic but will demonstrate the necessary features of a collaborative application since that's the focus of this tutorial.

## What We'll Be Building

Before we get our hands busy, let's go over what we'll be building. The app will be a simple note taking app that is accessible only to authenticated users. With the app, a user can create new note, edit the note and/or share the link to the note to other users for editing. In the case of editing a note, the app will be able to keep track of the users editing a particular note, show other users realtime edits that are being made on the note and lastly notify the other users when a user saves the note.

<img src="http://blog.pusher.com/wp-content/uploads/2017/04/how-to-build-an-online-collaborative-note-app-using-laravel-and-pusher-laravel-notes.gif" alt="" width="800" height="500" class="alignnone size-full wp-image-2821" />

Let's get started!

## Setting Up Laravel

Create a new Laravel project by opening your terminal and run the code below:

``` language-bash```
laravel new laravel-notes
```
Next, we need to setup our new Laravel project. First, we need to register the `App\Providers\BroadcastServiceProvider`. Open `config/app.php` and uncomment `App\Providers\BroadcastServiceProvider` in the providers array.

We then need to tell Laravel that we are using the Pusher driver in the `.env` file:

``` 
// .env

BROADCAST_DRIVER=pusher
```
Since we specified we want to use Pusher as our broadcasting driver, we need to install the Pusher PHP SDK:

``` language-bash
composer require pusher/pusher-php-server Setting Up Pusher
```
## Setting Up Pusher

If you don’t have one already, create a free Pusher account [here](https://pusher.com/signup) then log in to your dashboard and create an app. Take note of your app credentials as we’ll be using them shortly. For the purpose of this tutorial, we'll be triggering some client events in our online collaborative note app.

By default, when you create a Pusher app, client events are not enabled. We have to enable this for our app. To enable client events in your Pusher app, select the app then click on the **App Settings** tab, then check the box next to **Enable client events**.

<img src="http://blog.pusher.com/wp-content/uploads/2017/04/how-to-build-an-online-collaborative-note-app-using-laravel-and-pusher-laravel-notes-enable-client-event.png" alt="" width="1124" height="461" class="alignnone size-full wp-image-2820" />

Now, let’s fill in our Pusher app credentials. Update the `.env` file to contain our Pusher app credentials:

``` 
// .env

PUSHER_APP_ID=xxxxxx
PUSHER_APP_KEY=xxxxxxxxxxxxxxxxxxxx
PUSHER_APP_SECRET=xxxxxxxxxxxxxxxxxxxx
```
Remember to replace the **xs** with your Pusher app credentials. You can find your app credentials under the Keys section on the overview tab in the Pusher Dashboard.

Also, remember to fill in the cluster of your Pusher app and other additional options.

## Installing Frontend Dependencies

For this tutorial, we’ll be using Bootstrap, Vue and Axios, which have been setup for us by Laravel, though we still need to install each of the dependencies. To compile our CSS and JavaScript, we need to install Laravel Mix, which is a wrapper around Webpack. We can install these dependencies through NPM:

``` language-bash
npm install
```
We also need to install Laravel Echo, which is a JavaScript library that makes it painless to subscribe to channels and listen for events broadcast by Laravel and of course the Pusher JavaScript library:

``` language-bash
npm install --save laravel-echo pusher-js
```
Once installed, we need to tell Laravel Echo to use Pusher. At the bottom of the `resources/assets/js/bootstrap.js` file, uncomment the Laravel Echo section and update the details with:

``` language-javascript
// resources/assets/js/bootstrap.js

import Echo from "laravel-echo"

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: xxxxxxxxxxxxxxxxxxxx,
});
```
Remember to replace the **xs** with your Pusher app key.

## Authenticating Users

As mentioned earlier, our collaborative note app will be only accessible to authenticated users. So, we need an authentication system:

``` language-bash
php artisan make:auth
```
This will create the necessary routes, views and controllers needed for an authentication system.

Before we go on to create users, we need to run the `users` migration that comes with a fresh installation of Laravel. But to do this, we first need to set up our database. Open the `.env` file and enter your database details:

``` 
// .env

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel-notes
DB_USERNAME=root
DB_PASSWORD=
```
Update with your own database details. Now, we can run our migration:

``` language-bash
php artisan migrate
```
**NOTE:** There’s a bug in Laravel 5.4 if you’re running a version of MySQL older than 5.7.7 or MariaDB older than 10.2.2. This can be fixed by replacing the `boot()` of `app/Providers/AppServiceProvider.php` with:

``` language-php
// app/Providers/AppServiceProvider.php

// remember to use
Illuminate\Support\Facades\Schema;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
  Schema::defaultStringLength(191);
}
```
## Note Model and Migration

Create a `Note` model along with the migration file by running the command:

``` language-bash
php artisan make:model Note -m
```
Open the `Note` model and add the code below to it:

``` language-php
/**
 * Fields that can not be mass assigned
 * 
 * @var array
 */
protected $guarded = ['id'];

/**
 * Get the route key for the model.
 *
 * @return string
 */
public function getRouteKeyName()
{
  return 'slug';
}
```
Instead of manually specifying each field that can be mass assigned in the `$fillable` array, we simply use `$guarded` and add the `id` column as the field that can not be mass assigned, meaning every other field can be mass assigned. Laravel route model bind will by default use the `id` column on the model, but in this tutorial, we want to use the `slug` column instead, hence the `getRouteKeyName` method which will simply return the column we want to use for route model binding.

Within the `databases/migrations` directory, open the `notes` table migration that was created when we ran the command above and update the `up` method with:

``` language-php
Schema::create('notes', function (Blueprint $table) {
  $table->increments('id');
  $table->unsignedInteger('user_id');
  $table->string('title');
  $table->string('slug')->unique();
  $table->text('body');
  $table->timestamps();
});
```
Run the migration:

``` language-bash
php artisan migrate
```
## Defining App Routes

Open `routes/web.php` and replace the routes with the code below:

``` language-php
Auth::routes();

Route::get('/', 'NotesController@index');
Route::get('create', 'NotesController@create');
Route::post('create', 'NotesController@store');
Route::get('edit/{note}', 'NotesController@edit');
Route::patch('edit/{note}', 'NotesController@update');
```
The routes are straightforward: routes that will handle authentication, a homepage route to list all notes created a user, routes for creating a new note and lastly routes to update a specified note.

**NOTE:** Since we have removed the `/home` route, you might want to update the `redirectTo` property of both `app/Http/Controllers/Auth/LoginController.php` and `app/Http/Controllers/Auth/RegisterController.php` to:

``` language-php
protected $redirectTo = '/';
```
## NotesController

Let’s create the controller which will handle the logic of our chat app. Create a `NotesController` with the command below:

``` language-bash
php artisan make:controller NotesController
```
Open the new create `app/Http/Controllers/NotesController.php` file and add the following code to it:

``` language-php
// app/Http/Controllers/NotesController.php

use App\Note;

public function __construct()
{
  $this->middleware('auth');
}

/**
 * Display a listing of all notes.
 *
 * @return \Illuminate\Http\Response
 */
public function index()
{
  $notes = Note::where('user_id', auth()->user()->id)
                  ->orderBy('updated_at', 'DESC')
                  ->get();

  return view('notes.index', compact('notes'));
}

/**
 * Show the form for creating a new note.
 *
 * @return \Illuminate\Http\Response
 */
public function create()
{
  return view('notes.create');
}

/**
 * Store a newly created note in database.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function store(Request $request)
{
  $this->validate($request, [
    'title' => 'required',
    'body'  => 'required'
  ]);

  $note = Note::create([
    'user_id' => $request->user()->id,
    'title'   => $request->title,
    'slug'    => str_slug($request->title) . str_random(10),
    'body'    => $request->body
  ]);

  return redirect('/');
}

/**
 * Show the form for editing the specified note.
 *
 * @param  \App\Note  $note
 * @return \Illuminate\Http\Response
 */
public function edit(Note $note)
{
  return view('notes.edit', compact('note'));
}

/**
 * Update the specified note.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \App\Note  $note
 * @return \Illuminate\Http\Response
 */
public function update(Request $request, Note $note)
{
  $note->title = $request->title;
  $note->body = $request->body;

  $note->save();

  return 'Saved!';
}
```
Using the auth middleware in `NotesController`‘s `__contruct()` indicates that all the methods with the controller will only be accessible to authenticated users. The `index` method will fetch the notes created by the currently authenticated user and render a view with notes. The `create` method will display a form to create new note. The `store` method will do the actual persisting of the note to the database. Notice we're appending a random string to the slug so as to make it unique for each note. The `edit` method shows the form for editing a specified note. Lastly, the `update` method handles the actual update and persist to database.

## Creating Our Note App Views

When we ran `make:auth`, Laravel created a master layout called `app.blade.php` which we are going to leverage with some slight additions. So open `resources/view/layouts/app.blade.php` and update the left side of the navbar with:

``` language-html
<!-- resources/view/layouts/app.blade.php -->

<!-- Left Side Of Navbar -->
<ul class="nav navbar-nav">
  <li><a href="{{ url('create') }}">Create Note</a></li>
</ul>
```
All we did is add a link to create new note on the navbar.

### Create New Note View

Now, let's create the view for creating a new note. Create a new directory named `notes` within the `views` directory. Within the newly created `notes` directory, create a new file named `create.blade.php` and paste the code below to it:

``` language-html
<!-- resources/views/notes/create.blade.php -->
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Create new note</div>
                    <div class="panel-body">
                        <form action="{{ url('create') }}" method="POST" class="form" role="form">
                            {{ csrf_field() }}

                            <div class="form-group{{ $errors->has('title') ? ' has-error' : '' }}">
                                <input type="text" class="form-control" name="title" value="{{ old('title') }}" placeholder="Give your note a title" required autofocus>

                                @if ($errors->has('title'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('title') }}</strong>
                                    </span>
                                @endif
                            </div>

                            <div class="form-group{{ $errors->has('body') ? ' has-error' : '' }}">
                                <textarea class="form-control" name="body" rows="15" placeholder="...and here goes your note body" required>{{ old('body') }}</textarea>

                                @if ($errors->has('body'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('body') }}</strong>
                                    </span>
                                @endif
                            </div>

                            <button class="btn btn-primary pull-right">Save</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
This creates a form with two input fields (for title and body of the note respectively) and a save button.

### List All Notes View

Let's give our users a way to see all the notes they have created. Within the `notes` directory, create a new file named `index.blade.php` and paste the code below into it:

``` language-html
<!-- resources/views/notes/index.blade.php -->

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">My notes</div>
                    <div class="panel-body">
                        @if($notes->isEmpty())
                            <p>
                                You have not created any notes! <a href="{{ url('create') }}">Create one</a> now.
                            </p>
                        @else
                        <ul class="list-group">
                            @foreach($notes as $note)
                                <li class="list-group-item">
                                    <a href="{{ url('edit', [$note->slug]) }}">
                                        {{ $note->title }}
                                    </a>
                                    <span class="pull-right">{{ $note->updated_at->diffForHumans() }}</span>
                                </li>
                            @endforeach
                        </ul>
                        @endif
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
```
The simply displays a message if the user has not created any notes and a link to create a new note. Otherwise it will display all the notes created by the user in a list.

### Edit Note View

Let's create the edit view which will allow users to edit a note. Within the `notes` directory, create a new file named `edit.blade.php` and paste the code below into it:

``` language-html
<!-- resources/views/notes/edit.blade.php -->

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <edit-note :note="{{ $note }}"></edit-note>
            </div>
        </div>
    </div>
@endsection
```
You will notice we're using a custom tag `` with the edit view, this is our view component which we'll create shortly.

Now let's create a Vue component. Create a new file named `EditNote.vue` within `resources/assets/js/components` directory and paste the code below to it:

``` language-javascript
// resources/assets/js/components/EditNote.vue

<template>
    <div class="panel panel-default">
        <div class="panel-heading">Edit note</div>
        <div class="panel-body">
            <div class="form-group">
                <input type="text" class="form-control" v-model="title" @keydown="editingNote">
            </div>

            <div class="form-group">
                <textarea class="form-control" rows="15" v-model="body" @keydown="editingNote"></textarea>
            </div>

            <button class="btn btn-primary pull-right" @click="updateNote">Save</button>

            <p>
                Users editing this note:  <span class="badge">{{ usersEditing.length }}</span>
                <span class="label label-success" v-text="status"></span>
            </p>
        </div>
    </div>
</template>

<script>
    export default {
        props: [
            'note',
        ],

        data() {
            return {
                title: this.note.title,
                body: this.note.body,
                usersEditing: [],
                status: ''
            }
        },

        mounted() {
            Echo.join(`note.${this.note.slug}`)
                .here(users => {
                    this.usersEditing = users;
                })
                .joining(user => {
                    this.usersEditing.push(user);
                })
                .leaving(user => {
                    this.usersEditing = this.usersEditing.filter(u => u != user);
                })
                .listenForWhisper('editing', (e) => {
                    this.title = e.title;
                    this.body = e.body;
                })
                .listenForWhisper('saved', (e) => {
                    this.status = e.status;

                    // clear is status after 1s
                    setTimeout(() => {
                        this.status = '';
                    }, 1000);
                });
        },

        methods: {
            editingNote() {
                let channel = Echo.join(`note.${this.note.slug}`);

                // show changes after 1s
                setTimeout(() => {
                    channel.whisper('editing', {
                        title: this.title,
                        body: this.body
                    });
                }, 1000);
            },

            updateNote() {
                let note = {
                    title: this.title, 
                    body:  this.body
                };

                // persist to database
                axios.patch(`/edit/${this.note.slug}`, note)
                    .then(response => {
                        // show saved status
                        this.status = response.data;

                        // clear is status after 1s
                        setTimeout(() => {
                            this.status = '';
                        }, 1000);

                        // show saved status to others
                        Echo.join(`note.${this.note.slug}`)
                            .whisper('saved', {
                                status: response.data
                            });
                    });
            }
        }
    }
</script>
```
Let's explain each piece of the code. Just like we have in the 'create new note' form, the template section has two input fields: title and body. Each field is bound to data (title and body respectively). Once a user starts typing (that is, a keydown event) in any of the input fields, the `editingNote` method will be triggered. Also, when the save button is clicked, the `updateNote` method will be triggered. (We'll take a close look at these methods soon) Lastly on the template section, we display the number of users who are currently editing the specified note and also display a status message once the save button is clicked.

Moving to the script section of the component, first we define a property for the component called `note`. This `note` property will be the note that is currently being edited. Recall from the edit view where we used the `EditNote` component, you will notice we passed the whole note object as the component's `note` property. Next we define some data, the `title` and the `body` data are bound to respective input fields, the `usersEditing` will be an array of users editing the note and `status` will serve as an indicator for when a note's edits have been persisted to the database. The `mount` method will be triggered immediately the component is mounted, so it's a nice place to subscribe and listen to a channel. In our case, because we to be able to keep track of users editing a note, we'll make use of Pusher's presence channel.

Using Laravel Echo, we can subscribe to a presence channel using `Echo.join('channel-name')`. As you can see our channel name is `note.note-slug`. Once we subscribe to a presence channel, we can get all the users that are subscribed to the channel with the `here` method where we simply assign the subscribed users to the `usersEditing` array. When a user joins the channel, we simply add that user to the `usersEditing` array. Similarly, when a user leaves the channel, we remove that user from the `usersEditing` array. To display edits in realtime to other users, we listen for client events that are triggered as a user types using `listenForWhisper` and update the form data accordingly. In the same vein, we listen for when edits are saved and display the "Saved!" status to other users, then after a second we clear the status message.

Next, we define the methods we talked about earlier. The `editingNote` method simply triggers a client event to all users currently subscribed to the channel after a specified time (1 second). The `updateNote` method on the other hand sends a `PATCH` request with the edits made to persist the edits to the database. Once the request is successful, we display the message saved status to the user that made the save and clear the status message after 1 second. Lastly, we trigger a client event so other users can also see the message saved status.

Since we created a presence channel, only authenticated users will be able to subscribe and listen on the note channel. So, we need a way to authorize that the currently authenticated user can actually subscribe and listen on the channel. This can be done in the `routes/channels.php` file:

``` language-php
// routes/channels.php

Broadcast::channel('note.{slug}', function ($user, $slug) {
    return [
        'id'   => $user->id,
        'name' => $user->name
    ];
});
```
We pass to the `channel()`, the name of our channel and a callback function that will return the details of the user if the current user is authenticated.

Now let's register our new created component with our Vue instance, open `resources/assets/js/app.js` and add the line below just before Vue instantiation:

``` language-javascript
// resources/assets/js/app.js

Vue.component('edit-note', require('./components/EditNote.vue'));
```
Before testing out our online collaborative note app, we need to compile the JavaScript files using Laravel Mix using:

``` language-bash
npm run dev
```
Now we can start our note app by running:

``` language-bash
php artisan serve
```
The code of the complete demo is available on [GitHub](https://github.com/ammezie/laravel-notes).

## Conclusion

We have seen how to build a simple online collaborative note app using Laravel and Pusher. Sure there are other ways of accomplishing what we did in this tutorial, but we have seen how to build a collaborative app with Pusher's real-time features. Also, you will notice our note app doesn’t account for cases like concurrent editing of notes; to achieve that you'd want to look into [Operational Transformation](http://operational-transformation.github.io/).

This post was originally posted by the author on the [Pusher blog](https://blog.pusher.com/build-collaborative-note-app-using-laravel).