DEV Community

Simi Oluwatomi
Simi Oluwatomi

Posted on

Let's build a super simple referral system with Laravel

Sometime ago, I was asked to build a referral system as part of the feature set of a Laravel web application. Referral systems were not new to me but it was my first time building one. Referral systems are used to encourage users of an application to invite other users and get rewarded when people register for that application using their referral link.

I really wanted to keep things super simple but most of what I read online about referral systems were either too complicated or complex for my use case. After some tinkering around, I came up with something so simple I’m surprised it works. Together, we are going to build a referral system and examine some decisions and trade-offs we will have to make along the way.

To start, create a new Laravel project. I’ll name mine simple-referral. Allow composer or the laravel installer do its magic and you should have a barebones Laravel application at your fingertips. Configure the application database, run php artisan make:auth and let’s get cracking.

A referral flow looks something like this

  1. A user clicks a link that takes them to the application register page.
  2. When they register, their registration should be linked to the user whose referral link they used in registering.
  3. The user whose referral link was used to register should get notified that someone registered using their referral link.
  4. The new user should have his or her own referral link.

We could use this scenario to model our eloquent relationships which would look like the following

  • A user can be referred by another user.
  • A user can refer many users.

In essence, we have a One-To-Many relationship. Open the create_users_table migration and make the change below.



$table->unsignedBigInteger('referrer_id');


Enter fullscreen mode Exit fullscreen mode

But wait! Not all users will have a referrer, so let’s make the referrer column nullable. While at it, let's also add a foreign_key constraint to the users table. create_users_table migration should now look like this.



    $table->bigIncrements('id');
    $table->unsignedBigInteger('referrer_id')->nullable();
    $table->foreign('referrer_id')->references('id')->on('users');
    $table->string('name');    
    $table->string('email')->unique();            
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();   


Enter fullscreen mode Exit fullscreen mode

Migrate the database and make the changes below in User.php. In case you didn’t know, you just set up a self-referencing table.




/**
 * The attributes that are mass assignable.
 *
 * @var array
 */
protected $fillable = [
    'referrer_id', 'name', 'email', 'password',
];

/**
 * A user has a referrer.
 *
 * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
 */
public function referrer()
{
    return $this->belongsTo(User::class, 'referrer_id', 'id');
}

/**
 * A user has many referrals.
 *
 * @return \Illuminate\Database\Eloquent\Relations\HasMany
 */
public function referrals()
{
    return $this->hasMany(User::class, 'referrer_id', 'id');
}


Enter fullscreen mode Exit fullscreen mode

Now let's think about referral links and how we want our referral links to look like.

For the application I was building, one of the business requirements was that users have a referral link that could be easily remembered. Over time, I have learned that URLs that can be remembered easily are always short. There are exceptions to this but as I said, they are exceptions. After some back and forth, we agreed that referral links should look like this https://simple-referral/register?ref=username. This would require users to have a unique, unchanging username.

To satisfy this business requirement, amend create_users_table migration like so



//...
$table->string('username')->unique();


Enter fullscreen mode Exit fullscreen mode

Then open User.php and make the following changes



protected $fillable = [
    // ...
    'username',
];

/**
 * The accessors to append to the model's array form.
 *
 * @var array
 */
protected $appends = ['referral_link'];

/**
 * Get the user's referral link.
 *
 * @return string
 */
public function getReferralLinkAttribute()
{
    return $this->referral_link = route('register', ['ref' => $this->username]);
}



Enter fullscreen mode Exit fullscreen mode

Rather than storing the user's referral link in the database, we are using an accessor to compute it. Then we append the attribute to the User model's array or JSON form. To read more about accessors and eloquent serialization, click here and here.

Your requirements for a referral link might be different and here is another option you can consider https://simple-referral/register?ref=referral_token. In this case, edit create_users_table migration like so



 //...
 $table->string('referral_token')->unique();


Enter fullscreen mode Exit fullscreen mode

Then open User.php and

  • Add referral_token to the fillable fields.
  • Append referral_link to the model's array or JSON form.
  • Compute the referral link attribute using the user's referral_token instead of the username.

This method would allow users to have editable usernames but you would have to think of how to generate unique referral tokens for your users. I'll leave you to figure that out.

The User model has been prepped, and the database structure supports our referral system; let's move on to our Controller!

Open App\Http\Controllers\Auth\RegisterController.php. The Controller uses Illuminate\Foundation\Auth\RegistersUsers trait to perform most of it's functions meaning we can override the methods of the trait in the Controller.



/**
 * Show the application registration form.
 *
 * @return \Illuminate\Http\Response
 */
public function showRegistrationForm(Request $request)
{
    if ($request->has('ref')) {
        session(['referrer' => $request->query('ref')]);
    }

    return view('auth.register');
}

/**
 * Get a validator for an incoming registration request.
 *
 * @param  array  $data
 * @return \Illuminate\Contracts\Validation\Validator
 */
