Development of Membership System

The full development journey of integratable POS system-agnostic Membership System using Laravel 8 and ReactJS.

#laravel , #reactjs , #bootstrap

Published on: 07 Jan 2021 21:14pm Updated on: 09 Jan 2021 14:39pm

A friend of mine ask: "Is there a Membership System that can be integrated with any POS System?". I've thought: What if I could build one?

So I'm going to start the development of this system. Here's some basic features I will be developing first:

  • Membership data collection
  • Member's purchase history

Note

This post is intended as a demo of development / coding using Laravel 8. Bunch of technical terms are in used.

1 - Technical Specification

The core application functionality will be developed on Laravel (Version 8 released on 8th September 2020), along with ReactJS for the frontend user interface (UI).

Other specifications include:

  • PHP 7.4
  • MySQL Database 5.7
  • Apache 2.4
  • Linux OS (Ubuntu 20.04)
  • NodeJS

2 - Setup New Project

Let's first create a database named "membership". Login to mysql and enter password.

    mysql -u root -p
    Enter password: 

Note

I'm using root as my database username.

Once entered MySQL service, run the following command to create the database.

    mysql> create database membership;

The following message will appear.

    Query OK, 1 row is affected (0.01 sec)

Exit the mysql service and back to shell.

Create new Laravel project with Composer

Let's use Composer to create new Laravel project.

Run the following command to automatically create a "membership" directory, and install the necessary dependencies.

    composer create-project laravel/laravel --prefer-dist membership

The basic Laravel installation only includes basic backend setup. Since we'll be using ReactJS, let's install laravel/ui package that has the ReactJS boilerplate included.

    composer require laravel/ui

Note

laravel/ui package comes with few boilerplate of famous frontend frameworks: ReactJS, VueJS, Bootstrap and TailwindCSS.

Let's generate the ReactJS boilerplate.

    php artisan ui react --auth
    npm install

Note

laravel/ui comes with authentication module as well. The auth flag is used to generate the authentication module.

Explanation:

  1. npm is Node Package Manager, which runs on the NodeJS engine.
  2. npm install will run the installation of the required node packages for the frontend.

Configuration

Installation completed! Let's configure our project in the .env file (duplicate from .env.example).

    // .env
    APP_NAME="Membership System"
    //...
    DB_DATABASE=membership
    DB_USERNAME={your-db-username}
    DB_PASSWORD={your-db-password}

Explanation:

  1. The .env file is used to store the environment-specific configuration.
  2. It contains the sensitive information includes the database username and password for the application.

Note

Never commit the .env file into the Git, as it stores the sensitive information that should not be shared with others.

We're done setting up the project, ready to go next!


3 - Member Data Model

Let's define our first Model – Member. We assume each member to have these information:

  • Name
  • Email
  • Contact Number

We'll need to create a DB table "members" to store these information. Let's use Artisan command to generate the database migration script.

    php artisan make:migration create_members_table --table=members

Explanation:

  1. Using the flag --table=members in the command to directly include table name into the Schema up() and down() function.
  2. The command will generate a new file in database/migrations/xxxx_create_members_table, where xxxx is the timestamp of when it's being generated.

Note

Laravel provides Artisan command and it's pretty neat and convenient for developer to generate the Laravel functionality.

Let's start writing our first schema.

    /* database/migrations/xxxx_create_members_table.php */
    public function up()
    {
        Schema::create('members', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('name');
            $table->string('contact_number')->nullable();
            $table->string('email')->nullable();
            $table->dateTime('joined_date');
            $table->dateTime('expiry_date');
        });
    }

Explanation:

  1. The up() function will be called when we execute the migration. It's defined to create a new "members" DB table, with the table column defined within the function.
  2. Note that we've used id(), timestamps(), string(), dateTime() to define each column type. Refer to full documentation on Laravel for all available column types.

The down() method should be automatically defined when we generate the migration script with flag --table=members in the Artisan command above. It's used to drop the table when we rollback.

Note that I've also added joined_date and expiry_date column to capture the date and time of when the member joined, and when the membership is going to be expired.

We've done defining our first database migration script. Let's run the migration:

    php artisan migrate
    Migrating: xxxx_create_members_table
    Migrated: xxxx_create_members_table (123.45ms)

The table should be created in our "membership" database that we've created earlier.

Quick Verification

We can quickly verify by logging in back to MySQL and check:

    shell> mysql -u root -p
    mysql> use membership;
    Database changed

Enter the following command to list down the tables:

    mysql> show tables;

We shall see the "members" in the table list.

    +----------------------+
    | Tables_in_membership |
    +----------------------+
    | failed_jobs          |
    | members              |
    | migrations           |
    | password_resets      |
    | users                |
    +----------------------+

4 – Model/View/Controller (MVC) and Route

A simple understanding about MVC in layman terms:

  • Model - The data structure
  • View - The frontend, or what we see (HTML)
  • Controller - The handler to pass data to the view
  • Route - The URL to the controller

Learn it by doing it. Let's start our practice.

We'll need to create a couple of UI pages to allow users' interaction within the system:

  • Member Creation Page
  • Member Detail Page
  • Members Listing Page

Member Creation Page

Let's define the URL to this page. We'll use https://www.example.com/members/create.

Note

We're using www.example.com as an example domain, it can replaced by any other domain.

    /* routes/web.php */
    Route::get('members/create', 'MemberController@create')->name('members.create');
    Route::post('members', 'MemberController@store')->name('members.store');

Explanation:

  1. name('members.create') and name('members.store') are giving the name to the routes, based on the Laravel Resource Controller naming convention.
  2. Note that we have also defined the route for the POST method, which will be calling the controller store() when user submit the form.

