Skip to main content

Black Friday 2025! Only until December 1st: coupon FRIDAY25 for 40% off Yearly/Lifetime membership!

Read more here
Premium Members Only
Join to unlock this tutorial and all of our courses.
Tutorial Premium Tutorial

17+ Laravel "Bad Practices" You Should Avoid

April 30, 2024
17 min read

People keep asking me about "best practices" in Laravel. The thing is that Laravel allows us to perform things in many different ways, so there are almost no obvious-absolute-best practices. However, there are clear BAD practices. So, in this article, I will list the most common ones.


What is Considered a "Bad Practice"?

This is kind of my personal interpretation, but I define bad practice as a code that can cause real negative impact in the future:

  • Security issues
  • Performance issues
  • Increased bugs possibility

Some impact may be noticed only years later by new developers joining the team. But then, the price to fix those issues is often very high, usually about big refactorings.

First, I will list the really bad practices (in my opinion) that lead to severe impacts like performance or security issues.

In the second section of the article, I will list "not so bad" practices, some of which are debatable, so be active in the comments.

Now, let's dive into the list!


Bad Practice 1. Not Preventing N+1 Query with Eager Loading.

Let's start with the "elephant in the room".

Whenever I hear a problem with Laravel performance, I first check how many SQL queries are executed under the hood of Eloquent sentences.

Here is the classic example from the docs:

use App\Models\Book;
 
$books = Book::all();
 
foreach ($books as $book) {
echo $book->author->name;
}

That's right, for every book, it will run another SQL query to get its author.

You can fight this problem of too many (N+1) SQL queries on THREE levels.

  1. FIND & fix them: with Laravel Debugbar
  2. AVOID them: learn to use Eager Loading
  3. PREVENT them: add preventLazyLoading()

Bad Practice 2. Loading Too Much Data from DB.

This is somewhat related, also causing performance issues, but from another angle.

$posts = Post::with('comments')->get();
 
foreach ($posts as $post) {
echo $post->title . ': ' . $post->comments()->count();
}

See what is wrong here?

Yes, we're loading the full comments relationship with all the columns, although we need only the COUNT.

Instead, it should be this:

$posts = Post::withCount('comments')->get();
 
foreach ($posts as $post) {
echo $post->title . ': ' . $post->comments_count;
}

If you run too many SQL queries, they load your DB server and network which will probably just go slower and slower.

But if you load too much data from Eloquent into PHP variables, it's stored in RAM. And if that reaches the memory limit, your server will just crash and will load this for users in the browser:

Also, in the same example above, we load all the data for the Post Model, although we need only the title. If posts contain long text content with 1000+ or more words, all those kilobytes will be downloaded into memory. Multiply that by amount of users viewing the page, and you may have a big problem.

So, it should be:

$posts = Post::select('title')
->withCount('comments')
->get();
 
foreach ($posts as $post) {
echo $post->title . ': ' . $post->comments_count;
}

You may not feel the impact on smaller tables, but it may be just a general good habit to adopt.

So the general rule of thumb is "only load the data you actually need".

I also discuss these performance problems from above deeply in the premium tutorial Optimizing Laravel Eloquent and DB Speed: All You Need to Know and in the course Better Eloquent Performance.


Bad Practice 3. Chain Eloquent without checking.

Have you ever seen this code in Blade?

{{ $project->user->name }}

So, the Project belongs to a User, and it seems legit.

But guess what happens if the User model doesn't exist? For whatever reason: soft-deleted, typo in the name, etc.

Then you will get this error:

There are 4 ways to fix this relationship chain: here's a tutorial about it.

My favorite is just using PHP null-safe operators:

{{ $project->user?->name }}

But of course, it depends on the situation. However, my point is that it's a generally bad practice to chain without checking intermediate objects.

Here's another example:

$adminEmail = User::where('is_admin', 1)->first()->email;

What if there's no record of first()?

So, I see many developers trust/assume that the record will always exist because it exists for them at the moment.

Not a future-proof code.

So, always check if the expected record exists, and gracefully show errors if it doesn't.


Bad Practice 4. API returning 2xx Code with Errors.

Tired of talking about Eloquent? Let's switch to APIs. Have you seen something like this "horror" in Controller?

public function store()
{
if ($someCondition) {
return response()->json([
'error' => true,
'message' => 'Something happened'
], 200);
}
 
// ...
}

There are actually many things that need to be corrected in this snippet, but I want to emphasize that 200 number.

It's actually a default value, so this code would do the same:

return response()->json([
'error' => true,
'message' => 'Something happened'
]);

My point is that if the API has some error, you need to return the error code to the API client.

Imagine the face of a front-end or mobile developer who tries to call this API and gets no error code, but their app doesn't work. And they don't know why because API seems to return a good result!

It's so frustrating that it deserves a meme:

Unfortunately, I didn't find the original author, just this Reddit post.

In general, when creating APIs, communicate with all parties involved in consuming that API and agree on the standard requests/responses to avoid misunderstandings. Or, if you create a public API, please document all possible return results and their status codes.

There are also slight differences between returning, for example, 401 vs 403 code, and similar examples. But those are not crucial. The most important is the first number: success or not.

In general, there are dozens of HTTP status codes, but usually only about 10 of them are widely used. I like this list explained in human language.


Bad Practice 5. Blindly Trusting User Data Without Validation.

The classic example is...

Premium Members Only

This advanced tutorial is available exclusively to Laravel Daily Premium members.

Premium membership includes:

Access to all premium tutorials
Video and Text Courses
Private Discord Channel

Comments & Discussion

C
CONSTANTlNE ✓ Link copied!

thanks

LN
Loganathan Natarajan ✓ Link copied!

thanks for sharing

M
Malek ✓ Link copied!

All is good. But I think it's better to response with status 200 in api and if something happens into controller make your own status code. because the request response between front client and your api is successful but something happens within it.

M
Modestas ✓ Link copied!

Hi, this is not really the case. While request is successful by itself (the HTTP part) - it still failed to perform the action. This is exactly why we have status codes, as they cover quite a few cases of "why did the action fail".

Sending a 200 status with API back is only acceptable if the thing actually did the job, otherwise - you are saying to the user something like:

Hello, your purchase order for the medicine was a success, but we could not reach the money in your bank acount, so we give you an imaginary medicine box.

Or in other words, sending back 200 with an error message - is the same as lying. Sure, a tiny part of the communication was a success, but in reality - the task has failed and nothing was completed. Therefore it should get a correct response back

M
Malek ✓ Link copied!

Thankful. It was a perfect explanation. I was convinced to use status to indicate success or failure.

SB
Shadyar Bzhar Othman ✓ Link copied!

Thanks, for number 12 about migrations, if you have more migrations, does it takes more time to run or not?

M
Modestas ✓ Link copied!

Yes, but not really. The first run (if you have to migrate fresh) - might take a little longer.

But if you are deploying to production - you will only run the new ones. So there is no point to modify old ones.

Also, think about migrations like a github commit history. You don't go back and modify what was done, so you shouldn't modify migrations!

SB
Shadyar Bzhar Othman ✓ Link copied!

Thank you very much!

P
Pēteris ✓ Link copied!

Thank you! Nothing new, however, - good to read it again in the list one by one.

We'd Love Your Feedback

Tell us what you like or what we can improve

Feel free to share anything you like or dislike about this page or the platform in general.