Skip to main content
Tutorial

How I Structure Large Laravel Applications: Lessons from UAE Enterprise Projects

5 min read

Real talk about structuring large Laravel apps, including folder structure, migration pitfalls, and caching strategies from UAE enterprise projects

LaravelPHPapplication architectureenterprise softwareUAE tech

At 2:43 AM last March, I was staring at a migration file that had taken six hours to run on staging. The client? A logistics company in Jebel Ali with a database that handled 10x more transactions than their legacy system. By 3:15 AM, I realized the service layer we'd built wasn't scaling the way we'd planned—and that "quick fix" we'd called for on day one had become the entire foundation of the app. Sound familiar?

Here's what I've learned structuring Laravel applications for UAE and GCC enterprises since 2019: architecture is more about damage control than perfect design. The systems that survive are the ones that anticipate how teams and codebases will break, not just how users will interact with them.

Folder structure is just tribal knowledge written in slashes

The first thing I do on any app over 50k lines of code is blow up the default Laravel folder organization. I'm not saying app/Http/Controllers is bad, but when you're building a platform with eight developers spread across three time zones (looking at you, Saudi outsourcing team), you need a shared language for code location.

We now structure projects like this:

app
├── Features
│   └── VehicleRegistration
│       ├── Actions
│       ├── Exceptions
│       ├── Http
│       └── Models
├── Shared
│   ├── Enums
│   └── Helpers

This started during a Dubai-based property listing project where a junior dev spent 40 hours chasing a bug through three different controller directories. When each feature directory contains all its dependencies—including route files and middleware—new team members stop asking "where things go" and start focusing on solving problems. Yes, you'll write more use statements. No, it's not a big deal.

Testing isn't a checkbox, it's an insurance policy

In the early days, I treated tests like optional documentation. Now I write feature tests at 2 AM the day after deployment, when I'm still half-asleep and more likely to break production by accident. If something passes at 2 AM, it'll survive the real test: the Saudi developer who'll come in at dawn and start tweaking validation logic without reading the changelog.

For a construction supply platform project last year, I added Pest PHP alongside PHPUnit. The biggest payoff wasn't test coverage—it was developers who'd resisted TDD started writing tests first, just to avoid writing the same assertion syntax twice. Also discovered the hard way that assertJsonStructure() breaks when your API returns null in any unexpected place, and that happens way more than you think.

Migrations that don't ruin your week

I'll be real: database migrations in Laravel can turn into an absolute mess. The worst case? Three developers working off different branches, each adding a "users" table migration two days apart because nobody communicated. (Yes, that happened on a Sharjah e-commerce project. The client was not happy.)

After that, I formalized two rules:

  1. All table migrations get a date-based prefix like 20260130_create_invoices_table. This sounds dumb until you merge a branch and suddenly the database doesn't know which "add phone field" migration to run first.
  2. We use Laravel Schema Dump to freeze the database structure during onboarding sprints. It makes the initial setup 30% slower, but reduces "works on my machine" issues across the 5AM-9PM developer schedule we inherited from a Qatar telecom client.

Localization matters even if you hate Arabic

If you're building anything for the UAE market, plan for right-to-left (RTL) text. But don't do what I did on a 2022 HR system: assume dates and currencies are the only things to consider.

Here's what bit me on a Saudi food delivery app:

  • Arabic sort order for menus matters more than developers realize
  • Number formatting differences between en-sa and en aren't consistent
  • Text expansion rates mean translations can break frontends built around English character counts

I ended up writing a custom middleware component that sets the locale based on the request header first, falling back to the user's account. That way, API consumers can force a specific format regardless of user preferences, which matters when integrating with 3rd party systems in the GCC.

Caching isn't just "throw Redis at it"

I'm not going to lie: cache configuration is a pain in the ass. A real estate project built for a client in Khalifa City taught me that even a basic cache can create nightmares if not handled correctly.

What finally worked? Separate cache tags for each feature domain using Redis prefixes:

php
'stores' => [
    'feature_x' => 'app_features_x',
    'pricing' => 'app_pricing',
],

We were able to warm up the homepage cache with a custom Artisan command during Ramadan sales periods. Which reminds me: UAE users expect faster load times during promotional periods because Ramadan campaigns are serious business here. One second page load is table stakes, no matter if it's Eid or not.

When the architecture is already broken

Tawasul Limo was already halfway built when I joined as a technical lead. They had a "single responsibility principle" for models where a Customer model had 47 methods and loaded relationships across four different schemas. We didn't have time to rewrite everything.

Our fix? Create a second codebase for the new features under app/Features, and only touch the old sections if it absolutely wouldn't delay the release. It took six months to consolidate everything, but the client wasn't paying us to refactor for fun—they had a Ramadan campaign to launch in 10 weeks.

S

Sarah

Senior Full-Stack Developer & PMP-Certified Project Lead — Abu Dhabi, UAE

7+ years building web applications for UAE & GCC businesses. Specialising in Laravel, Next.js, and Arabic RTL development.

Work with Sarah