AppDividend
Latest Code Tutorials

Laravel and Vue Example | Build a twitter type web application

2,669

Get real time updates directly on you device, subscribe now.

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. We simply 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 currently signed in user follow any other registered user then he can also see the following user’s tweet in his timeline. The very basic app, but the very powerful web app to understand laravel and vue.js fundamental concepts.

Laravel and Vue Example

Install Laravel 5.6 and its NPM dependencies.

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 in 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 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. So create a controller by the following command.

php artisan make:controller PostController

Step 4: Define the relationships.

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

// User.php

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

Also, the Post belongs to User. So we can define the inverse relationship inside 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 install 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 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 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 current logged in user’s id inside user_id column on every saved post.

Also, we need to use 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 setup 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 frontend, 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. 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 by this component, we add the new data to the posts array and iterate the component to display the tweet and user as well.

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 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. We create a link based on the user’s name. Generally, we use the username for this, so that we can get unique URL, but for this demo, I am using the name for the unique URL.

Also, we need to add the unique property inside RegisterController.php file, so that we can 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 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 user’s name key. So we need to define the routing key inside User.php file.

// User.php

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

Create a view file called user.blade.php inside 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 a 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 a data that is 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 same do the thing here, the profile url is based on the name, so we can append one new data called profileUrl and then append to it inside User.php model.

Create an accessor for a profileLink inside 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 Followers table.

Type the following command to generate model 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);
}

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 him or her.

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

I am writing total three functions inside 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, then you can not see any of the follow or unfollow link.

If you sign in with the 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 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 click the follow button, you can see that there is unfollow button.

Also, we can see that the followers table has now 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 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 successfully completed all the task.

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 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 is 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 user can see his post as well as 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 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 post from your following user as well as your posts.

Laravel Vue Twitter Style App

 

Finally, our Laravel and Vue Example | Build a twitter type web application tutorial is over.

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 same twitter app, but I am just trying to teach you some of the 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 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. Logout 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 second user and you can also see that tweets.
1 Comment
  1. yakov says

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

Leave A Reply

Your email address will not be published.

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