Laravel: Products,Categories and SubCategories! (Relation ships)
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Laravel: Products, Categories, and SubCategories! Mastering Eloquent Relationships for Dynamic Routing
I understand your frustration. Building dynamic URLs that gracefully handle missing relationships is a classic challenge in web development. When you design your application structure around relational data (like products, categories, and subcategories), the routing layer needs to be smart enough to adapt to the actual state of your database.
As a senior developer, I can tell you that this problem isn't just about setting up routes; it’s about mastering Eloquent relationships and conditional logic within your controller to serve a coherent user experience. Let's break down how to achieve the dynamic routing you are aiming for in Laravel.
The Foundation: Eloquent Relationships
Before we tackle the routing, we must ensure our data structure is correctly modeled using Eloquent relationships. This is the backbone of any dynamic application. Based on your schema, we need to define how these tables connect:
- Product $\rightarrow$ SubCategory: A product belongs to one subcategory.
- Product $\rightarrow$ Category: A product belongs to one category.
- SubCategory $\rightarrow$ Category: A subcategory belongs to one category.
In your Eloquent models, you would define these relationships:
// app/Models/Product.php
class Product extends Model
{
public function subcategory()
{
return $this->belongsTo(SubCategory::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
}
These relationships allow you to easily fetch related data when you retrieve a product, which is essential for decision-making in your controller. Furthermore, understanding how Eloquent manages these associations is key to building robust applications on the Laravel framework, as showcased by best practices found on laravelcompany.com.
Solving the Dynamic URL Problem
Your goal is to handle two distinct views:
/category/{category}/{subcategory}(Specific view)/category/{category}(Broader view, when no subcategory exists)
The best way to solve this in Laravel is often by using Route Model Binding combined with conditional logic within your controller method.
Step 1: Define the Routes
You should define routes that attempt to match the most specific URL first. If that fails, you fall back to the broader route.
// routes/web.php
// Route for specific product/subcategory view
Route::get('/category/{category}/{subcategory}', [ProductController::class, 'showSpecificView']);
// Fallback route for category listing (no subcategory context)
Route::get('/category/{category}', [ProductController::class, 'showCategoryView']);
Step 2: Implement the Controller Logic
Inside your controller, you use the route parameters ($category and $subcategory) to determine which data scope is needed. The key is checking if the requested subcategory actually exists for the products being queried.
// app/Http/Controllers/ProductController.php
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
public function showSpecificView($category, $subcategory)
{
// Logic to fetch products matching this specific path
$products = Product::where('category_id', $category)->where('subcategory_id', $subcategory)->get();
// Return view...
}
public function showCategoryView($category)
{
// Logic to fetch all products within a category (the fallback)
$products = Product::where('category_id', $category)->with('subcategory')->get();
// Return view...
}
}
In the showCategoryView method, notice we use with('subcategory'). This tells Eloquent to eagerly load the related subcategory data. If a product does not have a subcategory_id (i.e., it's null), the relationship will simply return null, allowing you to display the category information without throwing an error.
Conclusion
By leveraging Laravel’s powerful routing system alongside Eloquent’s relational capabilities, you transform a simple URL problem into a sophisticated data retrieval challenge. The trick is to design your routes to offer both specific and general endpoints, and then use conditional checks within your controller methods to gracefully handle the absence of related data. This approach ensures clean URLs while maintaining database integrity, making your application much more scalable and user-friendly.