Backend DevelopmentFrontend DevelopmentVueJs Tutorials

Building Ecommerce Website With PHP Lumen Laravel And Nuxtjs 1: Preparing Project

Building Ecommerce Website With PHP Lumen Laravel And Nuxtjs

In this first part we will start preparing the environment we will work on it so we will setup two projects one for the server side which is base on Lumen and the other is the frontend side which is Nuxtjs.

 

 

 

Preparing Lumen

Creating a new lumen project somewhere in your server:

composer create-project --prefer-dist laravel/lumen online-shop-backend

Update APP_KEY in .env file like this:

APP_KEY=DPgGmNPCUSb0MYTYwKNnzRLsH0OvIgit

 

Preparing Nuxtjs

Now we we need to think about the nuxt project because we need to have two gateways one for the admin and the other for the the website. Unfortunately Nuxtjs doesn’t support Multi Apps in single project at this moment so we will create two nuxtjs projects but we will make them share the same node_modules folder and same package.json.

In the same folder as we created the lumen project create a nuxtjs project with:

npx create-nuxt-app online-shop-frontend

When initiating this command nuxtjs asks you a few questions just answer them as follows:

create nuxt project

 

 

 

 

 

 

 

 

In the same way create another Nuxt project for the admin panel:

npx create-nuxt-app admin

Select the same options as we did with the first app except that for “Rendering mode” select “spa”. Now let’s move the admin/ directory inside online-shop-frontend/ directory except node_modules, package.json, package-lock.json, .babelrc, .editorconfig, .eslintrc.js, .gitignore, .pretierrc

Update online-shop-frontend/nuxt.config.js add those entries:

srcDir: __dirname,
buildDir: '.nuxt/frontend'

Also online-shop-frontend/admin/nuxt.config.js add those entries:

srcDir: __dirname,
buildDir: '.nuxt/admin'

Update online-shop-frontend/package.json replace the scripts section with this section:

"scripts": {
    "frontend:dev": "nuxt --config-file nuxt.config.js -p=3000",
    "admin:dev": "nuxt --config-file admin/nuxt.config.js -p=4000",
    "dev": "concurrently \"npm run frontend:dev\" \"npm run admin:dev\"",
    "frontend:build": "nuxt build --config-file nuxt.config.js",
    "admin:build": "nuxt build --config-file admin/nuxt.config.js",
    "frontend:start": "nuxt start --config-file nuxt.config.js",
    "admin:start": "nuxt start --config-file admin/nuxt.config.js",
    "frontend:serve": "nuxt serve --config-file nuxt.config.js",
    "admin:serve": "nuxt serve --config-file admin/nuxt.config.js"
  },

Be sure to install this package globally:

npm install -g concurrently

Then inside “online-shop-frontend” run this command

npm run dev

Now the nuxt server will run the two projects simultaneously in ports 3000 and 4000 and any update to any project files we be refreshed automatically. Just navigate to this url you will see the Nuxt homepage.

 

As we progress into this tutorial we will work in parallel in Lumen and Nuxtjs so let’s start in lumen by preparing the database using lumen migrations.

 

Preparing Database & Migrations

Create a new mysql database call it any name i named it onlineshop. Then update lumen project .env db configuration as shown:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=onlineshop
DB_USERNAME=<username>
DB_PASSWORD=<password>

Required Tables

  • Users
  • Categories
  • Category Features
  • Brands
  • Products
  • Product Gallery
  • Product Features
  • Shopping Cart
  • Payment Methods (Cash, Paypal)
  • Shipping Addresses
  • Orders
  • Order Details

Now for each of the these tables we need to create the below migrations in order:

php artisan make:migration create_users_table
php artisan make:migration create_categories_table
php artisan make:migration create_category_features_table
php artisan make:migration create_brands_table
php artisan make:migration create_products_table
php artisan make:migration create_product_gallery_table
php artisan make:migration create_product_features_table
php artisan make:migration create_shopping_cart_table
php artisan make:migration create_shipping_addresses_table
php artisan make:migration create_payment_methods_table
php artisan make:migration create_orders_table
php artisan make:migration create_order_details_table

Now open each migration file located in database/migrations and update the up() method as follows:

YYYY_YY_YY_create_users_table.php

public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique()->notNullable();
            $table->string('password');
            $table->tinyInteger('is_super_admin')->default(0);   // for admin panel access
            $table->timestamps();
        });
    }

YYYY_YY_YY_create_categories_table.php

public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('description')->nullable();
            $table->bigInteger('parent_id')->unsigned()->nullable();
            $table->tinyInteger('featured')->default(0);
            $table->timestamps();

            $table->foreign('parent_id')->references('id')->on('categories')->onDelete('cascade');
        });
    }

