
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:
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