Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Route Closures vs. Controller Methods in Laravel

When you add an endpoint to a Laravel application, you decide whether its request-handling code goes in the route declaration or a controller. A route closure puts that code directly in the route declaration as an anonymous PHP function. A controller method puts it in a named method on a class and points the route to that method.

Laravel first matches a request’s URL and HTTP method to a route, then runs its closure or controller method. Use a closure for a short, self-contained action; use a controller when an action needs structure, belongs with related endpoints, or has grown beyond a few lines. The choice affects how you organize and maintain code, not which routing system Laravel uses.

How Laravel runs each route action

A route declaration tells Laravel which code to run for a particular HTTP method and URL. For example, a closure can return a simple response directly from the route file:

use Illuminate\Support\Facades\Route;

Route::get('/greeting', function () {
    return 'Hello World';
});

When a request matches this route, Laravel runs the closure. The closure can also receive URL values, called route parameters, and dependencies named in its PHP type declarations, such as Laravel’s request object. Before running the closure, Laravel supplies the values and dependencies its parameters request, as shown in its routing documentation.

A controller route points to a class and method instead:

use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;

Route::get('/users/{id}', [UserController::class, 'show']);

After the route matches, Laravel gets the controller from its service container, the part of Laravel that creates objects and supplies their dependencies. Laravel then calls the named method and supplies its parameters. A controller can also receive dependencies through its constructor. The controller documentation describes these patterns.

In both cases, Laravel matches the route before running the action. With a controller, Laravel calls a method on a class; with a closure, it runs the function directly. There is no established evidence of a meaningful per-request performance advantage for either approach.

Why controllers help as behavior grows

A controller gives request-handling code a named place outside the route declaration. Route files can then list which URLs call which actions, while controller methods do the request-handling work. Laravel’s documentation describes controllers as a way to group related request logic, such as actions for showing, creating, updating, and deleting users.

A closure works well for a small route that returns a static view. Put its behavior in a controller method when it needs authorization checks or input handling, or when it relies on several dependencies. That gives the code a named place and keeps the route file from growing.

Controllers also fit Laravel’s resource-controller conventions, which organize common actions around a resource such as users or products. For a complex endpoint that does not belong with a group of related actions, Laravel supports an invokable controller: a class with one method for one action. Closures can also receive injected dependencies, so Laravel supplying dependencies does not by itself require a controller.

A controller does not guarantee good organization. When one controller collects unrelated actions, it becomes as hard to understand as an oversized route file. Keep related actions together. Give a complex action its own controller when that makes its purpose clearer.

Choosing by app size and route caching

For a small application, a closure keeps a trivial endpoint close to its URL and avoids creating a class for a short response. Choose a controller when the endpoint’s code needs a named place, even if the application has few routes. For example, a closure that only returns a view is easy to scan. A controller is easier to manage when an endpoint handles several parts of a request.

In a larger application, developers have more routes and features to search when they need to change code. Controllers give related actions clear names and locations, and route declarations stay easier to scan. This structure works well as the codebase grows, but not every endpoint needs a controller.

Route caching is a separate consideration. Laravel’s route:cache command prepares routes so Laravel can register them more quickly. The deployment documentation highlights this benefit for applications with large route collections. Older guidance sometimes says closure routes cannot be cached. In Laravel 13.x, the framework’s route code handles serialized closures, which represent closures in a form Laravel can store. So the blanket claim that closure routes cannot be cached does not apply as a general rule to the current version. The routing implementation does not establish that every closure and captured value will serialize successfully.

Run php artisan route:cache against the project’s actual routes using the Laravel version and deployment environment you use. If it fails, use the reported route or serialization error to adjust that route. Do not move every closure into a controller just because of the old blanket claim about caching. Route caching also does not prove that controller dispatch makes individual requests faster.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Email updates

Usually a new article and a few links I found interesting.

No spam. Unsubscribe with one click.

Leave a Reply

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