YYYY_YY_YY_create_category_features_table.php

public function up()
    {
        Schema::create('category_features', function (Blueprint $table) {
            $table->id();
            $table->string('field_title');
            $table->tinyInteger('field_type')->comment('1 => text, 2 => color');
            $table->bigInteger('category_id')->unsigned();
            $table->timestamps();

            $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
        });
    }

YYYY_YY_YY_create_brands_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBrandsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('brands', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('brands');
    }
}

YYYY_YY_YY_create_products_table.php

public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('description')->nullable();
            $table->decimal('price');
            $table->integer('amount');
            $table->integer('discount')->nullable();
            $table->date('discount_start_date')->nullable();
            $table->date('discount_end_date')->nullable();
            $table->bigInteger('created_by')->nullable()->unsigned();
            $table->bigInteger('category_id')->nullable()->unsigned();
            $table->bigInteger('brand_id')->nullable()->unsigned();
             $table->string('product_code')->nullable();
             $table->tinyInteger('featured')->default(0);
            $table->timestamps();

            $table->foreign('category_id')->references('id')->on('categories')->onDelete('set null');
            $table->foreign('created_by')->references('id')->on('users')->onDelete('set null');
            $table->foreign('brand_id')->references('id')->on('brands')->onDelete('set null');
        });
    }

YYYY_YY_YY_create_product_gallery_table.php

public function up()
    {
        Schema::create('product_gallery', function (Blueprint $table) {
            $table->id();
            $table->string('image');
            $table->bigInteger('product_id')->unsigned();
            $table->timestamps();

            $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
        });
    }

YYYY_YY_YY_create_product_features_table.php

public function up()
    {
        Schema::create('product_features', function (Blueprint $table) {
            $table->id();
            $table->bigInteger('product_id')->unsigned();
            $table->bigInteger('field_id')->unsigned();
            $table->string('field_value')->nullable();
            $table->timestamps();

            $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
            $table->foreign('field_id')->references('id')->on('category_features')->onDelete('cascade');
        });
    }

YYYY_YY_YY_create_shopping_cart_table.php

public function up()
    {
        Schema::create('shopping_cart', function (Blueprint $table) {
            $table->id();
            $table->bigInteger('user_id')->unsigned();
            $table->bigInteger('product_id')->unsigned();
            $table->integer('amount');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
        });
    }

YYYY_YY_YY_create_shipping_addresses_table.php

public function up()
    {
        Schema::create('shipping_addresses', function (Blueprint $table) {
            $table->id();
            $table->string('address');
            $table->string('country', 10);
            $table->string('city', 100);
            $table->string('postal_code', 50);
            $table->string('mobile');
            $table->tinyInteger('is_primary');
            $table->bigInteger('user_id')->unsigned();
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        });
    }

YYYY_YY_YY_create_payment_methods_table.php

public function up()
    {
        Schema::create('payment_methods', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique()->default(NULL);
            $table->timestamps();
        });
    }

YYYY_YY_YY_create_orders_table.php

public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->bigInteger('user_id')->unsigned();
            $table->string('status', 50)->default('pending');
            $table->text('status_message')->nullable();
            $table->bigInteger('payment_method_id')->unsigned();
            $table->bigInteger('shipping_address_id')->unsigned();
            $table->decimal('total_price');
            $table->string('paypal_order_identifier')->nullabe();
            $table->string('paypal_email')->nullabe();
            $table->string('paypal_given_name')->nullabe();
            $table->string('paypal_payer_id')->nullabe();
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users');
            $table->foreign('payment_method_id')->references('id')->on('payment_methods');
            $table->foreign('shipping_address_id')->references('id')->on('shipping_addresses');
        });
    }

YYYY_YY_YY_create_order_details_table.php

public function up()
    {
        Schema::create('order_details', function (Blueprint $table) {
            $table->id();
            $table->bigInteger('order_id')->unsigned();
            $table->bigInteger('product_id')->unsigned();
            $table->decimal('price');
            $table->integer('amount');
            $table->timestamps();

            $table->foreign('order_id')->references('id')->on('orders');
            $table->foreign('product_id')->references('id')->on('products');
        });
    }

Here we created migrations for the above mentioned tables, from these migrations the category which hold the categories as a tree, for this purpose i added an important field the parent_id which enable us to parent-child relation between categories. Also the featured attribute marks the category as featured which we will display featured categories in the website homepage.

The category_features table hold category features that will be filled when creating specific product under this category like product color, product size etc.

The products table hold product data like title, price, amount, whether it’s featured or not, the assigned category etc.In this case for simplicity i suppose that products can be assigned to single category not to many categories.

The product_gallery hold product images to display on search and product details page.

