Courses

Livewire VS Vue VS React: Simple Project

Preparation: Models/Migrations/Factories

Welcome! This is a text-based course, but in the video above, you can see the overview of the project we're gonna build.

Before we start creating Livewire/Vue/React dynamic pages, let's build the back-end "core" base of our app: models, migrations, factories, seeders.

This is what we will have in the products table, by the end of this lesson:

Then, we will create three branches from that base, one for each stack.


Building Laravel application

This step has nothing special - just run:

laravel new product-demo

Once this is done, we can start working on our Models.

We want to create migration/model/factory combinations for these objects:

  1. Product Categories
  2. Product Manufacturers
  3. Products
  4. Cart

So, let's do it, model by model.


Product Categories: Migration/Model/Factory

Let's start with the simple migration:

Migration

Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});

app/Models/Category.php

use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
 
class Category extends Model
{
use HasFactory;
 
protected $fillable = ['name'];
 
public function products(): HasMany
{
return $this->hasMany(Product::class);
}
}

database/factories/CategoryFactory.php

use Illuminate\Database\Eloquent\Factories\Factory;
 
class CategoryFactory extends Factory
{
public function definition(): array
{
return [
'name' => $this->faker->text(20),
];
}
}

That's it. Our simple categories are ready.


Product Manufacturers: Migration/Model/Factory

Similar steps here.

Again, we start with our migration:

Migration

Schema::create('manufacturers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});

app/Models/Manufacturer.php

use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
 
class Manufacturer extends Model
{
use HasFactory;
 
protected $fillable = ['name'];
 
public function products(): HasMany
{
return $this->hasMany(Product::class);
}
}

database/factories/ManufacturerFactory.php

use Illuminate\Database\Eloquent\Factories\Factory;
 
class ManufacturerFactory extends Factory
{
public function definition(): array
{
return [
'name' => $this->faker->text(20),
];
}
}

Product: Migration/Model/Factory

Our Product table is a bit more complicated than the others, but nothing too hard:

Migration

Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description');
$table->decimal('price');
$table->foreignId('category_id')->constrained();
$table->foreignId('manufacturer_id')->constrained();
$table->timestamps();
});

But inside the Model, you will see us using a few things:

  • A constant array to get a list of price groups. This could be a separate table in your application.
  • A filter scope that contains different filter lists and returns a filtered products table.

And don't get scared. It looks complicated, but it's just a lot of ->when() conditions.

Let's look at the code:

app/Models/Product.php

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
 
class Product extends Model
{
use HasFactory;
 
protected $fillable = ['category_id', 'name', 'description', 'price', 'manufacturer_id'];
 
const PRICES = [
'Less than 50',
'From 50 to 100',
'From 100 to 500',
'More than 500',
];
 
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
 
public function scopeWithFilters($query, $prices, $categories, $manufacturers)
{
return $query->when(count($manufacturers), function ($query) use ($manufacturers) {
$query->whereIn('manufacturer_id', $manufacturers);
})
->when(count($categories), function ($query) use ($categories) {
$query->whereIn('category_id', $categories);
})
->when(count($prices), function ($query) use ($prices){
$query->where(function ($query) use ($prices) {
$query->when(in_array(0, $prices), function ($query) {
$query->orWhere('price', '<', '50');
})
->when(in_array(1, $prices), function ($query) {
$query->orWhereBetween('price', ['50', '100']);
})
->when(in_array(2, $prices), function ($query) {
$query->orWhereBetween('price', ['100', '500']);
})
->when(in_array(3, $prices), function ($query) {
$query->orWhere('price', '>', '500');
});
});
});
}
}

And lastly, we do need a Factory:

database/factories/ProductFactory.php

use App\Models\Product;
use App\Models\Category;
use App\Models\Manufacturer;
use Illuminate\Database\Eloquent\Factories\Factory;
 
class ProductFactory extends Factory
{
public function definition(): array
{
return [
'name' => $this->faker->word(),
'category_id' => Category::inRandomOrder()->first()->id,
'manufacturer_id' => Manufacturer::inRandomOrder()->first()->id,
'description' => $this->faker->paragraph(),
'price' => rand(10, 999),
];
}
}

That's it, our products are ready.


Cart: Migration/Model

Last preparation step - our Cart table.

Note: Since it's a demo application, we skip the user_id column, so adding to cart will be a bit "fake", just to focus on Livewire/Vue/React dynamic behavior. You should add the user_id in real applications!

Migration

Schema::create('carts', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained();
$table->timestamps();
});

app/Models/Cart.php

use Illuminate\Database\Eloquent\Model;
 
class Cart extends Model
{
protected $fillable = ['product_id'];
}

For the Cart, we don't need a Factory, because we don't seed any demo data for this table.

That's it! Our Cart is now ready to be used in our Demo.


Running Seeders

Of course, manually creating the data is tedious. That is why we had Factories in place. Now we can add a call to our seeder and get some rows into tables:

database/seeders/DatabaseSeeder.php

use App\Models\Category;
use App\Models\Manufacturer;
use App\Models\Product;
use App\Models\User;
use Illuminate\Database\Seeder;
 
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
 
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
User::factory()->create([
'name' => 'Test User',
'email' => 'admin@admin.com',
]);
 
Category::factory(5)->create();
Manufacturer::factory(5)->create();
Product::factory(20)->create();
}
}

Now, if you would run:

php artisan migrate --seed

You should see that there is data in all the tables.

That's it for our base application!

From here, we will start working with UI-based elements, as they all use the same "base application".


What's Next?

In all lessons, we will install Laravel Breeze with different stack choice. So expect to see these examples:

  • Blade Stack with Livewire
  • Inertia with Vue.js
  • Inertia with React.js

In the final GitHub repository, all those three stacks will have separate branches, and direct links will be available after each of the following lessons.

No comments or questions yet...