DEV Community

Cover image for 🛠️ Build Better Laravel Apps with Service Interfaces, Providers & Requests
Tahsin Abrar
Tahsin Abrar

Posted on • Edited on

🛠️ Build Better Laravel Apps with Service Interfaces, Providers & Requests

🧳 Real-life Use Case: Travel Booking Service

Let’s say we’re building an API where users can book a tour. Instead of putting everything in the controller, we’ll:

  • Create a BookingService for booking logic.
  • Define a BookingServiceInterface.
  • Bind it using a Service Provider.
  • Use a Form Request class to handle validation.
  • Inject the service into a controller for actual usage.

1. Create the Interface

php artisan make:interface Services/BookingServiceInterface 
Enter fullscreen mode Exit fullscreen mode

app/Services/BookingServiceInterface.php:

<?php namespace App\Services; interface BookingServiceInterface { public function createBooking(array $data); } 
Enter fullscreen mode Exit fullscreen mode

2. Create the Service Class

php artisan make:service Services/BookingService 
Enter fullscreen mode Exit fullscreen mode

app/Services/BookingService.php:

<?php namespace App\Services; use App\Models\Booking; class BookingService implements BookingServiceInterface { public function createBooking(array $data) { return Booking::create([ 'user_id' => $data['user_id'], 'tour_id' => $data['tour_id'], 'date' => $data['date'], 'people' => $data['people'], ]); } } 
Enter fullscreen mode Exit fullscreen mode

3. Create a Custom Form Request for Validation

php artisan make:request StoreBookingRequest 
Enter fullscreen mode Exit fullscreen mode

app/Http/Requests/StoreBookingRequest.php:

<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class StoreBookingRequest extends FormRequest { public function authorize(): bool { return true; } public function rules(): array { return [ 'user_id' => 'required|integer', 'tour_id' => 'required|integer', 'date' => 'required|date', 'people' => 'required|integer|min:1', ]; } } 
Enter fullscreen mode Exit fullscreen mode

✅ Now all validation logic is separated and reusable!


4. Create a Service Provider

php artisan make:provider BookingServiceProvider 
Enter fullscreen mode Exit fullscreen mode

app/Providers/BookingServiceProvider.php:

<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Services\BookingService; use App\Services\BookingServiceInterface; class BookingServiceProvider extends ServiceProvider { public function register() { $this->app->bind(BookingServiceInterface::class, BookingService::class); } public function boot() { // } } 
Enter fullscreen mode Exit fullscreen mode

Then register this provider in config/app.php:

App\Providers\BookingServiceProvider::class, 
Enter fullscreen mode Exit fullscreen mode

5. Use the Service in a Controller

php artisan make:controller BookingController 
Enter fullscreen mode Exit fullscreen mode

app/Http/Controllers/BookingController.php:

<?php namespace App\Http\Controllers; use App\Services\BookingServiceInterface; use App\Http\Requests\StoreBookingRequest; class BookingController extends Controller { protected $bookingService; public function __construct(BookingServiceInterface $bookingService) { $this->bookingService = $bookingService; } public function store(StoreBookingRequest $request) { $booking = $this->bookingService->createBooking($request->validated()); return response()->json([ 'message' => 'Booking created successfully!', 'data' => $booking, ]); } } 
Enter fullscreen mode Exit fullscreen mode

Now the controller is super clean — all it does is call the service with validated data.


✅ Why This Pattern Works in the Real World

  • Separation of Concerns: Controllers don’t care about validation or logic.
  • Testable: Easily mock BookingServiceInterface in tests.
  • Reusable: Same service can be used elsewhere — even in commands or jobs.
  • Maintainable: Validation logic is centralized in StoreBookingRequest.

🧪 Bonus: Mocking the Service in a Test

public function test_booking_creation() { $mock = \Mockery::mock(BookingServiceInterface::class); $mock->shouldReceive('createBooking')->once()->andReturn(['id' => 1]); $this->app->instance(BookingServiceInterface::class, $mock); $response = $this->postJson('/api/bookings', [ 'user_id' => 1, 'tour_id' => 2, 'date' => '2025-06-01', 'people' => 2, ]); $response->assertStatus(200); } 
Enter fullscreen mode Exit fullscreen mode

🧵 Wrapping Up

This is how you build Laravel applications that scale without becoming messy.

Top comments (0)