This tutorial is out of date and no longer maintained.
These days, various web applications are in one way or the other implementing a kind of favorite/like/recommend feature on the websites. These can be seen on sites like Medium, Facebook, Laracasts, and even here on scotch.io
and school.scotch.io
.
In this tutorial, I’ll be showing you how to implement a favorites feature using Vue.js in your Laravel application. Though we’ll be using the term favorites in this tutorial, this can be replaced with likes or recommends depending on your application.
We’ll be building a simple Posts app. This app will comprise of users and posts. Users will be able to create posts and as well mark posts as favorites. Finally, users will be able to see all the posts they marked as favorites.
The app will have a User model and a Post model, there will be an authentication system that will allow only authenticated users mark/unmark posts as favorites. We’ll make use of VueJs and Axios to make marking/un-marking posts as favorites dynamic, that is without reloading the page.
Before we start building, let’s take a quick look at what the Posts app will look like when we are done.
We’ll start by creating a new Laravel project, the name of the project will be laravel-vue-favorite
.
- laravel new laravel-vue-favorite
This will create a new Laravel 5.4 (which is the current version as of the time of this tutorial) project.
In a fresh installation of Laravel, Laravel provides some frontend frameworks and libraries with some basic setup to integrate these packages together. Among the frameworks and libraries are Bootstrap
, VueJs
and Axios
, which we will be using in this tutorial. But we still need to install these dependencies through NPM
:
- npm install
Also, we’ll make use of Laravel Mix to compile and build our CSS and JavaScript. The command above will also install all Laravel Mix dependencies.
For our Posts app, we’ll need a User
model (which comes with Laravel), a Post
model, and a Favorite
model and their respective migration files.
- php artisan make:model Post -m
- php artisan make:model Favorite -m
These will create a Post
model and a Favorite
model along with their migration files respectively. Open the posts
table migration file and update the up()
with:
/**
* Define posts table schema
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->string('title');
$table->text('body');
$table->timestamps();
});
}
The posts
table will contain an id
, user_id
(ID of the user that created the post), title
, body
, and some timestamps columns.
Next, open the favorites
table migration file and update the up()
with:
/**
* Define favorites table schema
*/
public function up()
{
Schema::create('favorites', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('post_id')->unsigned();
$table->timestamps();
});
}
The favorites
table will be a pivot table. It will contain two columns: user_id
which will hold the ID of the user that favorited a post and post_id
which will the ID of the post that was favorited.
For the users
table migration, we’ll be using the default Laravel provided.
Before we run our migrations, let’s set up our database. Add your database details to the .env
file:
DB_DATABASE=laravue
DB_USERNAME=root
DB_PASSWORD=root
Remember to update with your own database details. Now we can go on and run our migrations:
- php artisan migrate
We’ll also generate some seed data with which we can test our app with. To generate dummy data, we’ll make use of Laravel Model Factories. Laravel model factories make use of the Faker PHP library.
We’ll generate dummy data of Users and Posts. When you open the database/factories/ModelFactory.php
file, you will see that Laravel provides a User
model factory, which means we only need to create a Post
model factory. Add the snippets below to database/factories/ModelFactory.php
just after the User
model factory:
// database/factories/ModelFactory.php
$factory->define(App\Post::class, function (Faker\Generator $faker) {
// Get a random user
$user = \App\User::inRandomOrder()->first();
// generate fake data for post
return [
'user_id' => $user->id,
'title' => $faker->sentence,
'body' => $faker->text,
];
});
Let’s quickly run through the code. Remember from our posts table migration, we defined that a post must have a user ID. So, we get a random user and assign the user_id
of a post to the ID of the random user, then we use Faker to generate the title
and body
of the post.
With our model factories done, let’s move on to create our database seeder classes by running these commands:
- php artisan make:seeder UsersTableSeeder
- php artisan make:seeder PostsTableSeeder
Open database/seeds/UsersTableSeeder.php
and update the run()
with:
// database/seeds/UsersTableSeeder.php
/**
* Run the database seeds to create users.
*
* @return void
*/
public function run()
{
factory(App\User::class, 5)->create();
}
This will create 5 different users with dummy data when the seeder is run. We’ll do the same for Posts. Open database/seeds/PostsTableSeeder.php
and update the run()
with:
// database/seeds/PostsTableSeeder.php
/**
* Run the database seeds to create posts.
*
* @return void
*/
public function run()
{
factory(App\Post::class, 10)->create();
}
This will create 10 different posts with dummy data when the seeder is run.
Before we run the database seeders, let’s update the database/seeds/DatabaseSeeder.php
which is provided by default:
// database/seeds/DatabaseSeeder.php
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$this->call(UsersTableSeeder::class);
$this->call(PostsTableSeeder::class);
}
Now we can run the database seeders:
- php artisan db:seed
You should now see some dummy data in your database.
Before a user can mark a post as a favorite, the user must be logged in. So we need a kind of authentication system. Luckily for us, Laravel got our back on this. We’ll use the artisan make:auth
command to scaffold an authentication system.
- php artisan make:auth
This will create all of the necessary routes and views for authentication. We can go and register as a user, which we will use to test the functionality of the application we are building.
Let’s define the routes of our application. Open routes/web.php
and update with below:
// routes/web.php
Auth::routes();
Route::get('/', 'PostsController@index');
Route::post('favorite/{post}', 'PostsController@favoritePost');
Route::post('unfavorite/{post}', 'PostsController@unFavoritePost');
Route::get('my_favorites', 'UsersController@myFavorites')->middleware('auth');
The routes are pretty straightforward. Auth
routes that Laravel created when we ran the make:auth
command. A route to the homepage that will list all posts, two other routes for favoriting and unfavoriting posts. Lastly, a route that displays all posts that have been marked as favorites by a user. This route will be accessible to only authenticated users.
When a user registers or login, Laravel will redirect them to the /home
route by default. Since we have removed the /home
route that Laravel created when we ran make:auth
. We need to update the redirectTo
property of both app/Http/Controllers/Auth/LoginController.php
and app/Http/Controllers/Auth/RegisterController.php
to:
protected $redirectTo = '/';
Since a user can mark many posts as favorites and a post can be marked as favorites by many users, the relationship between users and favorite posts will be a many-to-many relationships. To define this relationship, open the User
model and add a favorites()
:
// app/User.php
/**
* Get all of favorite posts for the user.
*/
public function favorites()
{
return $this->belongsToMany(Post::class, 'favorites', 'user_id', 'post_id')->withTimeStamps();
}
Laravel will assume the pivot table is post_user
but since we gave the pivot table a different name (favorites), we have to pass in some additional arguments. The second argument is the name of the pivot table (favorites). The third argument is the foreign key name (user_id) of the model on which you are defining the relationship (User), while the fourth argument is the foreign key name (post_id) of the model that you are joining to (Post).
Noticed we chained withTimeStamps()
to the belongsToMany()
. This will allow the timestamps (create_at
and updated_at
) columns on the pivot table to be affected whenever a row is inserted or updated.
Let’s create a new controller that will handle displaying posts, marking a post as favorite and unfavorite a post.
- php artisan make:controller PostsController
Open the newly created app/Http/Controllers/PostsController.php
and add the snippet below to it:
// app/Http/Controllers/PostsController.php
// remember to use the Post model
use App\Post;
/**
* Display a paginated list of posts.
*
* @return Response
*/
public function index()
{
$posts = Post::paginate(5);
return view('posts.index', compact('posts'));
}
The index()
will get all posts and paginate them into 5 per page. Then render a view file (that we are yet to create) along with the posts, which will do the actual displaying of the posts.
Remember when we ran make:auth
command that Laravel created some views. We’ll be using the resources/views/layouts/app.blade.php
that was created and make a few additions to it. Add the code below just after the <title>
:
// resources/views/layouts/app.blade.php
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
Then add this just before the Logout
list item:
// resources/views/layouts/app.blade.php
<li>
<a href="{{ url('my_favorites') }}">My Favorites</a>
</li>
Now let’s create the index
view. Create a new posts
folder within views
directory and create a new index.blade.php
file within the newly created folder. Paste the code below into resources/views/posts/index.blade.php
:
// resources/views/posts/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="page-header">
<h3>All Posts</h3>
</div>
@forelse ($posts as $post)
<div class="panel panel-default">
<div class="panel-heading">
{{ $post->title }}
</div>
<div class="panel-body">
{{ $post->body }}
</div>
</div>
@empty
<p>No post created.</p>
@endforelse
{{ $posts->links() }}
</div>
</div>
</div>
@endsection
Pretty simple markup that displays a paginated list of posts. Open the homepage in your browser, you should see a page like the image below:
Next, let’s go back to PostsController
and add the methods that will handle marking a post as favorite and unfavorite a post. Add the code below to PostsController
:
// app/Http/Controllers/PostsController.php
// remember to use
use Illuminate\Support\Facades\Auth;
/**
* Favorite a particular post
*
* @param Post $post
* @return Response
*/
public function favoritePost(Post $post)
{
Auth::user()->favorites()->attach($post->id);
return back();
}
/**
* Unfavorite a particular post
*
* @param Post $post
* @return Response
*/
public function unFavoritePost(Post $post)
{
Auth::user()->favorites()->detach($post->id);
return back();
}
The favoritePost()
takes a post as an argument. Using the favorites
relationship we defined above, we attach the post ID to the ID of the authenticated user then insert it into the favorites
table. Finally, return back to the previous page.
The unFavoritePost()
is the reverse of favoritePost()
which simply removes the ID of the authenticated user along with the post ID from the favorites
table.
It’s time to integrate Vue
into our application. We’ll make the favorite button/icon a Vue component. Making it a Vue component will allow for reuse in multiple places with our application.
Once the favorite button/icon is clicked, we’ll mark the post as favorite or unfavorite without reloading the page, that is through AJAX
. For this, we’ll make use of Axios
which is a Promise-based HTTP client for the browser and node.js.
Create a new Favorite.vue
file within the resources/assets/js/components
folder and paste the code below into it:
// resources/assets/js/components/Favorite.vue
<template>
<span>
<a href="#" v-if="isFavorited" @click.prevent="unFavorite(post)">
<i class="fa fa-heart"></i>
</a>
<a href="#" v-else @click.prevent="favorite(post)">
<i class="fa fa-heart-o"></i>
</a>
</span>
</template>
<script>
export default {
props: ['post', 'favorited'],
data: function() {
return {
isFavorited: '',
}
},
mounted() {
this.isFavorited = this.isFavorite ? true : false;
},
computed: {
isFavorite() {
return this.favorited;
},
},
methods: {
favorite(post) {
axios.post('/favorite/'+post)
.then(response => this.isFavorited = true)
.catch(response => console.log(response.data));
},
unFavorite(post) {
axios.post('/unfavorite/'+post)
.then(response => this.isFavorited = false)
.catch(response => console.log(response.data));
}
}
}
</script>
The Favorite
component has two sections: template
and script
. In the template
section, we defined the markup that will be rendered when the component is used. We are using conditional rendering to show the appropriate button/icon. That is, if isFavorited
is true
, the button/icon should be marked as favorite and on click of the button/icon should trigger unFavorite()
. Else the button/icon should be marked as unfavorite and on click of the button/icon should trigger favorite()
.
Moving on to the script
section, we defined some properties for the component; post
(will be the ID of the post) and favorited
(will either be true
or false
depending on if the post has been favorited by the authenticated user). We also defined an isFavorited
data which will be used for the conditional rendering from above.
When the component is mounted, we set the value of isFavorited
to the value of isFavorite
computed property. That is, the isFavorite
computed property will return the value of favorited
prop which will either be true
or false
. We use a computed property so as to reactively get the value of the favorited
prop instead of using the value of favorited
prop that was passed directly.
Lastly, we defined two methods: favorite()
and unFavorite()
which both accepts the post
prop as arguments. Using Axios
, we make a POST
request to the routes we defined earlier. For the favorite()
, once the POST
request is successful, we set isFavorited
to true
and otherwise console log the errors. The same is applicable to the unFavorite()
just that we set isFavorited
to false
.
Before we can start to use the Favorite
component, we need to first register it on our Vue
root instance. Open resources/assets/js/app.js
, you will see that Laravel registers an Example
component. We are going to replace that with the Favorite
component:
// resources/assets/js/app.js
Vue.component('favorite', require('./components/Favorite.vue'));
Now we can compile and build our styles and scripts:
- npm run dev
We can now use the Favorite
component. Open resources/views/posts/index.blade.php
and add the snippets below to it after the closing div
of the panel-body
:
// resources/views/posts/index.blade.php
@if (Auth::check())
<div class="panel-footer">
<favorite
:post={{ $post->id }}
:favorited={{ $post->favorited() ? 'true' : 'false' }}
></favorite>
</div>
@endif
The favorite button/icon will only be displayed to authenticated users. As you can see, we passed to the Favorite
component the props that we defined when we created the component. To know if a post is has been favorited by the authenticated user, we call a favorited()
(which we are yet to create) on the post.
To create favorited()
, open app/Post.php
and add the code below to it:
// app/Post.php
// remember to use
use App\Favorite;
use Illuminate\Support\Facades\Auth;
/**
* Determine whether a post has been marked as favorite by a user.
*
* @return boolean
*/
public function favorited()
{
return (bool) Favorite::where('user_id', Auth::id())
->where('post_id', $this->id)
->first();
}
This gets and casts to boolean
the first result where the user_id
is equal to that of the authenticated user and where the post_id
is equal to the ID of the post the method is called on.
If you visit the homepage of the application in the browser and log in, you should get something similar to:
As you can see I have marked some posts as favorites.
Won’t it be nice for users to be able to see all the posts they have marked as favorites? Sure it will be. Remember we defined a my_favorites
route that will be accessible to only authenticated users, this is where users will be able to see the posts they’ve marked as favorites.
Let’s create a UsersController
that will handle this route.
- php artisan make:controller UsersController
Open app/Http/Controllers/UsersController.php
and add the code below to it:
// app/Http/Controllers/UsersController.php
// remember to use
use Illuminate\Support\Facades\Auth;
/**
* Get all favorite posts by user
*
* @return Response
*/
public function myFavorites()
{
$myFavorites = Auth::user()->favorites;
return view('users.my_favorites', compact('myFavorites'));
}
The myFavorites()
uses the favorites
relationship we defined earlier, get all the posts that the authenticated user has marked as favorites. Then return a view along with favorites posts.
Now let’s create the view. Create a new users
folder within the resources/views
directory and within the users
folder, create a new file my_favorites.blade.php
and paste the code below to it:
// resources/views/users/my_favorites.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="page-header">
<h3>My Favorites</h3>
</div>
@forelse ($myFavorites as $myFavorite)
<div class="panel panel-default">
<div class="panel-heading">
{{ $myFavorite->title }}
</div>
<div class="panel-body">
{{ $myFavorite->body }}
</div>
@if (Auth::check())
<div class="panel-footer">
<favorite
:post={{ $myFavorite->id }}
:favorited={{ $myFavorite->favorited() ? 'true' : 'false' }}
></favorite>
</div>
@endif
</div>
@empty
<p>You have no favorite posts.</p>
@endforelse
</div>
</div>
</div>
@endsection
The markup is similar to that of index.blade.php
. As you can see, we also used the Favorite
component. When viewed in the browser, you should see something similar to:
That’s it, we are done building the Post app and have seen how to allow only authenticated users mark/unmark posts as favorites without reloading the page using Vue.js and Axios.
I hope you find this tutorial useful. If you encounter any problems following this tutorial or have questions/suggestions, kindly drop them in the comment section below.
Also, I have made a vue-favorite component based on this tutorial which can be installed through npm
.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!