As part of my software development journey, I'm building the Strathmore Resource Exchange (SRE) - an internal marketplace for university students to share academic resources. Here's what I've learned so far about Laravel development, routing, and file handling.
The Project Vision
SRE solves two problem for Strathmore University students:
- Outdated academic materials - Official exam banks lag years behind
- Cluttered marketplace - Selling items via social media is insecure
The platform allows students to:
. Share past papers, textbooks, calculators
. Buy/sell items within a trusted ecosystem
. Access verified, recent study materials
Tech Stack
. Backend: Laravel 11 (PHP)
. Frontend: Vue 3 with Inertia.js
. Database: MySQL
. File Handling: Spatie Media Library
Key Learning #1: Resourceful Routing in Laravel
One of the most powerful Laravel features I discovered is resourceful routing. With just one line of code, you can create 7 complete CRUD routes:
Route::resource('listings', ListingController::class)
This single line generated:
| HTTP Method | URL | Controller Method | Purpose |
|---|---|---|---|
| GET | /listings |
index() |
Display all listings |
| GET | /listings/create |
create() |
Show form to create a listing |
| POST | /listings |
store() |
Store a new listing |
| GET | /listings/{listing} |
show() |
Show a single listing |
| GET | /listings/{listing}/edit |
edit() |
Show form to edit a listing |
| PUT / PATCH | /listings/{listing} |
update() |
Update an existing listing |
| DELETE | /listings/{listing} |
destroy() |
Delete a listing |
The Magic of Route Model Binding
When you type-hint a model in your controller
public function show(Listing $listing)
Laravel automatically:
- Takes the ID from the URL (
/listings/5) - Finds the Listing with ID 5
- Injects it into your method
- Returns 404 if not found
Middleware Group for Security
I learnt to protect routes using middleware groups:
Route::middleware(['auth','verified'])->group(function(){
Route::resource('listings', ListingController::class);
});
This ensures only authenticated users can access these routes in our marketplace.
Key Learning #2: Spatie Media Library for File Handling
Handling file uploads (PDFs for past papers, images for items) was a challenge until I discovered Spatie Media Library.
How It Works:
1. Installation & Setup
composer require spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider"
php artisan migrate
2. Model Configuration
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Listing extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('past_papers')
->acceptsMimeTypes(['application/pdf'])
->singleFile();
$this->addMediaCollection('listing_images')
->acceptsMimeTypes(['image/jpeg', 'image/png'])
->singleFile();
}
}
3. Database Structure
Spatie creates a media table that stores:
. File metadata (name, size, mimetype)
. Polymorphic relationship to any model
. Collection names for organization
4. File Upload in Controller
public function store(Request $request)
{
// Create the listing
$listing = Listing::create([...]);
// Handle file based on type
if ($request->hasFile('file')) {
$collection = $request->type === 'paper'
? 'past_papers'
: 'listing_images';
$listing->addMediaFromRequest('file')
->toMediaCollection($collection);
}
}
5. Retrieving Files
public function show(Listing $listing)
{
// Get the file URL
$listing->file_url = $listing->getFirstMediaUrl(
$listing->type === 'paper' ? 'past_papers' : 'listing_images'
);
return view('listing.show', compact('listing'));
}
Benefits of Spatie Media Library:
. Automatic file organization - Files stored in structured folders
. Multiple collections - Separate past papers from item images
. Easy retrieval - Simple methods to get file URLs
. Type validation - Restrict to PDFs or images
. Polymorphic relationships - Reusable across different models
Key Learning #3: Eloquent Relationships & Eager Loading
Database Structure:
// Listings belong to Users and Units
Schema::create('listings', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('unit_id')->nullable()->constrained();
// ... other fields
});
Model Relationships:
class Listing extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
public function unit()
{
return $this->belongsTo(Unit::class);
}
}
The N+1 Query Problem & Solution:
$listings = Listing::all();
foreach ($listings as $listing) {
echo $listing->user->name; // Makes separate query for EACH listing!
}
// Result: 1 query for listings + N queries for users = N+1 queries
**With eager loading (GOOD):**
$listings = Listing::with(['user', 'unit'])->get();
// Result: Only 3 queries total!
Key Learning #4: Conditional Validation
For my use case, I needed different validation rules based on listing type:
. Past papers: Require PDF files (max 10MB)
. Other items: Require images (max 5MB)
use Illuminate\Validation\Rule;
$request->validate([
'file' => Rule::when($request->type === 'paper', [
'required', 'file', 'mimes:pdf', 'max:10240'
], [
'required', 'image', 'mimes:jpg,jpeg,png', 'max:5120'
]),
]);
This uses Laravel's Rule::when() to apply different validation rules conditionally.
Key Learning #5: Inertia.js with Vue
Server-Side (Laravel Controller):
public function index()
{
return Inertia::render('Listings/Index', [
'listings' => Listing::with(['unit', 'user'])->paginate(12)
]);
}
Client-Side (Vue Component):
<template>
<div v-for="listing in listings.data" :key="listing.id">
<h2>{{ listing.title }}</h2>
<p>{{ listing.description }}</p>
</div>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps(['listings'])
</script>
Benefits of Inertia.js:
. Single-page app feel without building an API
. Server-side rendering benefits
. Shared validation errors between Laravel and Vue
. File uploads work seamlessly
Top comments (0)