Exploiting the potential of Laravel 11's AppServiceProvider

The AppServiceProvider file is often seen as a simple piece of code that you configure at the start of a project and then forget about. Yet it's one of the most important files for properly managing a Laravel application. If you want to organize your code, centralize certain configurations, or even add global functionalities, this is where it all happens.

In this article, I'll show you why the AppServiceProvider deserves more attention, and how you can really leverage it to make your application cleaner and more efficient.

Understanding the role of the AppServiceProvider

The AppServiceProvider is a file generated by default when you create a Laravel project. It is located in the app/Providers directory and contains two main methods: register and boot. These two methods are used to initialize services or configure global aspects of the application.

The register() method

The register method is used to register services in the dependency injection container. This is where you you add any bindings or singletons you want to make accessible throughout your application.

Here's an example:

public function register(): void
{
    $this->app->singleton(MyAwesomeService::class, function ($app) {
        return new MyAwesomeService();
    });
}

Here, we register a MyAwesomeService class as a singleton. This means that Laravel will create a single instance of this class, and share it wherever it's injected.

The boot() method

The boot method, on the other hand, is called after all services have been registered. This is where you can perform actions that require everything to be already loaded, such as defining custom validation rules, configure views or listen to events.

Here's an example:

use Illuminate\Support\Facades\Schema;

public function boot(): void
{
    Schema::defaultStringLength(191);
}

In this example, if you're using MySQL and are having problems with string lengths, you can define a default length.

Protect against destructive commands

When you're working on an application, it's essential to protect your database, especially in production. With the APP_ENV=production option, this protection prevents dangerous commands from executing and risking damage your data. During development, however, there's no need to worry about this, because commands such as db:wipe, migrate:fresh, migrate:refresh or migrate:reset are often used to test, reset or clean the database.

Business logic

Laravel offers us a Facade called Illuminate\Support\Facades\DB, which contains a static method called prohibitDestructiveCommands(). This method takes a boolean parameter. Fortunately, our AppServiceProvider inherits from AppServiceProvider, allowing us to access $app, a reference to the application. This allows us to use isProduction() to find out if we're in a production environment.

Here's how you can integrate it into your AppServiceProvider:

$this->app->isProduction() // return: true or false

Once this logic is in place, simply pass the value returned by isProduction() into DB::prohibitDestructiveCommands(). In this way, dangerous commands will be blocked in production.

In the boot() of your AppServiceProvider, call the configureCommands() method as follows:

use Illuminate\Support\Facades\DB;

final class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureCommands();
    }

    /**
    * Configure the application's commands.
    */
    private function configureCommands(): void
    {
        DB::prohibitDestructiveCommands(
            $this->app->isProduction()
        );
    }
}

With this configuration, you're now protected against destructive commands, and can continue with date configuration!

Making dates immutable

When working with dates in Laravel, we often use the [Carbon] library (https://carbon.nesbot.com/), which can pose a silent problem. problem. Indeed, after a few tests, it seems that dates are by default mutable in Laravel, which can lead to unexpected unexpected behavior.

This is just my opinion, of course. You don't have to adopt this approach, but personally, I think that dates in Laravel should be immutable by default.

Example of the problem

Let's take a model with two columns starts_at and ends_at, both instantiated in Carbon. Let's imagine we define a variable $now, containing the current date and time, and add seven days for ends_at.

$now = now();

$data = [
    'starts_at' => $now,
    'ends_at' => $now->addDays(7),
];

dd(
    $data['starts_at']->format('d-m-Y'), // 24-01-2025
    $data['ends_at']->format('d-m-Y'), // 24-01-2025
);

When you use dd() to display $data, you'll notice a small problem: the two dates, starts_at and ends_at, display exactly the same date (+7 days), which is not what we expected. On the other hand, your model will have the right data.

Solution

To correct this problem, we can use the Facade Illuminate\Support\Facades\Date and tell it to always to always use CarbonImmutable::class by default, an immutable version of Carbon.

Here's how to do it in your AppServiceProvider :

use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Date;

final class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureDates();
    }

    /**
    * Configure the application's dates.
    */
    private function configureDates(): void
    {
        Date::use(CarbonImmutable::class);
    }
}

Now, if you go back to your dd(), you'll see that the dates are correct.

dd(
    $data['starts_at']->format('d-m-Y'), // 17-01-2025
    $data['ends_at']->format('d-m-Y'), // 24-01-2025
);

Now you're ready to start configuring your models!

Models

I know that some people will disagree with me on this point, but after several years working with Laravel, I no longer swear by the famous $fillables = []; that Laravel recommends by default. working with Laravel, I no longer swear by the famous $fillables = []; that Laravel recommends by default. For me, it's a real time-saver not to have to manage it for each new application. Of course, you're free to skip this step if you don't want to disable the $fillables = []; and not have to add $guarded = []; to each template.

Should be Strict!

In my approach, templates should be strict by default. Here's a concrete example:

$user = User::find(1);
$user->propertyDoesntExist;

If you use the standard Laravel implementation, it will throw an exception to tell you that the property property does not exist in the User model. It's a great way of detecting errors quickly!

