AppDividend
Latest Code Tutorials

Laravel and Vue: How to Build a Twitter Type Web Application

In this Laravel and Vue Example, we will build a Twitter-type web application. For this tutorial, I am using Laravel and Vue.js. I am defining some components inside Vue.js. Also, I am using Axios to send a network request.

Laravel and Vue Example

Install Laravel and its NPM dependencies.

We build an application in which the user can post the tweet and appears in his timeline. Also, one user can follow or unfollow each other. If the currently signed-in user follows any other registered user, he can also see the following user’s tweet in his timeline: the fundamental app, but the compelling web app to understand laravel and vue.js fundamental concepts.

Step 1: Install and configure Laravel.

Type the following command in the terminal.

laravel new laratwitter

Go to the folder.

cd laratwitter

Install the front-end dependencies using the following command.

npm install

Now, configure the database inside the .env file.

Create an authentication using the following command.

php artisan make:auth

Migrate all the tables in the database using the following command.

php artisan migrate

Register one user on this URL: http://laratwitter.test/register

Next step is to modify the resources >> views >> home.blade.php file.

We need to add two things to that page.

  1. Tweet Form
  2. Timelines
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-4">
            Tweet Form
        </div>
        <div class="col-md-8">
            TimeLines
        </div>
    </div>
</div>
@endsection

Okay, now go to this URL: http://laratwitter.test/home, and you can see the changes.

Step 2: Create a Tweet Form.

We are using Vue.js. So let us create a component called FormComponent.vue inside resources >> assets >> js >> components folder.

Write the following code inside FormComponent.vue file.

// FormComponent.vue

<template>
    <div class="col-md-4">
        <form>
            <div class="form-group">
                <textarea 
                    class="form-control" 
                    rows="8" cols="8" 
                    maxlength="130" 
                    required>
                </textarea>
            </div>
            <div class="form-group">
                <button class="btn btn-primary">
                    Tweet
                </button>
            </div>
        </form>        
    </div>
</template>
<script>
export default {
    
}
</script>

Import this component inside resources >> assets >> js >> app.js file.

// app.js

require('./bootstrap');

window.Vue = require('vue');

Vue.component('form-component', require('./components/FormComponent.vue'));

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

Also, we need to add this component inside a home.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
       <form-component></form-component>
        <div class="col-md-8">
            TimeLines
        </div>
    </div>
</div>
@endsection

Now, refresh the webpage, and you can see the form component rendered inside the blade file.

Step 3: Create a Post model and migration.

Okay, now create a model and migration.

php artisan make:model Post -m

Write the following schema inside a create_posts_table.php file.

// create_posts_table

public function up()
{
   Schema::create('posts', function (Blueprint $table) {
       $table->increments('id');
       $table->integer('user_id')->unsigned();
       $table->string('body', 130);
       $table->timestamps();

       $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    });
}

Migrate the posts table.

php artisan migrate

You can see that the posts table is created in the MySQL database.

Also, we need to create a PostController. To create a controller by the following command.

php artisan make:controller PostController

Step 4: Define the relationships.

Inside the User.php model, we need to define a function that is a relationship between the Post model and the User model.

// User.php

public function posts()
{
   return $this->hasMany(Post::class);
}

Also, the Post belongs to the User. So we can define the inverse relationship inside the Post.php model.

// Post.php

public function user()
{
   return $this->belongsTo(User::class);
}

Step 5: Saving the Tweet in the database.

When we have used the command npm install, it has already installed the axios library. So we can use the axios to send a POST request to a Laravel backend web server and store the tweet in the database.

But first, let us define the route. So set the route inside routes >> web.php file.

// web.php

Route::post('tweet/save', 'PostController@store');

Also, we need to define the $fillable property inside the Post.php model to prevent the Mass Assignment Exception.

<?php

// Post.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{

    protected $fillable = ['user_id', 'body'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

The final thing is to define the store function inside the PostController.php file.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Post;

class PostController extends Controller
{
    public function store(Request $request, Post $post)
    {
        $newPost = $request->user()->posts()->create([
            'body' => $request->get('body')
        ]);
   
        return response()->json($post->with('user')->find($newPost->id));
    }
}

I have used the relationship to save the Post data, and it automatically adds the currently logged-in user’s id inside the user_id column on every saved post.

Also, we need to use the axios library to send a POST request, so write the code inside FormComponent.vue file.

// FormComponent.vue

<template>
    <div class="col-md-4">
        <form @submit.prevent="saveTweet">
            <div class="form-group">
                <textarea 
                    class="form-control" 
                    rows="8" cols="8" 
                    maxlength="130"
                    v-model="body"
                    required>
                </textarea>
            </div>
            <div class="form-group">
                <button class="btn btn-primary">
                    Tweet
                </button>
            </div>
        </form>        
    </div>
</template>
<script>
export default {
    data() {
        return {
            body: ''
        }
    },
    methods: {
        saveTweet() {
            axios.post('/tweet/save', {body: this.body}).then(res => {
                console.log(res.data);
            }).catch(e => {
                console.log(e);
            });
            
        }
    }
}
</script>

CSRF_TOKEN will be included by default inside every POST request, so we do need to add manually. Now, if everything is set up correctly, then you can be able to save the Post, and in response, you get the Post object with its associated user.

Laravel and Vue Example Build a twitter type web application

 

Step 6: Create a vue event.

To display all the tweets on the front end, we need to first show the tweets to the Timeline, and for that, we need to create an event. Remember, we are not refreshing the page to fetch all the tweets. So when we add a new tweet, we need to display that tweet in the user’s timeline without refresh the page. 

So, for that, we create an Event and then fire that event. 

That fired event listen by Timeline component and update the UI according to it.

We can also use Vuex for this, but right now, we are not diving into Stores and actions; let us keep it simple.

Inside resources >> assets >> js folder, create one js file called event.js. Write the following code inside it.

// event.js

import Vue from 'vue';

export default new Vue();

Now, import this file inside FormComponent.vue file.

// FormComponent.vue

<template>
    <div class="col-md-4">
        <form @submit.prevent="saveTweet">
            <div class="form-group">
                <textarea 
                    class="form-control" 
                    rows="8" cols="8" 
                    maxlength="130"
                    v-model="body"
                    required>
                </textarea>
            </div>
            <div class="form-group">
                <button class="btn btn-primary">
                    Tweet
                </button>
            </div>
        </form>        
    </div>
</template>
<script>
import Event from '../event.js';
export default {
    data() {
        return {
            body: '',
            postData: {}
        }
    },
    methods: {
        saveTweet() {
            axios.post('/tweet/save', {body: this.body}).then(res => {
                this.postData = res.data;
                Event.$emit('added_tweet', this.postData);
            }).catch(e => {
                console.log(e);
            });
            this.body = '';
        }
    }
}
</script>

So, when the new post is saved, we can emit an event that has the saved tweet with the user.

Here, we are emitting the newly saved post with an event, so that listener can catch that data and update the UI with that data.

Step 7: Create a Timeline Component.

Now, we need to create second vue component called TimelineComponent.vue inside resources >> assets >> js >> components folder. Write the following code inside TimelineComponent.vue file.

// TimelineComponent.vue

<template>
    <div class="col-md-8 posts">
        <p v-if="!posts.length">No posts</p>
        <div class="media" v-for="post in posts" :key="post.id">
            <img class="mr-3" />
            <div class="media-body">
                <div class="mt-3">
                    <a href="#">{{ post.user.name }}</a>
                </div>
                <p>{{ post.body }}</p>
            </div>
        </div>
    </div>
</template>
<script>
import Event from '../event.js';

export default {
    data() {
        return {
            posts: [],
            post: {}
        }
    },
    mounted() {
        Event.$on('added_tweet', (post) => {
            this.posts.unshift(post);
        });
    }
}
</script>

So, we are listening for that event, and when the event is listened to by this component, we add the new data to the posts array and iterate the component to display the tweet and user.

Now, register this component inside an app.js file.

// app.js

Vue.component('timeline-component', require('./components/TimelineComponent.vue'));

Also, add this component inside a home.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <form-component></form-component>
        <timeline-component></timeline-component>
    </div>
</div>
@endsection

Save the file and go to the: http://laratwitter.test/home.

Add a new tweet, and you can see the name and tweet.

Step 8: Show time in the timeline.

We need to append one attribute inside the Post.php model to get the time.

So, we can use the append attribute to add the time data and then fetch it in the response. Very simple.

// Post.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{

    protected $fillable = ['user_id', 'body'];

    protected $appends = ['createdDate'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function getCreatedDateAttribute()
    {
        return $this->created_at->diffForHumans();
    }

}

Now, we can access the createdDate property inside TimelineComponent.vue file.

<div class="mt-3">
   <a href="#">
     {{ post.user.name }}
   </a> | {{ post.createdDate }}
</div>

Now, refresh the page, add the tweet, and you can see something like 1 second ago.

Step 9: Create a user profile.

We use Route Model Binding to display the user profile. Then, we create a link based on the user’s name. Generally, we use the username for this to get a unique URL, but for this demo, I am using the name for the unique URL.

Also, we need to add the unique property inside the RegisterController.php file to build the user profile link unique.

// RegisterController.php

protected function validator(array $data)
{
   return Validator::make($data, [
      'name' => 'required|string|max:255|unique:users',
      'email' => 'required|string|email|max:255|unique:users',
      'password' => 'required|string|min:6|confirmed',
   ]);
}

Now, we need to create one controller called UserController. Type the following command.

php artisan make:controller UserController

Now, add the following function inside the UserController.php file.

<?php

// UserController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\User;

class UserController extends Controller
{
    public function show(User $user)
    {
        return view('user', compact('user'));
    }
}

Here, I am using Route Model Binding, but we will use my name on the route. So, we can access it through the user’s name key. So we need to define the routing key inside the User.php file.

// User.php

public function getRouteKeyName()
{
   return 'name';
}

Create a view file called user.blade.php inside the views folder.

<!-- user.blade.php -->

@extends('layouts.app')

@section('content')
<div class="container">
    {{ $user->name }}
</div>
@endsection

Also, we need to define the route inside a web.php file.

// web.php

<?php

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');
Route::post('tweet/save', 'PostController@store');

Route::get('users/{user}', 'UserController@show')->name('user.show');

Let us say; I have registered the user with the name: krunal.

We can access its profile using this URL:http://laratwitter.test/users/krunal

When we hit that URL, we can see the profile page is appearing. So till now, we have done all correctly.

Step 10: Display URL inside TimelineComponent.

Generally, when we need data created from another data from the same table, we do not need to store that data; we append that data in that model object while fetching it.

We can do the same thing here; the profile url is based on the name, so we can append one new data called profileUrl and then append it inside the User.php model.

Create an accessor for a profileLink inside the User.php model file.

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    protected $fillable = [
        'name', 'email', 'password',
    ];

    protected $hidden = [
        'password', 'remember_token',
    ];

    protected $appends = ['profileLink'];

    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    public function getRouteKeyName()
    {
        return 'name';
    }

    public function getProfileLinkAttribute()
    {
        return route('user.show', $this);
    }

}