The product_features hold product features which are basically created in category_features table then  when selecting specific category it will fetch these features.

The shopping_cart hold shopping cart details, The shipping_addresses hold shipping data when sending this product to client. The orders and order_details tables hold information about orders when processing the checkout process.

 

Now let’s migrate the tables using:

php artisan migrate

That’s Fine! now the mysql tables is successfully generated let’s create some seeders one for table users to insert the admin user and the other for payment methods.

Create the seeder:

php artisan make:seeder UsersTableSeeder
php artisan make:seeder PaymentMethodsTableSeeder

 

Open database/seeds/UsersTableSeeder.php and add this code inside the run() method:

// create super admin user
        \Illuminate\Support\Facades\DB::table('users')
            ->insert(array('name' => 'admin', 'email' => 'admin@admin.com', 'password' => app('hash')->make('admin12345'), 'is_super_admin' => 1));

Also update database/seeds/PaymentMethodsTableSeeder.php:

\Illuminate\Support\Facades\DB::table('payment_methods')
            ->insert(array('name' => 'Paypal', 'slug' => 'paypal'));
        \Illuminate\Support\Facades\DB::table('payment_methods')
            ->insert(array('name' => 'Pay on delivery', 'slug' => 'cash'));

Call those seeder inside DatabaseSeeder.php:

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call('UsersTableSeeder');
        $this->call('PaymentMethodsTableSeeder');
    }
}

Then run

composer dump-autoload

Finally execute this seeder using:

php artisan db:seed

Great let’s move to the next step is to create the eloquent Models.

 

Creating The Models

Create a new Models/ directory inside the app/ directory, we will create our models inside this directory. Next move the default User model inside Models/ directory and update it as follows:

Models/User.php

<?php

namespace App\Models;

use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Model;
use Laravel\Lumen\Auth\Authorizable;

class User extends Model implements AuthenticatableContract, AuthorizableContract
{
    use Authenticatable, Authorizable;

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

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [
        'password',
    ];
}

Then create other model classes for the other tables along with the relationships like shown below:

Models/Brand.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Brand extends Model
{

}

Models/Category.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    public function features()
    {
        return $this->hasMany(CategoryFeature::class, 'category_id');
    }

    public function products()
    {
        return $this->hasMany(Product::class, 'product_id');
    }

    public function children()
    {
        return $this->hasMany(self::class, 'parent_id');
    }

    public function parent()
    {
        return $this->belongsTo(self::class, 'parent_id');
    }
}

Models/CategoryFeature.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class CategoryFeature extends Model
{
     protected $hidden = ["created_at", "updated_at"];
}

Models/Product.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    public function category()
    {
        return $this->belongsTo(Category::class, 'category_id');
    }

    public function user()
    {
        return $this->belongsTo(User::class, 'created_by');
    }

    public function features()
    {
        return $this->hasMany(ProductFeature::class, 'product_id');
    }

    public function gallery()
    {
        return $this->hasMany(ProductGallery::class, 'product_id');
    }

    public function brand()
    {
        return $this->belongsTo(Brand::class, 'brand_id');
    }
}

Models/ProductFeature.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ProductFeature extends Model
{

}

Models/ProductGallery.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ProductGallery extends Model
{
    protected $table = "product_gallery";
}

Models/ShoppingCart.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ShoppingCart extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function product()
    {
        return $this->belongsTo(Product::class, 'product_id');
    }
}

Models/ShippingAddress.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ShippingAddress extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}

Models/Order.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function paymentMethod()
    {
        return $this->belongsTo(PaymentMethod::class, 'payment_method_id');
    }

    public function shippingAddress()
    {
        return $this->belongsTo(ShippingAddress::class, 'shipping_address_id');
    }

    public function orderDetails()
    {
        return $this->hasMany(OrderDetail::class, 'order_id');
    }
}

Models/OrderDetail.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class OrderDetail extends Model
{
    public function product()
    {
        return $this->belongsTo(Product::class, 'product_id');
    }
}

Models/PaymentMethod.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class PaymentMethod extends Model
{

}

Then update User.php to add the relationships:

public function products()
    {
        return $this->hasMany(Product::class, 'created_by');
    }

    public function orders()
    {
        return $this->hasMany(Order::class, 'user_id');
    }

    public function shippingAddresses()
    {
        return $this->hasMany(ShippingAddress::class, 'user_id');
    }

    public function shoppingCart()
    {
        return $this->hasMany(ShoppingCart::class, 'user_id');
    }

As you see above i created the models and added the relations between them. Now we will continue to part 2 to implement the authentication using JWT.

 

Continue to Part2: Authentication with JWT

 

5 3 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
1
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments