Fat controllers in Laravel
A controller should coordinate the input HTTP, not concentrate validation, business rules, queries, authorization, transformations, email sending and complete flow decisions.

The controller shouldn't be where everything lives
In Laravel it is very easy to start solving logic directly within the controller. The request arrives there, the framework makes it easy to validate, query models, return JSON and trigger processes. For a first version, that speed can be useful.
The problem appears when that pattern becomes the norm. The store method starts by validating, then decides permissions, then queries various models, calculates totals, creates records, sends notifications, and constructs a manual response. The controller stops being a gateway and becomes the complete use case.
A healthy controller coordinates. Receives the request, delegates validation, authorization and execution, and returns a response. When you absorb all the behavior, the application can continue to function, but each endpoint becomes more difficult to read, test, and reuse.
Signs of a controller that is too big
The most obvious sign is many-line store, update, or destroy methods. But size is not the only indicator. It is also worth observing if there is manual validation within the method, complex queries, business conditions mixed with HTTP, repeated authorization or handwritten response transformations.
Another common symptom is finding email sending, jobs, notifications or integrations running directly from the controller. Sometimes generic try/catch also appear that hide real errors, duplicate code between endpoints, or inconsistent responses depending on who implemented each method.
If to test a business behavior you need to prepare a complete request, authenticate the user, set up too many dependencies and go through everything HTTP, probably the use case is too attached to the controller.
Why does so much happen?
The controller is the first visible point when a request arrives. In a young project, solving there seems natural: it is at hand, it is quickly understood and it does not require deciding on a structure. The pressure to deliver features reinforces that decision because creating an Action or a Service seems like extra work.
The problem is cumulative. An isolated period doesn't hurt. Ten rules distributed among several controllers, yes. When there is no clear application layer, each endpoint ends up solving its own version of the process. This generates duplication, inconsistencies and an architecture that depends too much on HTTP.
Laravel does not force you to write fat controllers. It just makes it very cheap to start like that. Therefore, when the application is already sustaining business, it is worth checking if that initial convenience is holding back maintainability.
What risks does it generate?
A large controller makes endpoints more difficult to maintain. Changing a rule can affect validation, persistence, response, notifications, and permissions in the same method. The cognitive cost increases because the reader has to understand many layers at once.
More fragile tests also appear. If the behavior lives inside the controller, testing it without going through HTTP is complicated. This pushes you to write very broad feature tests for rules that could be tested more directly if they were in an Action or Service.
Coupling with HTTP makes it difficult to reuse logic in commands, jobs, internal processes, or APIs alternatives. A small change can have unexpected impact because the logic is not isolated. In the medium term, refactoring is scarier than continuing to accumulate code.
How to lose weight on a controller
The first extraction is usually validation. Form Requests allow you to remove HTTP rules from the method and leave the controller cleaner. Authorization should be moved to Policies or Gates when the decision is repeated or represents a clear access rule.
The use cases fit well into Actions or Services. The name and the responsibility do not matter so much: a class that expresses “create order”, “approve request” or “synchronize client” allows you to test the flow without depending on HTTP. Responses can be delegated to API Resources to avoid scattered manual transformations.
Asynchronous processes, heavy emails or integrations should go to Jobs when it makes sense. Events and Listeners can help decouple side effects, but should not be used as a hiding place for logic. DTOs or Data Objects can be useful if the project needs to pass data between layers more clearly.
Conceptual example
Before: A store method validates fields, checks permissions, creates multiple records, calculates amounts, applies discounts, sends an email, triggers a notification, logs activity, and returns a custom response. Everything works, but the controller no longer coordinates: it executes the entire use case.
After: StoreOrderRequest validates. OrderPolicy authorizes. CreateOrderAction executes the use case. OrderResource transforms the output. A Job manages heavy tasks. The controller is reduced to a few lines that read as a clear sequence.
The advantage is practical. If the validation changes, you know where to tap. If the use case changes, you try it without HTTP. If the answer changes, you modify the resource. If the shipment changes, you review the job. Each piece has fewer reasons to change.
Quick checklist
A controller deserves review if it contains methods of more than 40 or 50 lines, business rules, repeated validations, complex queries, direct emails or notifications, manual response transformations or duplicate logic between endpoints.
The useful question is not “can I move this?”, but “would it be clearer if this decision lived outside of HTTP?” If you can test the use case without going through a full request, the application will probably be easier to maintain.
Closing
A large controller is usually an early sign that the application is losing separation of responsibilities. It is not always urgent, but it is advisable to detect it before each endpoint becomes a fragile piece.
If your controllers are starting to look like complete use cases, a Backend Audit Laravel can help you detect which logic should be extracted first and which changes would reduce the most risk.
Does your Laravel show similar signals?
If your application works, but each change begins to be slower, more fragile or more difficult to explain, a Backend Audit Laravel can help you sort out risks, quick wins and technical priorities.