Now, we can display this URL inside TimelineComponent.vue file.

// TimelineComponent.vue

<template>
    <div class="col-md-8 posts">
        <p v-if="!posts.length">No posts</p>
        <div class="media" v-for="post in posts" :key="post.id">
            <img class="mr-3" />
            <div class="media-body">
                <div class="mt-3">
                    <a :href="post.user.profileLink">{{ post.user.name }}</a> | {{ post.createdDate }}
                </div>
                <p>{{ post.body }}</p>
            </div>
        </div>
    </div>
</template>
<script>
import Event from '../event.js';

export default {
    data() {
        return {
            posts: [],
            post: {}
        }
    },
    mounted() {
        Event.$on('added_tweet', (post) => {
            this.posts.unshift(post);
        });
    }
}
</script>

Step 11: Display Follow or Unfollow link.

Create a migration file for the Followers table.

Type the following command to generate models and migrations.

php artisan make:model Follower -m

Now, write the following schema inside create_followers_table.

// create_users_table

public function up()
{
        Schema::create('followers', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->unsigned();
            $table->integer('follower_id')->unsigned();
            $table->nullableTimestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->foreign('follower_id')->references('id')->on('users')->onDelete('cascade');
            
        });
}

Migrate the table.

php artisan migrate

Now, we need to set up the relationship with the User model.

