Laravel Best Practices for 2024
- Development Environment Setup
- Code Organization
- Request Validation & Authorization
- Performance Optimization
- Testing Best Practices
- Security Considerations
- API Development
- Maintenance and Deployment
- Common Pitfalls to Avoid
- Conclusion
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
- N+1 Query Problems: Always use eager loading when accessing relationships
-
Mass Assignment Vulnerabilities: Always define
$fillable
or$guarded
- Raw SQL Queries: Use Query Builder or Eloquent when possible
- Lack of Validation: Always validate user input
- 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! 🚀