To force this approach in your application, you can add this piece of code to your AppServiceProvider :

use Illuminate\Database\Eloquent\Model;

Model::shouldBeStrict(! $this->app->isProduction());

Disabling Mass Assignment

I'd now like to talk about managing Mass Assignment in Laravel, which is great for beginners, but can become a bit beginners, but can become a bit restrictive when you're already familiar with the framework. After six years of use, I've found that it's more of a hindrance than anything else to working quickly and efficiently. to work quickly and efficiently.

If you don't want to apply this feature, you can skip this step.

Here's how to disable the Mass Assignment mechanism:

use Illuminate\Database\Eloquent\Model;

Model::unguard();

Simply remove the one you don't want to use.

Putting both in place

If you want to apply both strict templates and disable Mass Assignment, you can set this up in your AppServiceProvider :

use Illuminate\Database\Eloquent\Model;

final class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureModels();
    }

    /**
    * Configure the application's models.
    */
    private function configureModels(): void
    {
        Model::shouldBeStrict(! $this->app->isProduction());
        Model::unguard();
    }
}

Now you're ready to start configuring your passwords!

Password validation

Password validation is something I often configure in my projects. By default, Laravel offers a fairly basic password rule, which may not be sufficient to guarantee user security in more serious applications. Depending on the context of your application, I prefer them to be more robust.

Why is this important?

In my opinion, depending on the application environment, it's important to configure strict password rules in production, while retaining a certain degree of flexibility flexibility in development. In production, the aim is to ensure that no weak or compromised or compromised passwords can be used, while in development, rigor can be reduced to facilitate testing.

Business logic

Here's how I implement this in my AppServiceProvider. I use the I'm using the Facade IlluminateValidationRulesPassword to define the validation rule for passwords. validation rule, and make it conditional on the application environment application environment (production or development).

use Illuminate\Validation\Rules\Password;

final class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configurePasswordValidation();
    }

    /**
    * Configure the application's password validation.
    */
    private function configurePasswordValidation(): void
    {
        Password::defaults(
            fn () => $this->app->isProduction()
                ? Password::min(8)->uncompromised()
                : null
        );
    }
}

Now you're ready to configure the HTTPS scheme!

Force HTTPS Schema

For me, HTTPS is an absolute necessity, even locally. Working exclusively with HTTPS has become a habit, as it simplifies things and ensures a certain consistency between development and production environments. I mainly use Laravel Herd locally, which makes it easy for me to activate HTTPS on my projects without having to worry about additional configurations.

With a view to securing all connections, whether for users or for the application itself, I always make sure to force the use of the HTTPS, even in my development environment. This avoids any confusion and ensures that all communications are encrypted from the outset.

Business logic

Here's how I set this up in my AppServiceProvider to force the use of the HTTPS scheme throughout the application, regardless of the environment (local or production):

use Illuminate\Support\Facades\URL;

final class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureUrls();
    }

    /**
    * Configure the application's URLs.
    */
    private function configureUrls(): void
    {
        URL::forceScheme('https');
    }
}

This ensures that your application is ready for a secure environment environment right from the start. Now you're ready to start configuring Vite for your assets!

Use Aggressive Prefetching for Vite

Laravel 11 has introduced a very interesting feature: the ability to simplify your assets into a single file using Vite. This new feature can greatly improve your application's performance, notably by reducing the number of HTTP requests needed to load your assets.

Business logic

Here's how to activate this feature in your Laravel application:

use Illuminate\Support\Facades\Vite;

final class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureVite();
    }

    /**
    * Configure the application's Vite.
    */
    private function configureVite(): void
    {
        Vite::useAggressivePrefetching();
    }
}

Thank you for following this article to the end.

Your AppServiceProvider now

Here's the complete file with all the configurations. It allows you to to configure the application with the best practices we've discussed in the article :

use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Vite;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\Rules\Password;

final class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureCommands();
        $this->configureDates();
        $this->configureModels();
        $this->configurePasswordValidation();
        $this->configureUrls();
        $this->configureVite();
    }

    /**
    * Configure the application's commands.
    */
    private function configureCommands(): void
    {
        DB::prohibitDestructiveCommands(
            $this->app->isProduction()
        );
    }

    /**
    * Configure the application's dates.
    */
    private function configureDates(): void
    {
        Date::use(CarbonImmutable::class);
    }

    /**
    * Configure the application's models.
    */
    private function configureModels(): void
    {
        Model::shouldBeStrict(! $this->app->isProduction());
        Model::unguard();
    }

    /**
    * Configure the application's password validation.
    */
    private function configurePasswordValidation(): void
    {
        Password::defaults(
            fn () => $this->app->isProduction()
                ? Password::min(8)->uncompromised()
                : null
        );
    }

    /**
    * Configure the application's URLs.
    */
    private function configureUrls(): void
    {
        URL::forceScheme('https');
    }

    /**
    * Configure the application's Vite.
    */
    private function configureVite(): void
    {
        Vite::useAggressivePrefetching();
    }
}