Now we'll create a new members/create.blade.php file - the View.

This page will be a form page, which will consist of 3 input fields that correspond to the 3 member's information as defined earlier.

  • Name
  • Email
  • Contact Number

Note

laravel/ui also install Bootstrap when we generate the frontend boilerplate earlier. We'll be using Bootstrap for the UI styling of our UI.

    <!-- resources/views/members/create.blade.php -->
    <form action="{{ route('members.store') }}" method="post">
        @csrf
        <div class="form-group">
            <label for="name">Name</label>
            <input type="text" class="form-control" id="name" name="name">
        </div>
        <div class="form-group">
            <label for="email">Email</label>
            <input type="email" class="form-control" id="email" name="email">
        </div>
        <div class="form-group">
            <label for="contactNumber">Contact Number</label>
            <input type="text" class="form-control" id="contactNumber" name="contact_number">
        </div>
        <button type="submit" class="btn btn-primary">Create</button>
    </form>

Explanation:

  1. {{ route('members.store') }} is the route name to the Member Controller as defined earlier.
  2. @csrf is Cross Site Request Forgery, used to protect the form from being submitted by non-authorised external domain.

Now, let's create a controller by using Artisan command.

    php artisan make:controller MemberController --model=Member

Note

With the flag --model=Member in the command, it creates the controller as a Resource Controller, and the Member model will be included automatically.

The command will create a new file at app/Http/Controllers/MemberController.php.

Let's define the create function.

    /* app/Http/Controllers/MemberController.php */
    public function create()
    {
        return view('members.create');
    }

Explanation:

  1. This is a simple function that only need to render a static HTML with form.
  2. The return of this function is calling the view helper to render the members/create.blade.php.

We'll need another controller to handle the POST request when user submit the creation form. Let's define the store function.

    /* app/Http/Controllers/MemberController.php */
    public function store(Request $request)
    {
        $member = Member::create($request->all());
        return redirect()->route('members.show');
    }

Explanation:

  1. Member::create($request->all()) directly inserts the POST request data $request->all() into database.
  2. This function does not return any view. Instead, it redirects user to the Member Detail page after member is created.

Note

The method above require the Member model to have the 3 data fields to be fillable. Let's define the $fillable in the model.

    /* app/Models/Member.php */
    class Member extends Model
    {
        //...
        protected $fillable = [
            'name',
            'email',
            'contact_number',
        ];
        //...
    }
    

Now we can create a member into database using the form by navigating to the URL https://www.example.com/members/create.

Member Creation Page
Member Creation Page

Note

After submission, you will face error 404 because the route route('members.show') is not setup yet.

Error 404
Error 404

Member Detail Page

This is a page to layout the member's detail information.

Let's use https://www.example.com/members/{id}, where the {id} is the id of the member in the database.

Example: https://www.example.com/members/1 will render the detail of member with id=1.

    /* routes/web.php */
    Route::get('members/{member}', 'MemberController@show')->name('members.show');

Let's create a new members/show.blade.php file.

    <!-- resources/views/members/show.blade.php -->
    <div class="card">
        <div class="card-body">
            <h2>{{ $member->name }}</h2>
            <p>Email: {{ $member->email }}</p>
            <p>Contact Number: {{ $member->contact_number }}</p>
        </div>
    </div>

The blade file require us to pass $member variable into the view. Let's define the show controller function.

    /* app/Http/Controllers/MemberController.php */
    public function show(Member $member)
    {
        return view('members.show')
            ->with([
                'member' => $member,
            ]);
    }

Explanation:

  1. The controller is using the parameter {member} defined in the route.
  2. The Member $member type-hint the variable $member to the Member model.
  3. With the type-hint variable, we don't have to manually write $member = Member::find($id); to retrieve for the member again.

We can now refresh the page to see the member detail page.

Member Detail Page
Member Detail Page

Members Listing Page

We'll also need a page to list down all the members available in the record. Let's use https://www.example.com/members to show the listing page.

    /* routes/web.php */
    Route::get('members', 'MemberController@index')->name('members.index');

Let's create a new members/index.blade.php.

    <!-- resources/views/members/index.blade.php -->
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
                <th>Contact Number</th>
            </tr>
        </thead>
        <tbody>
            @foreach($members as $member)
                <tr>
                    <td>{{ $member->id }}</td>
                    <td>{{ $member->name }}</td>
                    <td>{{ $member->email }}</td>
                    <td>{{ $member->contact_number }}</td>
                </tr>
            @endforeach
        </tbody>
    </table>

Now let's define the function to the listing page index().

    /* app/Http/Controllers/MemberController.php */
    public function index()
    {
        $members = Member::all();

        return view('members.index', [
            'members' => $members,
        ]);
    }

Explanation:

  1. Member::all() retrieves all the members from database.

Note

As personal preference, I like to create new line for each element in the array, and end the last line with comma(,). This is because GIT won't track it as line change when a new element is added to the array in future.

Let's navigate to the https://www.example.com/members to see the listing page.

Members Listing Page
Example of Member Listing Page with 5 members in the database.

5 - Adapting Laravel Convention

Instead of writing each route explicitly, we can replace all the routes we defined earlier with one simple line Route::resource, which will automatically map to index, create, store, edit, update, destroy function.

    /* routes/web.php */
    Route::resource('members', 'MemberController');

Conclusion

That's pretty much a simple setup of a basic Membership data storage functionality on Laravel 8. The UI's are minimal and plain. We will discuss further on improving the user experience by implementing hints on the form, validation, as well as performance optimisation in upcoming posts.

Next steps...

We'll be developing the transaction history record functionality, and we'll talk about how to use Eloquent ORM to build up the data relationship between the Member and Transaction history.

Next post: Membership System - Transaction History