How I built and designed MyWishlist - Part One
jolamemushaj
Posted on May 9, 2024
The moment I finished Jeffrey Way's course called Laravel 8 From Scratch, I thought that a good way to prove to myself that I understood how Laravel works was to try to create a project myself from scratch. This is where the idea of creating MyWishlist was born.
MyWishlist is a website where a user can add, edit or delete various products and links, which can be divided into categories.
Initially, I started it as a simple CRUD, but recently I came back to give it a more decent design (even though it's purple everywhere 😄) in order to have a more professional appearance. In MyWishlist, the user can add a product where he can specify the name, category to which this product belongs, size, color, price, currency in which the product is sold on the page where the user saw this product and a url that directs you to the page where the product is actually advertised.
The project started with the installation of Laravel and Tailwind. Later during the project I also installed Vue, to experiment a bit with the integration of Vue in an already started project, and to convert some of the blades into Vue components.
Routes
The first step for me was specifying the routes
in web.php
. The first route loads welcome.blade.php
by using the uncommon Route::view()
call:
Route::view('/', 'welcome');
Then I created a page layout using Laravel components, while styling with Tailwind.
Then I gradually continued adding Controllers, Models, Migrations, Factories and Seeders for Products and Categories as the two most important entities.
Models
While working with the backend, it is important to define the models and Eloquent relationships
that exist between them. The first relationship was that a product belongs to a category and a category has many products.
This is the code for the Product model:
//app/Models/Product.php
class Product extends Model
{
use HasFactory;
protected $guarded = [];
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
}
This is the code for the Category model:
//app/Models/Category.php
class Category extends Model
{
use HasFactory;
protected $guarded = [];
public function products(): HasMany
{
return $this->hasMany(Product::class);
}
}
Unit Tests
Since I saw a Jeffrey Way course called Code Katas with PHPUnit, it stuck to me that a good developer should write tests
. So I started writing the first test, where I tested if a product belongs to a category.
Below you will find the code for the test:
// tests/Units/ProductTest.php
public function test_a_product_belongs_to_a_category(): void
{
// arrange
$category = Category::factory()->create();
$product = Product::factory()->create(['category_id' => $category->id]);
// act
$result = $product->category;
// assert
$this->assertInstanceOf(Category::class, $result);
$this->assertEquals($category->id, $result->id);
}
The second test was that a category has many products. Code below:
// tests/Units/CategoryTest.php
public function test_a_category_has_many_products(): void
{
// Arrange
$category = Category::factory()->create();
$product1 = Product::factory()->create(['category_id' => $category->id]);
$product2 = Product::factory()->create(['category_id' => $category->id]);
$otherProduct = Product::factory()->create(['category_id' => Category::factory()->create()->id]);
// Act
$products = $category->products;
// Assert
$this->assertTrue($products->contains($product1));
$this->assertTrue($products->contains($product2));
$this->assertEquals(2, $products->count());
}
Database Migrations
It is also very important to create a proper schema
for the database following the principle to pick the smallest data type that will hold all of your data. Then the connection between the tables should be made through foreign keys
and constraints
. Instead of modifying the production database directly, we use migrations
which must be stored in the database/migrations directory.
Here's how to create a database migration in Laravel:
php artisan make:migration create_categories_table
This command will create the structure for the following code:
return new class extends Migration
{
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
}
};
Also the Schema created for the Product:
return new class extends Migration
{
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
$table->string('name');
$table->string('color');
$table->float('price');
$table->string('currency');
$table->string('size');
$table->string('thumbnail')->nullable();
$table->string('url');
$table->timestamps();
});
}
};
Here we notice that the id
column, which is the primary key
in the categories table, serves as a foreign key
in the products table, where it is renamed category_id
. In this way, the tables are connected to each other.
Factories
Another important element, which helps preparing a local environment, are Factories. Through Factories we generate random database records, instead of manually specifying the value of each column. While working with MyWishlist I generated Factories mainly for Products, Categories and Websites.
Below you can find the code written in ProductFactory.php:
class ProductFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->name(),
'category_id' => Category::factory(),
'size' => fake()->randomDigit(),
'price' => fake()->randomDigit(),
'currency' => fake()->currencyCode(),
'color' => fake()->colorName(),
'url' => fake()->url(),
];
}
}
The fake()
function is a helper method which is used for generating fake data.
Seeders
Meanwhile Seeders are used to generate initial data that the app needs.
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$clothing = Category::factory()->create(['name' => 'Clothing']);
$accessories = Category::factory()->create(['name' => 'Accessories']);
$shoes = Category::factory()->create(['name' => 'Shoes']);
Product::factory(15)->create(['category_id' => $clothing->id]);
Product::factory(15)->create(['category_id' => $accessories->id]);
Product::factory(15)->create(['category_id' => $shoes->id]);
}
}
As can be seen, inside the run()
method I have created three categories Clothing, Accessories, and Shoes using the Category model factory. Each category is assigned a name using the create()
method. Then, it creates 15 products for each category, using the Product model factory.
Until now, I have been writing for all the stuff related to Models and the Database. In the following post, I will write about the Controller logic, a Middleware I used to block editing, storing or deleting records when I deployed on the server, and how I converted Blade views into Vue components.
Thank you for making it this far 😊.
Posted on May 9, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.