
Beautiful Follow/Followers System with Authentication, Laravel, Livewire, and Bootstrap 5
🌟 Why Build a Follow System?
Social engagement is the heartbeat of modern web apps. Whether you’re creating a community platform, a blog with user interactions, or a full-fledged social network, a follow system keeps users connected. In this guide, you’ll learn how to build a real-time follow/followers system using Laravel, Livewire, and Bootstrap 5—with zero JavaScript hassle and sleek UI components. Let’s dive in!
🚀 Technologies We’ll Use
- Laravel 10.x: Robust PHP framework for backend logic.
- Livewire: Dynamic UI interactions without JavaScript.
- Bootstrap 5: Modern, responsive styling.
- Laravel Breeze: Effortless authentication scaffolding.
Step-by-Step Guide
1. Installing Laravel
Let’s begin by creating a new Laravel project. If you haven’t installed Composer yet, please refer to the official Laravel installation guide.
composer create-project --prefer-dist laravel/laravel follow-system cd follow-system
2. Setting Up Authentication with Laravel Breeze
Laravel Breeze offers a simple and lightweight authentication system out of the box. Let’s set it up:
composer require laravel/breeze --dev php artisan breeze:install npm install && npm run dev php artisan migrate
This will scaffold basic auth features such as login, registration, and password resets.
3. Installing Livewire
Livewire allows us to build dynamic interfaces without leaving the comfort of Laravel.
composer require livewire/livewire
Now, include Livewire’s styles and scripts in your main layout file (resources/views/layouts/app.blade.php):
In the <head> section:
@livewireStyles
Just before the closing </body> tag:
@livewireScripts
4. Creating the Follow/Followers System
Step 1: Migration for the follows Table
Let’s create a migration to manage follow relationships between users:
php artisan make:migration create_follows_table
In the generated migration file:
Schema::create('follows', function (Blueprint $table) { $table->id(); $table->foreignId('follower_id')->constrained('users')->onDelete('cascade'); $table->foreignId('following_id')->constrained('users')->onDelete('cascade'); $table->timestamps(); $table->softDeletes(); // Enables soft deletes });
Run the migration:
php artisan migrate
Step 2: Creating the Follow Model
Next, let’s create a model to represent follow relationships:
php artisan make:model Follow
And define the model as follows:
namespace App\Models; use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Database\Eloquent\SoftDeletes; class Follow extends Pivot { use SoftDeletes; protected $table = 'follows'; protected $fillable = ['follower_id', 'following_id']; protected $dates = ['deleted_at']; }
5. Implementing the Followable Trait
Step 1: Creating the Trait - app/Traits/Followable.php
Define a trait with the necessary follow/unfollow logic:
namespace App\Traits; use App\Models\Follow; use App\Models\User; use Illuminate\Support\Facades\Auth; trait Followable { public function isFollowing(User $user): bool { return $this->followings() ->where('following_id', $user->id) ->whereNull('follows.deleted_at') ->exists(); } public function follow(User $user): void { $existingFollow = Follow::where('following_id', $user->id) ->where('follower_id', Auth::id()) ->withTrashed() ->first(); if ($existingFollow) { $existingFollow->deleted_at = null; $existingFollow->save(); } else { $this->followings()->attach($user->id); } } public function unfollow(User $user): void { if ($this->isFollowing($user)) { $this->followings()->detach($user->id); } } public function followings() { return $this->belongsToMany(User::class, 'follows', 'follower_id', 'following_id') ->using(Follow::class) ->wherePivotNull('deleted_at') ->withTimestamps(); } public function followers() { return $this->belongsToMany(User::class, 'follows', 'following_id', 'follower_id') ->using(Follow::class) ->wherePivotNull('deleted_at') ->withTimestamps(); } public function followerCount() { return $this->followers()->count(); } public function followingCount() { return $this->followings()->count(); } }
Step 2: Using the Trait in the User Model
Now let’s include this trait in the User model:
namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use App\Traits\Followable; class User extends Authenticatable { use Notifiable, Followable; protected $fillable = ['name', 'email', 'password']; protected $hidden = ['password', 'remember_token']; protected $casts = ['email_verified_at' => 'datetime']; }
6. Creating the Livewire Component for Follow Button
Run the following command:
php artisan make:livewire FollowButton
In the FollowButton.php component:
namespace App\Livewire; use Livewire\Component; class FollowButton extends Component { public $user; public $isFollowing = false; protected $listeners = ['followChanged' => 'updateFollowStatus']; public function mount($user) { $this->user = $user; $this->isFollowing = auth()->check() && auth()->user()->isFollowing($this->user); } public function follow() { if (!auth()->check()) { return redirect()->route('login'); } auth()->user()->follow($this->user); $this->isFollowing = true; $this->dispatch('followChanged'); } public function unfollow() { if (auth()->check()) { auth()->user()->unfollow($this->user); $this->isFollowing = false; $this->dispatch('followChanged'); } } public function updateFollowStatus() { $this->isFollowing = auth()->check() && auth()->user()->isFollowing($this->user); } public function render() { return view('livewire.follow-button'); } }
In the corresponding Blade view (follow-button.blade.php):
<div> @auth @if($isFollowing) <button wire:click="unfollow" class="btn btn-primary w-100 d-md-inline-block"> <i class="bi bi-person-x"></i> Unfollow </button> @else <button wire:click="follow" class="btn btn-info w-100 d-md-inline-block"> <i class="bi bi-person-plus"></i> Follow </button> @endif @endauth @guest <a href="{{ route('login') }}" class="btn btn-info w-100 d-md-inline-block">Follow</a> @endguest </div>
7. Livewire Component: User Follow Stats
Create the component:
php artisan make:livewire UserFollowStats
Update the UserFollowStats.php:
namespace App\Livewire; use App\Models\User; use Livewire\Component; class UserFollowStats extends Component { public $user; protected $listeners = ['followChanged' => '$refresh']; public function mount(User $user) { $this->user = $user; } public function render() { return view('livewire.user-follow-stats'); } }
In the view (user-follow-stats.blade.php):
<div class="d-flex align-items-center gap-4 py-2"> <div class="d-flex align-items-center gap-2"> <i class="bi bi-people-fill fs-5 text-primary"></i> <div class="text-start"> <div class="fw-bold fs-6 mb-0">{{ $user->followerCount() }}</div> <small class="text-muted">Followers</small> </div> </div> <div class="d-flex align-items-center gap-2"> <i class="bi bi-person-plus-fill fs-5 text-success"></i> <div class="text-start"> <div class="fw-bold fs-6 mb-0">{{ $user->followingCount() }}</div> <small class="text-muted">Following</small> </div> </div> </div>
9. User List Page
Add this route:
Route::get('/users', function () { $users = App\Models\User::all(); return view('users.index', compact('users')); })->name('users.index');
app.blade.php (for layout) - resources/views /layouts/app.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{ config('app.name', 'Laravel') }}</title> <!-- Bootstrap 5 CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Livewire Styles --> @livewireStyles </head> <body> <div id="app"> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <!-- Your navigation code here --> <a class="navbar-brand" href="#">AppName</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link active" aria-current="page" href="#">Home</a> </li> <!-- Add other nav items as necessary --> </ul> </div> </nav> <main class="container mt-4"> @yield('content') </main> </div> <!-- Bootstrap 5 JavaScript and Popper.js --> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.min.js"></script> <!-- Livewire Scripts --> @livewireScripts </body> </html>
Create users/index.blade.php:
@extends('layouts.app') @section('content') <div class="container"> <h2>Users List</h2> <div class="list-group"> @foreach($users as $user) <div class="list-group-item d-flex justify-content-between"> <span>{{ $user->name }}</span> <div class="mb-2"> <livewire:user-follow-stats :user="$user" /> </div> <div class="d-block d-md-none"> <livewire:follow-button :user="$user" /> </div> </div> @endforeach </div> </div> @endsection
🎨 Why This Works
- Real-Time Updates: Livewire automatically refreshes UI components when follows/unfollows happen.
- Clean Code: The Followable trait centralizes follow logic, making it reusable.
- Responsive Design: Bootstrap 5 ensures the UI looks great on all devices.
- Security: Laravel’s authentication and middleware protect user actions.
🚀 Next Steps
- Add notifications when someone follows a user.
- Implement a “Following Feed” to display posts from followed users.
- Style the user list with avatars and bios using Bootstrap 5 cards.
đź’ˇ Final Thoughts
You’ve just built a scalable, real-time follow system with Laravel and Livewire—perfect for boosting user engagement. The combination of Laravel’s elegance, Livewire’s reactivity, and Bootstrap’s styling makes this a powerhouse setup.
Go ahead and launch your next social app! ✨
Happy coding!
0 Comments