Here is a situation I have been in more than once. A project starts small. Controllers are clean. Then six months later, the same Eloquent query appears in five different places. You need to change the logic. You update it in three places and forget the other two. Bugs appear.
The repository pattern solves this. But before I explain how, let me be honest — it adds complexity. Do not use it on small projects. Use it when your project is genuinely large and you have multiple developers.
What is a repository, really?
A repository is just a class that handles all database operations for one model. Instead of writing User::where('email', $email)->first() in your controller, you write $this->users->findByEmail($email). The repository hides the database details.
Why does this matter? Because your controller no longer cares how users are stored. You could switch from MySQL to MongoDB and your controller would not change at all. You can also mock the repository in tests, which makes testing much easier.
How to build one
Start with an interface. This defines the contract — what operations are available:
interface UserRepositoryInterface {
public function findById(int $id): User;
public function findByEmail(string $email): ?User;
public function create(array $data): User;
public function update(User $user, array $data): User;
}Then create the implementation:
class EloquentUserRepository implements UserRepositoryInterface {
public function findById(int $id): User {
return User::findOrFail($id);
}
public function findByEmail(string $email): ?User {
return User::where('email', $email)->first();
}
public function create(array $data): User {
return User::create($data);
}
public function update(User $user, array $data): User {
$user->update($data);
return $user->fresh();
}
}Bind the interface to the implementation in a service provider:
$this->app->bind(UserRepositoryInterface::class, EloquentUserRepository::class);Now inject it into your controller:
public function __construct(private UserRepositoryInterface $users) {}
public function show(int $id): JsonResponse {
return response()->json($this->users->findById($id));
}The honest tradeoff
This pattern adds files and indirection. For a small project, it is overkill. For a project with 10+ developers and 50+ models, it is worth it.
A good middle ground: use it only for your most complex models — the ones with many queries scattered across the codebase. Leave simple models as plain Eloquent calls.
The goal is always readable, maintainable code. The repository pattern is a tool, not a rule.




