DEV Community

Nelson Orina
Nelson Orina

Posted on

Building a University Resource Sharing Platform: My Laravel Learning Journey

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:

  1. Outdated academic materials - Official exam banks lag years behind
  2. 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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Laravel automatically:

  1. Takes the ID from the URL (/listings/5)
  2. Finds the Listing with ID 5
  3. Injects it into your method
  4. 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);
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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'));
}
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

Model Relationships:

class Listing extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function unit()
    {
        return $this->belongsTo(Unit::class);
    }
}
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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'
    ]),
]);
Enter fullscreen mode Exit fullscreen mode

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)
    ]);
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)