protected function validator(array $data)
{
    return Validator::make($data, [
        'name' => ['required', 'string', 'max:255'],
        'username' => ['required', 'string', 'unique:users', 'alpha_dash', 'min:3', 'max:30'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'password' => ['required', 'string', 'min:8', 'confirmed'],
    ]);
}

/**
 * Create a new user instance after a valid registration.
 *
 * @param array $data
 *
 * @return \App\Models\User
 */
protected function create(array $data)
{
    $referrer = User::whereUsername(session()->pull('referrer'))->first();

    return User::create([
        'name'        => $data['name'],
        'username'    => $data['username'],
        'email'       => $data['email'],
        'referrer_id' => $referrer ? $referrer->id : null,
        'password'    => Hash::make($data['password']),
    ]);
}


Enter fullscreen mode Exit fullscreen mode

It looks like a lot but we are only doing three things

  • Set a session variable named 'referrer' if the register route contains a ref query parameter.
  • Form field validation before creating the user.
  • Retrieve the referrer from the session when creating a user then set its id as the referrer_id of the user being created.

To complete our simple referral system, users should get a notification when new users register using their referral link. To be as flexible and powerful as possible, I'll be using a Notification instead of a Mailable. Run php artisan make:notification nameOfTheNotification to generate a Notification. I'll be naming mine ReferralBonus. I'll leave you to decide the channel(s) through which the notification will be sent.

Open RegisterController.php and override the registered method like so



/**
 * The user has been registered.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  mixed  $user
 * @return mixed
 */
protected function registered(Request $request, $user)
{
    if ($user->referrer !== null) {
        Notification::send($user->referrer, new ReferrerBonus($user));
    }

    return redirect($this->redirectPath());
}


Enter fullscreen mode Exit fullscreen mode

Here we send a notification to the newly registered user if it has a referrer. We then redirect to the redirect path defined on the RegisterController. Note: Do not forget to import all necessary classes.

Now let's deal with out front-end. Open register.blade.php and add a form input field for username.



//...
<div class="form-group row">
    <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Username') }}</label>

    <div class="col-md-6">
        <input id="username" type="text" class="form-control @error('name') is-invalid @enderror" name="username" value="{{ old('username') }}" required autocomplete="username">

        @error('username')
            <span class="invalid-feedback" role="alert">
                <strong>{{ $message }}</strong>
            </span>
        @enderror
    </div>
</div>


Enter fullscreen mode Exit fullscreen mode

Now, let's modify home.blade.php to display some user information



<div class="card-body">
    @if (session('status'))
        <div class="alert alert-success" role="alert">
            {{ session('status') }}
        </div>
    @endif

    You are logged in!

    <ul class="list-group mt-3">
        <li class="list-group-item">Username: {{ Auth::user()->username }}</li>
        <li class="list-group-item">Email: {{ Auth::user()->email }}</li>
        <li class="list-group-item">Referral link: {{ Auth::user()->referral_link }}</li>
        <li class="list-group-item">Referrer: {{ Auth::user()->referrer->name ?? 'Not Specified' }}</li>
        <li class="list-group-item">Refferal count: {{ count(Auth::user()->referrals)  ?? '0' }}</li>
    </ul>
</div>


Enter fullscreen mode Exit fullscreen mode

Our simple referral system is now complete. In case you are having challenges following along, you can browse through the repository I created for this tutorial on Github

GitHub logo simioluwatomi / simple-referral

A super simple referral system built with Laravel

Image of User Details

This project demos a super simple referral system built with Laravel. To read the tutorial I wrote for this, click here .

To run the project

  • Clone it to your computer.
  • Run composer install to install application dependencies.
  • Copy .env.example to .env.
  • Run php artisan key:generate to generate an application key.
  • Add database credentials to .env.
  • Run php artisan migrate to migrate the database.
  • To run the tests included, add database credentials for the testing database connection defined in config/database.php to .env.

Top comments (37)

Collapse
 
cyprian_dev profile image
Cyprian

So for those who would be using Laravel 8 with jetstream, this stack uses Fortify for authentication and i quote from laravel documentation 'Fortify does not provide its own user interface, it is meant to be paired with your own user interface which makes requests to the routes it registers'. if that is hard to understand then follow the laravel.com/docs/8.x/fortify#intro... for clarity or drop a comment below.

So let's get to the chase. The most important thing in this system is the session that Mr Simi set to cache a session variable named 'referrer' if the register route contains a ref query parameter. now go to App\Providers\FortiftyServiceProvider in the boot() method and paste the code below

Fortify::registerView(function(Request $request) {
if ($request->has('ref')) {
session(['referrer' => $request->query('ref')]);
}
return view('auth.register');
});
Now the code is doing the same as that in the registerController.

Secondly, after caching the ref=user and getting the user's id, you would need to pass in this data in the create() method. You will find this file in the App\Actions\Foritfy\CreateNewUser class. Like what i did, i replaced the codes in this file with that of the RegisterController and then pass in the

    $referrer = User::whereUsername(session()->pull('referrer'))->first();
Enter fullscreen mode Exit fullscreen mode

This is the code that pulls the referrer's credentials and parses it in to create a new user. Every other thing stays the same from the User class and from the migration class file too.

It worked for me because i was using laravel 8 for a referral system project. Like i said, drop a comment if you have any challanges below...Cheers

Collapse
 
reubendickson profile image
ReubenDickson

Thanks for this guys. I'm new to Laravel. The project I'm working on includes referrals system and I'm using Jetstream. Would you like to share what you've done please. Thanks

Collapse
 
simioluwatomi profile image
Simi Oluwatomi

Thanks! I should update this tutorial to reflect the changes in Laravel since it was written but I'm currently swamped with work.

Thanks once again

Collapse
 
cyprian_dev profile image
Cyprian

Okay boss.

Collapse
 
sarjid profile image
Sarjid Islam Habil • Edited

Thanks it's work fine in laravel 8..ihave make refferel system just 40 minutes.. Thanks for Your Help..

Collapse
 
dotcorps profile image
dotcorps

where to write Notification code

Collapse
 
simioluwatomi profile image
Simi Oluwatomi

You have to create a notification. Kindly refer to the notification section of the Laravel docs to get started with that

laravel.com/docs/8.x/notifications

Thread Thread
 
jhims profile image
Willy

How can I get in touch with you? a means of contact

Thread Thread
 
simioluwatomi profile image
Simi Oluwatomi

You can send me a tweet on X (formerly Twitter)

Thread Thread
 
jhims profile image
Willy

unable to send message by x

Thread Thread
 
simioluwatomi profile image
Simi Oluwatomi

Send me a mail at deifilius1@gmail.com

Collapse
 
clementn profile image
Clement

Very nice and very useful. Thank you.
I use it with a small change.

I moved the code to write the referrer into the session

    if ($request->has('ref')) {
        session(['referrer' => $request->query('ref')]);
    }
Enter fullscreen mode Exit fullscreen mode

from app/Http/Controllers/Auth/RegisterController.php to a custom made middleware app/Http/Middleware/CheckReferrer.php

That way, the ?ref=referrer can be used on any link on the website and not only on register?ref=referrer

P.S. The custom middleware can be created using the artisan command:
php artisan make:middleware CheckAge
then it has to be registered within app/Http/Kernel.php

    protected $middlewareGroups = [
        'web' => [
...........
            \App\Http\Middleware\CheckReferrer::class,
        ],
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jcarlosr profile image
Juan Ramos

Nice suggestion Clement. Although I think the session won't last much and it can be replaced if the user visits a page with a new ref parameter.
I need to keep the referrer value for at least 7 days and set the value only if it's not already there. For this particular case I think I will be using Cookies.

Collapse
 
simioluwatomi profile image
Simi Oluwatomi

This is great. I really didn't think about this use case as mine was only focused on registration

Collapse
 
legaciespanda profile image
Ernest Obot • Edited

Observation One

$referrer = User::whereUsername(session()->pull('referrer'))->first(); //Giving issues

should be
$session_data = session()->pull('referrer');
$referrer = User::whereUsername($session_data )->first();
//Fixed

I observed that laravel was having difficulty retrieving the referrerfrom the session and querying the database at the same time which was giving error when getting the id. The solution was to first get the referrer value from the session and pass it to the eloquent.

Observation 2
'referrer_id' => $referrer ? $referrer->id : null,

This is because $referrer does not return a boolean value instead a user object. So using it this way will return an error and of course logical error.

What I did in my case, was to check that referrer value from session was not null.

Should be
'referrer_id' => $session != null ? $referrer->id : null,

or
'referrer_id' => !is_null($session) ? $referrer->id : null,

Collapse
 
devsahm profile image
Fagbenro Damilare Samuel

You just saved me today. I almost wasted the whole day looking for solutions online. The logic is so simple and readable. It worked perfectly.

Collapse
 
simioluwatomi profile image
Simi Oluwatomi

I used to overthink things like this and think of very complex solutions that cover a lot of edge cases. But then I realized that if the solution to something is becoming too complex, maybe, just maybe I'm overthinking it. That has really helped me a lot

Collapse
 
christopherokonkwo profile image
Christopher C. Okonkwo

You really made it simple. Of all its clean without external packages. Kudos Bro

Collapse
 
simioluwatomi profile image
Simi Oluwatomi

thank you. that was the aim from the get go

Collapse
 
developia profile image
Opiaaustin

works fine for me just have an issue with, after using a referral link to register i get the error cant find call referralbonus, and i have imported the class, any ideas?

Collapse
 
sirg97 profile image
SirG

Thanks for this!

Collapse
 
hendisantika profile image
Hendi Santika

Very good article. Simple & good explanation. You just saved me today. The logic is so simple and readable. It worked perfectly.

Collapse
 
isaactobiloba profile image
Tobi

Everyone seems to understand the whereUsername() method on the User model. I can't find the explanation anywhere so I'm guessing it's a custom method?

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
cyprian_dev profile image
Cyprian

the session may not be set properly