// User.php

public function following()
{
   return $this->belongsToMany(User::class, followers, user_id, follower_id);
}

The next step is, we need to put some condition that is following.

  1. The user can not follow himself.
  2. If another user is not followed by him, then he can follow the user.
  3. If the user is followed by another user, then he can unfollow them.

We will define each condition with the dedicated function inside the User.php file.

I am writing a total of three functions inside the User.php model.

  1. function isNot()
  2. function isFollowing()
  3. function canFollow()
<?php

// User.php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    protected $fillable = [
        'name', 'email', 'password',
    ];

    protected $hidden = [
        'password', 'remember_token',
    ];

    protected $appends = ['profileLink'];

    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    public function getRouteKeyName()
    {
        return 'name';
    }

    public function getProfileLinkAttribute()
    {
        return route('user.show', $this);
    }

    public function following()
    {
        return $this->belongsToMany(User::class, 'followers', 'user_id', 'follower_id');
    }

    public function isNot($user)
    {
        return $this->id !== $user->id;
    }

    public function isFollowing($user)
    {
        return (bool) $this->following->where('id', $user->id)->count();
    }

    public function canFollow($user)
    {
        if(!$this->isNot($user)) {
            return false;
        }
        return !$this->isFollowing($user);
    }

}

Okay, now we have written all the conditions regarding function, we can head over to the user.blade.php file and implement the follow or unfollow link.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <h3>{{ $user->name }}</h3>
            @if(auth()->user()->isNot($user))
                @if(auth()->user()->isFollowing($user))
                    <a href="#" class="btn btn-danger">No Follow</a>
                @else
                    <a href="#" class="btn btn-success">Follow</a>
                @endif
            @endif
        </div>
    </div>
</div>
@endsection

Okay, now, if you go to your profile, you can not see any follow or unfollow links.

If you sign in with a different user, then you can be able to see follow or unfollow button.

Step 12: Follow the User.

Define the users/{user}/follow route inside a web.php file.

<?php

// web.php

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');
Route::post('tweet/save', 'PostController@store');

Route::get('users/{user}', 'UserController@show')->name('user.show');

Route::get('users/{user}/follow', 'UserController@follow')->name('user.follow');

Now, we can write the follow() function inside the UserController.php file.

// UserController.php

public function follow(Request $request, User $user)
{
    if($request->user()->canFollow($user)) {
        $request->user()->following()->attach($user);
    }
    return redirect()->back();
}

Also, we need to update the user.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <h3>{{ $user->name }}</h3>
            @if(auth()->user()->isNot($user))
                @if(auth()->user()->isFollowing($user))
                    <a href="#" class="btn btn-danger">No Follow</a>
                @else
                    <a href="{{ route('user.follow', $user) }}" class="btn btn-success">Follow</a>
                @endif
            @endif
        </div>
    </div>
</div>
@endsection

Now, when you sign in with another user and type your profile url, you can see that you will be able to follow that user, and then after you click the follow button, you can see that there is unfollow button.

Also, we can see that the followers table now has one entry.

Laravel Vue Example Tutorial

 

Step 13: Unfollow, the User.

We can unfollow the user, which is currently following. So we can write the following function inside the User.php file.

// User.php

public function canUnFollow($user)
{
    return $this->isFollowing($user);
}

Also, we need to define the unfollow route inside a web.php file.

// web.php

Route::get('users/{user}/unfollow', 'UserController@unfollow')->name('user.unfollow');

Now, write the unFollow() function inside UserController.php file.

// UserController.php

public function unFollow(Request $request, User $user)
{
   if($request->user()->canUnFollow($user)) {
       $request->user()->following()->detach($user);
    }
       return redirect()->back();
}

Also, we need to update the user.blade.php file.

