Laravel Best Practices
Consistency First
Before applying any practice, check what the application already does. The best choice is the one the codebase already uses, even if another pattern would be theoretically better. Inconsistency is worse than a suboptimal pattern.
Check sibling files, related controllers, models, or tests for established patterns. If one exists, follow it — don't introduce a second way. These are defaults for when no pattern exists yet, not overrides.
Universal Practices
These apply to nearly every Laravel task. The reference files below provide code examples and edge cases for specific workflows.
Performance
- •Eager load with
with()— never lazy load. EnableModel::preventLazyLoading()in dev - •
withCount()instead of loading relations just to count - •
chunk()/chunkById()for large datasets — neverModel::all()in loops - •Select only needed columns — include foreign keys in eager load selects
- •Index columns in
WHERE,ORDER BY,JOIN - •No queries in Blade templates — pass data from controllers
Security
- •Define
$fillableon every model — never$guarded = []on models accepting user input - •Authorize every action via policies or gates
- •Never interpolate user input into queries — use Eloquent or parameter binding
- •
{{ }}for output,{!! !!}only for trusted pre-sanitized content - •
@csrfon all POST/PUT/DELETE forms - •
encryptedcast +$hiddenfor sensitive database fields
Architecture
- •Constructor dependency injection — never
app()orresolve()inside classes - •Controllers under 10 lines per method — extract to Action or service classes
- •Form Request classes for validation — never inline in controllers
- •
$request->validated()only — never$request->all() - •Implicit route model binding —
show(Post $post)notshow(int $id) - •Default
ORDER BY id DESCorcreated_at DESC— explicit ordering always - •
env()only in config files — direct calls returnnullwhen config is cached - •Follow conventions — don't override table names, primary keys, or pivot naming unless necessary
Eloquent
- •Return type hints on relationships (
HasMany,BelongsTo, etc.) - •Attribute casts in
casts()method — cast all date columns, booleans, JSON - •Local scopes for reusable query constraints — global scopes only for universal filters (soft deletes, tenancy)
Async
- •
retry_aftermust exceed jobtimeout— otherwise jobs duplicate while still running - •Always implement
failed()on jobs - •
ShouldDispatchAfterCommiton events/notifications inside transactions - •
afterCommit()on queued mailables inside transactions
References
Read the relevant reference based on what you're doing:
- •optimizing-queries.md — "I need efficient queries" — N+1 prevention, subqueries, chunking, indexes, collections
- •writing-models.md — "I'm setting up a model" — relationships, scopes, casts,
whereBelongsTo - •securing-code.md — "I need to make this secure" — mass assignment, authorization, SQL injection, XSS, CSRF, file uploads, encryption
- •validating-input.md — "I need to validate user input" — Form Requests,
validated(), conditional rules - •structuring-code.md — "I'm organizing code or setting up routes" — Action classes, DI, route model binding, resource routes,
defer(),Context, config - •writing-migrations.md — "I need to create a migration" — foreign keys, indexes, defaults, immutability, DDL/DML separation
- •writing-tests.md — "I need to write tests" — database refresh, fakes ordering, factories, assertions
- •handling-async.md — "I need background work" — jobs, retry, backoff, events, notifications, mail, scheduling
- •calling-apis.md — "I need to call an external API" — timeouts, retry, pooling, error handling, faking
- •handling-errors.md — "I need error handling" — exception reporting, rendering, throttling, JSON APIs
- •implementing-caching.md — "I need caching" —
remember(),flexible(),once(), locks, tags - •building-views.md — "I'm working on templates" — Blade components, attributes, composers, naming conventions, helpers