Laravel Best practice with Examples

Using best practices in Laravel means using tried-and-true ways to write code that is clean, easy to understand, and simple to fix when something goes wrong.

Best practices are helpful tips and rules that many Laravel developers follow to make websites and apps that work smoothly, stay secure, and can be improved easily over time.

Following these standards helps prevent common mistakes, makes teamwork easier, and ensures that what’s built today will still work well in the future as your project grows or new developers join in. It will keep you code cleaner and easy to understand for other developers.

Single Responsibility Principle

With single responsibility principle, every feature or class is focused on one topic. Which makes it easy to understand, extend or test.

Bad Example: Mixing logging and update logic in a controller.

public function update(Request $request): string
{
    $validated = $request->validate([
        'title' => 'required|max:255',
        'events' => 'required|array:date,type'
    ]);

    foreach ($request->events as $event) {
        $date = $this->carbon->parse($event['date'])->toString();
        $this->logger->log('Update event ' . $date . ' :: ' . $);
    }

    $this->event->updateGeneralEvent($request->validated());
    return back();
}

Good Example: Separate concerns into dedicated services.

public function update(UpdateRequest $request): string
{
    $this->logService->logEvents($request->events);
    $this->event->updateGeneralEvent($request->validated());
    return back();
}

In both example, we are dividing application logic into different parts. Like we have moved validation logic into request class which only focus on validation part. By using service classes, it makes logging functionality reusable as well as maintainable.

Methods Should Focused On Just One Thing

Functions should be focused and purposeful. It can be extendable but sometimes extending it for more features makes is complex. So instead of, adding more and more login into single function it should be divided into micro functions which focus on just single functionality.

Bad Example:

public function getFullNameAttribute(): string
{
    if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
        return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
    } else {
        return $this->first_name . '. ' . $this->last_name;
    }
}

Good Example:

public function getFullNameAttribute(): string
{
    return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}

public function isVerifiedClient(): bool
{
    return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}

public function getFullNameLong(): string
{
    return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}

public function getFullNameShort(): string
{
    return $this->first_name . '. ' . $this->last_name;
}

This approach improves readability and makes each method clear in its purpose. Here, each function can be reused with different requirements.

In this case, It can be used full name functions without depending on verified.

Fat Models, Skinny Controllers

In Laravel, we prefer skinny controller and fat models. To perform database operation in Laravel using Eloquent can be used with design pattern like repository. But for simpler case we can utilize models with methods to it will be easy to reuse queries.

As well as controllers should be focused on HTTP request handling only. So whenever, we need to use same queries then we don’t have to invoke controller methods or duplicate code.

Bad Example:

public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($q) {
            $q->where('created_at', '>', Carbon::today()->subWeek());
        }])
        ->get();

    return view('index', ['clients' => $clients]);
}

Good Example:

// Controller
public function index()
{
    return view('index', ['clients' => $this->client->getWithNewOrders()]);
}

class Client extends Model
{
    public function getWithNewOrders(): Collection
    {
        return $this->verified()
            ->with(['orders' => function ($q) {
                $q->where('created_at', '>', Carbon::today()->subWeek());
            }])
            ->get();
    }
}

In this example, model is focused on business logic while controller is focused or optimized for handling HTTP request only.

DRY: Reuse Logic

As we already seen example of reusable logic into focused methods it can help to remove duplicate code. The application logic should be focused repeated code so it will help with 2 things. First is it will make code easy to read or understand. Second, we don’t have to re-write logic every time for performing same operations.

In Laravel framework, We can apply this dry principle with different cases. Let’s understand it with some different scenaerios:

  • Form Requests for Validation: Using Laravel’s form validation class instead of inline validation we can use same validation logic for web and APIs.
  • Laravel Eloquent Scopes: Creating eloquent scopes for commonly used queries or condition to reuse query logic. We will see an example for this scope in further.
  • Laravel API Resource Class: Using API resource class for models, We can create resource for single model and use it whenever required. It will help to load additional fields which are dynamically calculated and with single logic for calculation.
  • Service Classes: Move repeated business logic (e.g., payment processing, report generation) from controllers into dedicated services.
  • Helper Functions / Macros: Extract frequently used snippets (string manipulation, formatting) into helpers or extend classes with macros.

Example

public function scopeActive($q)
{
    return $q->where('verified', true)->whereNotNull('deleted_at');
}

public function getActive(): Collection
{
    return $this->active()->get();
}

public function getArticles(): Collection
{
    return $this->whereHas('user', function ($q) {
        $q->active();
    })->get();
}

In this example, we have defined scope for active that is being used with both other methods.

Favor Laravel’s Eloquent, Collections Over Raw SQL

Use Laravel’s in-built Eloquent ORM instead of raw SQL queries. Which provides expressive methods and features.

Bad Example:

SELECT *
FROM `articles`
WHERE EXISTS (SELECT *
              FROM `users`
              WHERE `articles`.`user_id` = `users`.`id`
              AND EXISTS (SELECT *
                          FROM `profiles`
                          WHERE `profiles`.`user_id` = `users`.`id`)
              AND `users`.`deleted_at` IS NULL)
AND `verified` = '1'
AND `active` = '1'
ORDER BY `created_at` DESC

Good Example:

Article::has('user.profile')->verified()->latest()->get();

Use Eager Loading

Eager loading is best thing in Laravel in terms of solving universal N + 1 problem. It throws unusual loads to the database server when working with large data set.
With help of eager loading, we can load relevant data into memory while getting primary data.

Bad Example:

@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach

Good Example:

$users = User::with('profile')->get();

@foreach ($users as $user)
    {{ $user->profile->name }}
@endforeach

In this example, It will load profile data into users collection and use it. Without using the Laravel eloquent’s with method it will query database each time when loop iterates. It will effect performance and response time of request.

Chunk Data for Heavy Tasks

For processing large data sets, use chunking to reduce memory usage:

$this->chunk(500, function ($users) {
    foreach ($users as $user) {
        // sending mail or performing any other operation for user.
    }
});

Chunking is essential for scalability in data-heavy applications. For this code example, it will send mail to each user. In this case it will fetch 500 users at a time and perform operation for them. Once one batch of 500 users is processed then it will start another batch. So it will load only 500 user’s data into memory and it will help to optimize performace.

Do Not Get Data Directly from .env

Route values through config files for safety and consistency. In general practice, the env file is not exposed to the version control also it’s not cached into memory.

In that case re-route data of env through config file for general purpose. It helps to protects your app from environment leaks.

Conclusion

Laravel best practices is key to building clean, reliable, and maintainable applications. By organizing code properly, using Laravel’s powerful features like Eloquent and Form Requests, and keeping business logic separated, developers can avoid common pitfalls and deliver high-quality software that is easier to manage and grow over time. Whether working solo or in a team, adopting these practices will save time, reduce bugs, and make development more enjoyable and efficient.