@if(auth()->user()->isNot($user))
      @if(auth()->user()->isFollowing($user))
           <a href="{{ route('user.unfollow', $user) }}" class="btn btn-danger">No Follow</a>
      @else
           <a href="{{ route('user.follow', $user) }}" class="btn btn-success">Follow</a>
      @endif
@endif

Now, finally, the follow and unfollow functionality is over.

Step 14: Fetch tweets based on the following user.

Okay, till now, we have completed all the tasks.

The next task is to fetch all tweets whom you follow. So, when we start following any users, we can see their tweets.

So, we can define the index() function inside the PostController.php file.

<?php

// PostController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Post;

class PostController extends Controller
{

    public function index(Request $request, Post $post)
    {
        $posts = $post->whereIn('user_id', $request->user()->following()
                        ->pluck('users.id')
                        ->push($request->user()->id))
                        ->with('user')
                        ->orderBy('created_at', 'desc')
                        ->take($request->get('limit', 10))
                        ->get();
          
        return response()->json($posts);
    }

    public function store(Request $request, Post $post)
    {
        $newPost = $request->user()->posts()->create([
            'body' => $request->get('body')
        ]);
   
        return response()->json($post->with('user')->find($newPost->id));
    }
}

So, here, I have fetched all the posts, which are currently signed in user as well as all the user’s that is currently signed in user is following. It may be more than one user.

Basically, what I am trying to say is that currently, signed-in users can see his post and other people’s post, which he is following at the moment.

Now, all we need to do is display all the posts.

Define the route for this index() function.

// web.php

Route::get('posts', 'PostController@index')->name('posts.index');

Okay, now finally, we need to send a network request using the axios library.

We need to define the axios get request inside our TimelineComponent.vue file.

// TimelineComponent.vue

<template>
    <div class="col-md-8 posts">
        <p v-if="!posts.length">No posts</p>
        <div class="media" v-for="post in posts" :key="post.id">
            <img class="mr-3" />
            <div class="media-body">
                <div class="mt-3">
                    <a :href="post.user.profileLink">{{ post.user.name }}</a> | {{ post.createdDate }}
                </div>
                <p>{{ post.body }}</p>
            </div>
        </div>
    </div>
</template>
<script>
import Event from '../event.js';

export default {
    data() {
        return {
            posts: [],
            post: {}
        }
    },
    mounted() {
        axios.get('/posts').then((resp => {
            this.posts = resp.data;
        }));
        Event.$on('added_tweet', (post) => {
            this.posts.unshift(post);
        });
    }
}
</script>

Now, you can be able to see all the posts from your following user and your posts.

Laravel Vue Twitter Style App

In this Laravel and Vue Example, we have covered lots of things and concepts.

I hope it can help you with your real-time projects.

This is not the same Twitter app, but I am just trying to teach you some concepts like Events in Vue.js, Laravel Model relationships, and complex queries.

I am putting this Laravel and Vue Example project on Github so you can check it out there. 

Github Code

Steps to use Github

  1. Clone the repo and go into the folder.
  2. Type the following command: composer install
  3. Install npm dependencies: npm install
  4. Start the watch: npm run watch
  5. Create a database and configure it inside the .env file.
  6. Migrate the database: php artisan migrate
  7. Register the two users.
  8. Create some tweets using the signed-in user.
  9. Log out that user and sign in with the second user.
  10. Go to the first user’s profile and follow it.
  11. Go to the home route, and now you can see that followed user’s post.
  12. Now, create the tweets using a second user, and you can also see those tweets.
5 Comments
  1. yakov says

    Thanks.
    This website very good.
    I’m learning a lot of knowledge using this website.
    Really thanks.

  2. Kennexcorp says

    Run npm run dev and npm run watch after running npm install to automatically compile vue components.

  3. Stephen says

    Great work you doing here. Boss I wondering if you could assist me am working on a laravel project involving audio streaming but I can’t seek my audio files, if I try to the song starts again from the beginning. Please assist.

  4. almokhtar says

    what i didn’t understand is why you make a pivot table for users and followers ??? please i need your help

  5. laravel development says

    Thank you for sharing such useful information.

Leave A Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.