10 Years Ago I Learned Laravel 5. Here’s What Changed That Actually Matters

A developer-nostalgia piece tracing Laravel’s evolution — and why most “what’s new” posts miss the real architectural shift.


The first Laravel app I ever shipped had a routes.php file with every single route in the entire application in it, a Controller.php I extended for reasons I couldn’t have explained if asked, and an auth system I was fairly sure worked by magic, because Auth::user() just… knew who was logged in, and I hadn’t yet learned enough to be suspicious of things that worked without me understanding why. That was Laravel 5 — new that year, February 2015, and the version most people my career stage cut their teeth on. Middleware was the exciting new thing. The directory restructuring that put controllers under app/Http instead of loose in app/ felt, at the time, like a genuinely big deal, because it was — it was Laravel deciding HTTP concerns deserved their own namespace, separate from the domain logic living in app/ proper.

Most “what’s new in Laravel” posts read like changelogs — a list of features shipped since last time, useful as reference and forgettable as narrative. This isn’t that. This is the version of the story that’s actually interesting to someone who’s been doing this a while: not “here’s a list of new things,” but “here’s the shape of what Laravel is now, compared to the shape of what it was then” — because the real shift isn’t any single feature. It’s that Laravel quietly stopped being a framework you configure and became a framework you assemble from first-party, pre-integrated pieces that already know how to talk to each other. That’s a different kind of thing to build on top of, and most retrospectives miss it because they’re counting features instead of noticing the architecture underneath them changed.


2015: A Framework That Gave You Good Defaults and Otherwise Got Out of the Way

Laravel 5 in 2015 was, by the standards of the PHP ecosystem at the time, remarkably opinionated — middleware, a real directory structure, Eloquent, Blade, task scheduling, the beginnings of contracts-based architecture. But it was opinionated about structure, not about what your app actually did once you got there. Auth was scaffolded, but real-time features, queues beyond the basics, search, permissions, API tooling — all of that was either DIY or a third-party package, often maintained by a single person, often abandoned within a couple of major versions, and always your job to figure out how it should fit together with everything else.

This wasn’t a criticism of Laravel 5 even at the time — it was the correct shape for a framework still establishing what “the Laravel way” of doing anything actually was. But it meant a real app, even eight years ago, was Laravel-the-framework plus a personally-assembled stack of decisions: which auth package, which permission package, which queue driver, which search solution, each chosen independently, each integrated by hand, each a slightly different flavor of “figure it out yourself” depending on which blog post you happened to read that week.


What Actually Changed — Not a Feature List, a Different Relationship to the Ecosystem

Here’s the part most retrospectives get wrong: they’ll tell you Laravel added Reverb for real-time, Pennant for feature flags, Prism for AI integration, Octane for performance, Cashier matured, Sanctum replaced the old token auth story, Scout got a real database-native driver. All true. All also missing the actual shift, which is this: every one of those is now first-party, maintained by the same team, documented in the same place, and built to assume the others exist.

That’s a categorically different thing than “Laravel added more features.” In 2015, if you wanted real-time functionality, you found a community package, read its (possibly outdated) docs, wired it into your app by hand, and hoped it kept getting maintained. In 2026, Reverb exists specifically because Laravel decided real-time was common enough, and important enough, to own the whole vertical rather than leave it to the ecosystem — and because it’s first-party, it’s built with the explicit assumption that it’ll be sitting next to Sanctum-authenticated private channels, next to an Inertia frontend, next to a queue system it can dispatch alongside. The pieces were designed as a system, not independently and later duct-taped together by whoever happened to be building an app that needed more than one of them.

This is the actual architectural shift underneath eight years of changelogs: Laravel moved from “a good framework you build a stack around” to “a stack that mostly already exists, opinionated end to end, that you occasionally need to step outside of.” The default expectation for a new Laravel project in 2026 isn’t “figure out your auth, real-time, search, and AI integration strategy” — it’s “here’s the first-party answer for each of those, already designed to fit together, use it unless you have a specific reason not to.” That’s not a list of features. That’s a different relationship between the framework and the developer building on it.


The Specific Things That Would Have Actually Surprised 2015-Me

