Eventify

LaravelReactMySQLBootstrap 5

Eventify is a full-stack Laravel platform for creating, promoting, and managing events of any scale — from a small house party to a sold-out concert — with real ticket inventory, geocoded venues, and PDF receipts, so an organizer doesn't need a separate ticketing service bolted onto a listing page.

Architecture and Tech Stack

Core Architecture

  • Backend: Laravel 10 (PHP), server-rendered dashboard + a REST API
  • Frontend: Blade views + React for interactive pieces, Bootstrap 5 for styling
  • Database: MySQL, six related tables (users, roles, events, tickets, categories, statuses, locations), all soft-deleting via deleted_at
  • Geocoding: OpenStreetMap Nominatim — turns an address into latitude/longitude on the locations table
  • PDF generation: DomPDF — streams ticket and event reports on demand
  • Authorization: a custom role middleware (role:admin|user), no policy classes or permission bit-masks

Layered Architecture

Two roles share the same dashboard and event/ticket flows; only the admin-only routes (user management, categories, statuses) are gated by the role middleware. The public /explore and /events/{id}/report routes need no authentication at all.

Request Flow

Creating an event does four things in one request: geocode the address, store the image, assign a Disponible/Agotado status from capacity, and persist — all before the organizer sees a response.

Key Features

Features at a Glance

Event Creation with Live Geocoding

Submitting the event form (POST /dashboard/events/store) creates a Location record, then queries Nominatim with the combined address string and stores the returned coordinates if found — the event carries a real, mappable location, not just a text address. Updates re-geocode only when the address or city actually changes, so editing a price doesn't re-hit the geocoding API.

Ticket Purchase with Automatic Capacity Tracking

Buying a ticket (POST /dashboard/tickets/store) is guarded by a single check — reject if capacity < quantity or capacity == 0 — then decrements capacity, increments attendees, and flips the event's status to Agotado (sold out) the moment capacity hits zero, without a separate admin step.

On-Demand PDF Receipts

Both a ticket (GET /dashboard/tickets/{id}/report) and a full event report (GET /events/{id}/report, publicly accessible) render through DomPDF from a Blade template — pulling in the eager-loaded event relationship, formatting dates with Carbon, and computing the total (quantity × price) at render time rather than storing a stale total.

Two-Tier Roles Without a Permission Framework

Access control is a lowercase string comparison in a custom middleware against a route's declared roles (role:admin|user) — no policy classes, no permission bit-masks, no extra package. admin-only sections (user management, categories, statuses) are simply routes the user role never matches.

Technical Highlights

Status transitions are a side effect, not a separate workflow

Disponible/Agotado isn't a field an admin sets manually during normal operation — it's derived automatically from the capacity math on every ticket purchase, and only Finalizado (event concluded) is an explicit admin action. One less state for the UI to manage by hand.

Smart re-geocoding avoids wasted API calls

The update path only re-queries Nominatim when the address or city fields actually changed — every other edit (price, capacity, description) skips the geocoding round-trip entirely.

A known concurrency gap, named rather than hidden

The capacity check (capacity < quantity) has no database-level lock, so a genuine race condition exists under concurrent purchases of the last few tickets — worth calling out explicitly rather than presenting the ticketing flow as bulletproof; a production deployment would wrap it in lockForUpdate().

Project Structure

app/
├── Http/Controllers/
│   ├── EventController.php     # Create/update/delete + geocoding + image upload
│   ├── TicketController.php    # Purchase flow + capacity/status updates + PDF report
│   ├── CategoryController.php  # Admin CRUD
│   └── ...
├── Models/
│   ├── Event.php                # belongsTo User/Location/Category/Status, hasMany Ticket
│   ├── Ticket.php                # belongsTo User, Event
│   ├── Location.php              # address + geocoded lat/lon
│   ├── Category.php / Status.php
│   └── User.php / Role.php
├── Http/Middleware/
│   └── RoleMiddleware.php        # role:admin|user route guard
└── Http/Resources/                # API response transformers

database/
├── migrations/                    # 6 related tables, all soft-deleting
└── seeders/
    └── CategoryTableSeeder.php    # Musical, Teatro, Deportivo, Cultural, Familiar, Gastronómico

resources/views/
└── reports/
    └── ticket-report.blade.php    # DomPDF template for ticket & event reports

Impact and Scalability

  • One platform, full lifecycle: listing, geocoded venue, ticket sales, and receipts, without stitching together separate services
  • Public API surface: full CRUD for events/categories, read-only for locations/statuses, so the platform can be integrated with beyond its own dashboard
  • Simple, auditable authorization: a two-role system that's easy to reason about, at the cost of not supporting finer-grained permissions if the platform grows
  • Honest about its limits: the documented concurrency gap in ticket purchasing is a real, named consideration for scaling to high-concurrency sales

Notes

Built with Laravel 10, MySQL, React, and Bootstrap 5. Code is public on GitHub. For a deeper technical deep-dive, see the full documentation wiki.


© 2026 Felipe Giraldo