Introduction
How often have you added a cool new API integration to your Laravel project, released the code to production, and then realised that you've forgotten to add the API key to your .env
file?
I'll hold my hand up and admit that I've done this quite a few times.
I usually realise when I do some quick manual testing and get an error message. Thankfully, I usually catch it before the users do, but it's still not ideal.
In this article, I'm going to show you how I use my package "Laravel Config Validator" (ashallendesign/laravel-config-validator
) to validate my Laravel project's config and spot things like missing API keys.
What is Laravel Config Validator?
Laravel Config Validator is a package that I created to help you validate your Laravel project's config files. It allows you to define validation rules for each config value and then run a command to validate them.
๐ As a side note, if you want to take your understanding of Laravel validation rules to the next level, I'd highlighy recommend checking out Mastering Laravel Validation Rules by Joel Clermont and Aaron Saray. It's a deep dive into validation rules with real-world examples and hands-on exercises. I actually wrote a review for the book a while ago and still stand by it being a great read that I think every Laravel developer should have a copy of!
Now, back to the package... The best part about the package (in my opinion) is that you don't need to learn any new syntax! Under the hood, the package uses the Validator
provided by Laravel. So, you can use the same rules that you would typically use in your request validation. This means you can get up and running with it really quickly.
For example, if you wanted to validate your config/services.php
file, you could create a file called config-validation/services.php
and add the following:
config-validation/services.php
1use App\Rules\ConfigValidation\ValidFathomApiKey;2use App\Rules\ConfigValidation\ValidMailcoachApiKey;3use AshAllenDesign\ConfigValidator\Services\Rule;45return [6 Rule::make('mailcoach.api_key')->rules([7 'required',8 'string',9 new ValidMailcoachApiKey(),10 ]),1112 Rule::make('fathom.api_key')->rules([13 'required',14 'string',15 new ValidFathomApiKey(),16 ]),17];
In the above example, we're defining some rules that we can use for validating the services.mailcoach.api_key
and services.fathom.api_key
config values.
We can then run the following command to validate the config values:
1php artisan config:validate
If any of the config values fail validation, you'll be presented with error messages so that you can fix them. For instance, if the Fathom Analytics API key is invalid, you might see the following output:
Benefits of Validating Your Laravel Project's Config
There are a few benefits to validating your Laravel project's config files:
Reduce Bugs and Runtime Errors
As I mentioned earlier, if you forget to add your API keys to the .env
, you'll likely only realise at runtime when your code attempts to use the config field.
By validating your config files, you can catch these errors as part of your deployment process and fix them before they cause any issues.
Faster Onboarding for New Developers
As a freelance web developer, I'm regularly joining new teams and working on new projects. This means there's usually a brief period of time when I'm first setting the project up locally and getting it running. This includes configuring any API keys that are required.
Depending on the project, there can sometimes be a lot of tinkering with the API keys to get them working, such as making sure the token has the correct scopes, and so on.
By using a package such as Laravel Config Validator, you could run the validation command and see if any of the API keys are invalid. This would save a lot of time so the developer can get up and running quicker and start contributing to the project.
The handy thing about Laravel Config Validator is that you can configure certain rules to only run in certain environments. So this means you could have a relaxed set of rules for your local environment, but then have stricter rules for your production environment.
Encourages Code-as-Documentation
Another benefit of using this package is that it encourages code-as-documentation, because you can update your config validation rules each time your config changes rather than updating a boring markdown file with your project's documentation that's likely hidden away and never looked at.
Test API Integrations
The package allows you to write complex validation logic relatively easily. For example, let's say that you had a config field that stored your Fathom API keys. You could write a simple rule to make sure that the key is present and is a string.
But, you could take it one step further and write a rule that makes a HTTP request to the API to confirm that the keys are actually valid and can be used to make the requests in your application.
Later in this article, we'll take a look at how you can create one of these custom rules.
Now, let's take a look at how you can install the package and start using it in your own projects.
Installing the Package
To get started with using the package, you'll need to install it using Composer. You can do so by running the following command in your project root:
1composer require ashallendesign/laravel-config-validator
Defining Config Validation Rulesets
Now that we have the package installed, we can start creating our own rulesets.
Creating new rulesets is really simple using the package's provided command. For example, if we wanted to create a ruleset for validating the config in the config/app.php
file, we could run the following command:
1php artisan make:config-validation app
Running the above command would create a file in config-validation/app.php
ready for you to start adding your config validation.
It's important to remember that your config validation files' names need to match the config file that you want to validate. For example, if you want to validate your config/mail.php
file, your validation file would need to be stored as config-validation/mail.php
.
Adding Rules to a Ruleset
Once you have your ruleset file created in the config-validation
directory, we can start adding our validation rules.
Under the hood, the package uses the built-in Validator
class that ships with Laravel and makes use of all the existing validation rules available in Laravel, so working with it should seem pretty familiar.
Let's take a basic example and create a new validation rule for the driver
field in the app/mail.php
file. We'll define a rule that says the field must be one of the following: smtp
, sendmail
, mailgun
, ses
, or postmark
. To do this, we could write the following rule:
config-validation/mail.php
1use AshAllenDesign\ConfigValidator\Services\Rule;23return [45 // ...67 Rule::make('driver')8 ->rules([9 'in:smtp,sendmail,mailgun,ses,postmark',10 ]),1112 // ...1314];
As you can see, it's really simply to define a rule!
Custom Validation Error Messages
There may be times when you want to override the default error message for a specific validation rule. This is simple to do using the messages
method on the Rule
object when defining your rulesets.
For example, if we wanted to override the error message for our mail.driver
config validation rule that we've created, we could do it like so:
config-validation/mail.php
1use AshAllenDesign\ConfigValidator\Services\Rule;23return [45 // ...67 Rule::make('driver')8 ->rules(['in:smtp,sendmail,mailgun,ses,postmark'])9 ->messages(['in' => 'Our custom error message here']),1011 // ...1213];
Only Running in Specific App Environments
It's possible that you might want to create rules that should only ever be run in specific environments. For example, you might want to create a relaxed set of validation rules for your local development environment and have a stricter set of rules for production.
To specify the environment that a rule can be run in, you can use the environments
method available on the Rule
object. If no environment is defined, the rule will be run in all environments by default.
For example, let's imagine that when we're working on our project locally, we don't really care which mail driver we're using. But, when in production, we want to make sure that we are only using mailgun
. So, we could create a relaxed rule to run locally and a stricter rule to run in production like so:
config-validation/mail.php
1use AshAllenDesign\ConfigValidator\Services\Rule;23return [4 Rule::make('driver')5 ->rules(['in:smtp,sendmail,mailgun,ses,postmark'])6 ->environments([Rule::ENV_LOCAL]),78 Rule::make('driver')9 ->rules(['in:mailgun'])10 ->environments([Rule::ENV_PRODUCTION])11];
Running the Config Validation
Now that we've learned how to define our own config validation rulesets, let's take a look at how to actually run the validation.
Using the Command
The most common (and easiest) way that you can run your config validation is using the Artisan command that ships with the package.
You can use it by running the following command:
1php artisan config:validate
However, you might not always want to validate all of the config values in your application. So, the command allows you to specify the exact config files that you want to validate. To do this, you can use the --files
option.
For example, if we only wanted to validate the config/auth.php
file, we could run the following command:
1php artisan config:validate --files=auth
Likewise, if wanted to explicitly define more than one file to validate at once, we could pass multiple names to the --files
option separated by commas. For example, to validate the config/auth.php
and config/services.php
files, we could run the following command:
1php artisan config:validate --files=auth,services
Running the Validation Manually
There may be times when you'd prefer to run the validation command manually in your code, rather than using the provided command. To do this, you can simply call the run
method on the AshAllenDesign\ConfigValidator\Services\ConfigValidator
class like so:
1use AshAllenDesign\ConfigValidator\Services\ConfigValidator;2 3(new ConfigValidator())->run();
By default, the run
method will return true
if all the validation checks pass. If any of the checks fail, an AshAllenDesign\ConfigValidator\Exceptions\InvalidConfigValueException
will be thrown.
Similar to how we can define the specific validation rulesets that should be run using the command, we can also do the same with the ConfigValidator
object. To do this, we can pass an array of config file names to the run
method.
For example, if we wanted to only run the validation for the config/auth.php
file, we could use the following code:
1use AshAllenDesign\ConfigValidator\Services\ConfigValidator;2 3(new ConfigValidator())->run(['auth']);
Running in a Service Provider
A good example of where you might want to manually run your config validation could be in a service provider. This is an ideal place to run the config validation rules in your local environment when developing to make sure that you have all the valid values. This is particularly useful if you are jumping between Git branches often that include different fields with different required config fields.
Here's how we could automatically run the config validation on each request in our local
environment, and ignore it when running in production
or testing
:
app/Providers/AppServiceProvider.php
1namespace App\Providers;23use AshAllenDesign\ConfigValidator\Services\ConfigValidator;4use Illuminate\Support\Facades\App;5use Illuminate\Support\ServiceProvider;67class AppServiceProvider extends ServiceProvider8{9 public function boot(ConfigValidator $configValidator)10 {11 if (App::environment() === 'local') {12 $configValidator->run();13 }14 }15}
Throwing and Preventing Exceptions
As we mentioned above, by default, the ConfigValidator
class will throw an AshAllenDesign\ConfigValidator\Exceptions\InvalidConfigValueException
exception if the validation fails. The exception will contain the error message of the first config value that failed the validation.
However, there may be times when you don't want to throw an exception on the first failure and would rather run all the rules at once. To do this, you can prevent the exception from being thrown and instead rely on the boolean return value of the run
method by using the throwExceptionOnFailure
method. If we disable exceptions from being thrown, the run
method will return false
if any of the validation checks fail.
By preventing any exceptions from being thrown, we can then get the failed validation errors using the errors
method, which will return them as an array.
The example belows shows how you could prevent any exceptions from being thrown so that you can grab the errors:
1$configValidator = new ConfigValidator();2 3$errors = $configValidator->throwExceptionOnFailure(false)4 ->run()5 ->errors();
This is actually how the package's command runs the validator so that it can display all of the failures in the output at once.
Validating API Keys
Now let's take things a bit further and look at how we could make a rule that validates an API key.
We'll talk about how to validate an API for Fathom Analytics (which is the awesome analytics service I use for all my sites). However, you can use the same approach for any API.
We'll assume that we are fetching our Fathom Analytics key in our config/services.php
file like so:
config/services.php
1return [23 // ...45 'fathom' => [6 'api_key' => env('FATHOM_API_KEY'),7 ],8];
So we'll go ahead and create a config validation file for the config/services.php
file using the following command:
1php artisan make:config-validation services
After running this command, you should now have a file called config-validation/services.php
in your project.
We can go ahead and define our config validation rule in this file. We're going to want to ensure that the services.fathom.api_key
config field is required, is a string, and is a valid Fathom API key that can be used to make requests successfully. We can do this by adding the following to the config-validation/services.php
file:
config-validation/services.php
1use App\Rules\ConfigValidation\ValidFathomApiKey;2use AshAllenDesign\ConfigValidator\Services\Rule;34return [5 Rule::make('fathom.api_key')->rules([6 'required',7 'string',8 new ValidFathomApiKey(),9 ]),10];
As you can see in our example, we're using the Rule::make()
method to define the config field that we want to validate. We're then using the rules()
method to define the rules that we want to use to validate the config field.
You might have also noticed that we're referencing an App\Rules\ConfigValidation\ValidFathomApiKey
class. We haven't made this class yet, but this is what will be responsible for ensuring the API key can be used to make a request.
As I mentioned earlier, the package is using Laravel's built-in Validator
under-the-hood. So this means we can pass any validation rule (including custom rules that implement the Illuminate\Contracts\Validation\ValidationRule
interface) to it like you would do when validating user input in a request.
Let's create this class now using the following command:
1php artisan make:rule ConfigValidation/ValidFathomApiKey
This will create a file called app/Rules/ConfigValidation/ValidFathomApiKey.php
in your project. I've chosen to place the rule inside a ConfigValidation
directory so that it's obvious to me that a rule isn't intended to be used in our application itself, but rather just for validating our config files. This is purely a personal preference though, so you can place the rule wherever you like.
In order for us to validate the API key, we're going to make a request to the Fathom API and check if it's successful. If it is, we'll assume that the API key is valid. If it's not, we'll assume that the API key is invalid and return an error message.
We can do this by adding the following to the app/Rules/ConfigValidation/ValidFathomApiKey.php
file:
app/Rules/ConfigValidation/ValidFathomApiKey.php
1declare(strict_types=1);23namespace App\Rules\ConfigValidation;45use Closure;6use Illuminate\Contracts\Validation\ValidationRule;7use Illuminate\Support\Facades\Http;89final readonly class ValidFathomApiKey implements ValidationRule10{11 public function validate(12 string $attribute,13 mixed $value,14 Closure $fail15 ): void {16 $response = Http::acceptJson()17 ->withToken($value)18 ->get('https://api.usefathom.com/v1/account');1920 if ($response->failed()) {21 $fail('The provided Fathom API key is invalid.');22 }23 }24}
In the above example, we're making a request to the Fathom API using the API key that's defined in the services.fathom.api_key
config field (passed as the $value
parameter). If the request is successful, we'll assume that the API key is valid. If it's not, we'll assume that the API key is invalid and fail the validation.
Generally, you'd want to use an API route that gives you enough information to determine if the API key is valid. In this instance, we just want to check that we can make a request, so I chose to make the request to the /v1/account
endpoint.
However, you might want to make a request to a different endpoint that gives you confidence the API key has the correct scopes and permissions needed. For example, if you're interacting with an API that has very granular control and you want to ensure your token can be used to read user information, you might want to make a request to a users-related endpoint in the API. Just be careful not to make any requests that could cause any side effects, such as creating, updating, or deleting data! How you choose to validate your API keys is entirely up to you.
We can now run the following command to validate our config values:
1php artisan config:validate
If the API key is valid, you'll see the following output:
If the API key is specified but it's invalid, you'll see the following output:
If you forgot to specify the API key in your .env
file, you'll see the following output:
As you can see, the package can give be used to get a lot of information about the state of your config files.
I'm probably a little biased because I built this package, but I think it's a really useful tool to have in your Laravel toolbox. It's saved me a lot of time and headaches when I've accidentally released code to production with missing config values.
Validating Your Config During Deployment Using Laravel Forge
To give some more context about how you can use this package in your deployment process, let's look at a quick example of how to use it with Laravel Forge.
Pretty much every project is unique in the way they do their deployments, so I'm not going to into much detail about how to set up your deployment process. Instead, I'm just going to show you a basic example of how you can use Laravel Config Validator to validate your config files as part of your deployment process.
Hopefully this example will give you some ideas about how you can use it in your own projects.
At the time of writing this article, the default Laravel Forge deployment script looks like so:
1cd /home/ashallendesign/ashallendesign.co.uk 2git pull origin $FORGE_SITE_BRANCH 3 4$FORGE_COMPOSER install --no-dev --no-interaction --prefer-dist --optimize-autoloader 5 6( flock -w 10 9 || exit 1 7echo 'Restarting FPM...'; sudo -S service $FORGE_PHP_FPM reload ) 9>/tmp/fpmlock 8 9if [ -f artisan ]; then10$FORGE_PHP artisan migrate --force11fi
The script above is executing the following steps:
- Moving into the project root directory.
- Pulling the latest code from the Git repository.
- Installing the project's dependencies using Composer.
- Restarting the PHP FPM service.
- Running any migrations.
I like to change this script slightly so that I put my site into maintenance mode before I start the deployment process. This means that if there are any issues with the deployment, the site will still be in maintenance mode and users won't see any errors. Then I run the usual deployment steps, validate my config, and then take the site out of maintenance mode if everything was successful.
So my deployment script might look something like so:
1cd /home/ashallendesign/ashallendesign.co.uk 2 3$FORGE_PHP artisan down 4 5git pull origin $FORGE_SITE_BRANCH 6 7$FORGE_COMPOSER install --no-dev --no-interaction --prefer-dist --optimize-autoloader 8 9( flock -w 10 9 || exit 110echo 'Restarting FPM...'; sudo -S service $FORGE_PHP_FPM reload ) 9>/tmp/fpmlock11 12if [ -f artisan ]; then13$FORGE_PHP artisan migrate --force14fi15 16$FORGE_PHP artisan config:validate17$FORGE_PHP artisan up
As you can see, I've added the following steps:
- Putting the site into maintenance mode before we start pulling down the new code.
- Validating the config fields after running the migrations.
- Taking the site out of maintenance mode.
As a result, this now means that if the config validation fails, the site will remain in maintenance mode and users won't see any errors. This gives me time to fix the issue and then run the deployment script again.
Conclusion
Hopefully, this post should have given you an overview of how you can use the Laravel Config Validator package in your Laravel apps to validate your app's config values. If you're interested in checking out the code for the package, you can view it in the GitHub repo.
If you enjoyed reading this post, I'd love to hear about it. Likewise, if you have any feedback to improve the future ones, I'd also love to hear that too.
You might also be interested in checking out my 220+ page ebook "Battle Ready Laravel" which covers similar topics in more depth.
Or, you might want to check out my other 440+ ebook "Consuming APIs in Laravel" which teaches you how to use Laravel to consume APIs from other services.
If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter below.
Keep on building awesome stuff! ๐