Not “these are new,” but “these would have genuinely changed how I thought about building things,” which is a different and more honest bar:

That “real-time” stopped being a special, scary category of feature. In 2015, adding live updates to an app meant evaluating third-party WebSocket services, wrestling with a Socket.io integration that lived in an entirely separate mental universe from the Laravel app it was bolted onto. Reverb, wired through Echo, authenticated through the same Sanctum tokens already protecting the rest of the app, broadcasting from the same Laravel events already firing for other reasons — real-time became a feature you add, not an architecture you adopt.

That the framework has an opinion about AI integration at all. This one wouldn’t have made sense as a sentence in 2015 — there wasn’t a category of “AI integration” to have an opinion about yet. That Laravel now has Prism, a first-party-quality abstraction over multiple LLM providers with structured output and streaming built in, says something about how completely the framework’s philosophy — “here’s the ecosystem’s best current answer, integrated, so you don’t have to assemble it yourself” — extended to a problem space that didn’t exist a decade ago. The philosophy generalized further than the specific technology it started with.

That testing stopped being an afterthought bolted onto PHPUnit’s ceremony. Pest’s rise — it()/expect() blocks reading like actual sentences describing behavior, rather than a class extends TestCase with a test prefix on every method — changed what a test file looks like enough that reading one now feels like reading documentation of intent, not just assertions. This is a smaller shift than the ecosystem-integration point above, but it’s the one that changes daily developer experience the most directly, every single day, in every single test file.

That the monolith won, in a way 2015-me would not have called correctly. The mid-2010s conventional wisdom was that the future was decoupled — a Laravel API, a separate JavaScript frontend, talking over REST, because that’s what “modern” meant at the time. Inertia’s rise, and the entire “modern monolith” pattern it enabled, is close to a rejection of that conventional wisdom: Laravel controllers returning Inertia responses directly to Vue or React page components, no separate API layer for an app that has exactly one frontend consumer, no JSON serialization layer existing purely to satisfy an architectural fashion rather than a real requirement. 2015-me, deep in the “SPA + API” consensus of the time, would not have called this coming back around.


What Didn’t Change, and Is Worth Noticing Precisely Because It Didn’t

Eloquent’s actual API is remarkably recognizable. Model::where(...)->get(), relationships defined as methods returning HasMany/BelongsTo/BelongsToMany, $model->save() — this is close to unchanged in its everyday shape across eight years and roughly seven major versions. What’s evolved around it (attribute casting, first-class enum support, more expressive relationship methods) is additive, not a rewrite of the mental model. This is genuinely rare in software over an eight-year span, and it’s worth crediting deliberately: Eloquent’s API design in 2015 was good enough that the framework never needed to break it to keep improving.

Blade is still Blade. @if, @foreach, {{ }} — the templating syntax a 2015 developer wrote is still exactly what a 2026 developer writes for a server-rendered Blade view, in the (now smaller, but real) set of apps that still render Blade rather than Inertia or an API response. Components got real (<x-alert type="error" />), but the fundamental syntax underneath survived essentially untouched.

Artisan’s whole philosophy — a good CLI generates the boilerplate so you write the logic — never went away, it just got more things to generate. php artisan make:model, make:controller, make:migration in 2015 versus make:policy, and dozens of package-specific generators in 2026, are the exact same underlying idea, extended to cover far more of what a modern Laravel app actually contains.


The One Rule

The mistake in almost every “Laravel in [year]” retrospective is treating the story as a list of features shipped, which makes every single one of these posts read the same regardless of which year it’s written in — new package, new artisan command, slightly nicer syntax, repeat. The actual story, for anyone who’s been doing this long enough to notice it, is that the relationship between the framework and the developer changed shape entirely: from “a well-designed core you build a stack around, one decision at a time” to “an opinionated, pre-integrated stack you occasionally step outside of for a specific, deliberate reason.” That’s not a feature. It’s a different philosophy about how much of a real application’s architecture should already be decided before a single line of business logic gets written — and eight years in, it’s the actual thing that changed, hiding underneath a changelog that makes it look like nothing more than a very long list of new things.

Leave a Reply

Your email address will not be published. Required fields are marked *