Laravel

Laravel Best Practices for 2024

· 6 min read
Laravel Best Practices for 2024

Laravel Best Practices for 2024

As Laravel continues to evolve, staying current with best practices is crucial for building maintainable, scalable applications. This guide covers the latest recommendations and patterns for Laravel development in 2024.

Development Environment Setup

Laravel Sail: Modern Docker Development

Laravel Sail provides a lightweight Docker-based development environment:

curl -s "https://laravel.build/example-app" | bash
cd example-app
./vendor/bin/sail up

Key Benefits:

  • Consistent development environment across team
  • Built-in services (MySQL, Redis, Mailpit, etc.)
  • No local PHP installation required
  • Easy service configuration

IDE Setup for Maximum Productivity

Install these essential extensions for better Laravel development:

{
    "recommendations": [
        "amiralizadeh9480.laravel-extra-intellisense",
        "onecentlin.laravel-blade",
        "shufo.vscode-blade-formatter",
        "bmewburn.vscode-intelephense-client"
    ]
}

Code Organization

Service Layer Implementation

Separate business logic from controllers:

class PostService
{
    public function __construct(
        private readonly Post $post,
        private readonly TagService $tagService
    ) {}

    public function create(array $data): Post
    {
        DB::transaction(function () use ($data) {
            $post = $this->post->create([
                'title' => $data['title'],
                'content' => $data['content'],
                'user_id' => auth()->id()
            ]);

            if (isset($data['tags'])) {
                $this->tagService->attachToPost($post, $data['tags']);
            }

            event(new PostCreated($post));

            return $post;
        });
    }
}

Repository Pattern (When Needed)

Use repositories for complex database interactions:

class PostRepository
{
    public function __construct(
        private readonly Post $model
    ) {}

    public function getPublishedWithAuthors(int $perPage = 15)
    {
        return $this->model->query()
            ->with('author')
            ->published()
            ->latest()
            ->paginate($perPage);
    }

    public function findBySlugOrFail(string $slug): Post
    {
        return $this->model->query()
            ->with(['author', 'comments' => fn($query) => $query->latest()])
            ->whereSlug($slug)
            ->firstOrFail();
    }
}

Request Validation & Authorization

Form Request Validation

Keep controllers clean with dedicated form requests:

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Post::class);
    }

    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255', 'unique:posts'],
            'content' => ['required', 'string', 'min:100'],
            'category_id' => ['required', 'exists:categories,id'],
            'tags' => ['array', 'max:5'],
            'tags.*' => ['exists:tags,id'],
            'published_at' => ['nullable', 'date', 'after_or_equal:today']
        ];
    }

    public function messages(): array
    {
        return [
            'title.unique' => 'This title has already been used. Please choose another.',
            'content.min' => 'The post content should be at least 100 characters long.',
            'tags.max' => 'You can only select up to 5 tags.'
        ];
    }
}

Performance Optimization

Query Optimization

Efficient database queries using Laravel's features:

// Bad ❌
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name;
}

// Good ✅
$posts = Post::with('author')->get();
foreach ($posts as $post) {
    echo $post->author->name;
}

// Better ✅✅
$posts = Post::query()
    ->select(['id', 'title', 'author_id', 'created_at'])
    ->with(['author' => fn($query) => $query->select(['id', 'name'])])
    ->latest()
    ->get();

Caching Strategies

Implement effective caching:

class PostController
{
    public function show(string $slug)
    {
        $post = Cache::remember(
            "posts.{$slug}",
            now()->addHours(24),
            fn() => Post::whereSlug($slug)
                ->with('author', 'comments')
                ->firstOrFail()
        );

        return view('posts.show', compact('post'));
    }
}

Testing Best Practices

Feature Tests

Write comprehensive feature tests:

class CreatePostTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function user_can_create_a_post()
    {
        $user = User::factory()->create();
        $category = Category::factory()->create();

        $response = $this->actingAs($user)
            ->post(route('posts.store'), [
                'title' => 'My New Post',
                'content' => 'Post content here...',
                'category_id' => $category->id
            ]);

        $response->assertRedirect(route('posts.index'));

        $this->assertDatabaseHas('posts', [
            'title' => 'My New Post',
            'user_id' => $user->id
        ]);
    }
}

Security Considerations

Secure File Uploads

Handle file uploads securely:

class MediaController
{
    public function store(Request $request)
    {
        $request->validate([
            'file' => ['required', 'file', 'mimes:jpg,png,pdf', 'max:10240']
        ]);

        $path = $request->file('file')->store(
            path: 'uploads/' . auth()->id(),
            options: ['disk' => 'private']
        );

        return Media::create([
            'user_id' => auth()->id(),
            'path' => $path,
            'original_name' => $request->file('file')->getClientOriginalName(),
            'mime_type' => $request->file('file')->getMimeType(),
            'size' => $request->file('file')->getSize()
        ]);
    }
}

API Development

API Resources

Use API Resources for consistent responses:

class PostResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'slug' => $this->slug,
            'content' => $this->when($request->user()?->can('view-full-post'), $this->content),
            'excerpt' => Str::limit($this->content, 200),
            'author' => new UserResource($this->whenLoaded('author')),
            'created_at' => $this->created_at->toISOString(),
            'updated_at' => $this->updated_at->toISOString(),
            '_links' => [
                'self' => route('api.posts.show', $this->id),
                'author' => route('api.users.show', $this->author_id)
            ]
        ];
    }
}

Maintenance and Deployment

Queue Management

Handle long-running tasks with queues:

class ProcessPostImages implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        private readonly Post $post
    ) {}

    public function handle(): void
    {
        $this->post->images->each(function ($image) {
            ImageProcessor::dispatch($image)
                ->onQueue('images')
                ->delay(now()->addSeconds(30));
        });
    }
}

Common Pitfalls to Avoid

  1. N+1 Query Problems: Always use eager loading when accessing relationships
  2. Mass Assignment Vulnerabilities: Always define $fillable or $guarded
  3. Raw SQL Queries: Use Query Builder or Eloquent when possible
  4. Lack of Validation: Always validate user input
  5. Missing Database Indexes: Add indexes for frequently queried columns

Conclusion

Following these Laravel best practices will help you build more maintainable, secure, and performant applications. Remember to:

  • Keep controllers thin
  • Use form requests for validation
  • Implement service layer for business logic
  • Write comprehensive tests
  • Optimize database queries
  • Handle errors gracefully
  • Document your code

Stay updated with Laravel's documentation and community resources for the latest recommendations and updates.

Happy coding! 🚀

Share this article