0

Built-in middleware: TrimStrings, ConvertEmptyStringsToNull, TrustProxies

Intermediate5 min read·lv-07-007

Concept

Laravel ships with several built-in middleware that handle common concerns automatically. Understanding what they do prevents accidentally disabling important behaviors.

TrimStrings: Trims whitespace from all string input (GET/POST parameters). Prevents " admin " matching "admin" in comparisons. Applied to all requests in the web middleware group. Configured to skip certain keys (passwords).

ConvertEmptyStringsToNull: Converts empty string values ("") to null. This makes form inputs where the user left fields blank treat them as null rather than empty strings. Affects nullable database columns correctly. Be aware: after this middleware, $request->input('optional_field') returns null, not "".

TrustProxies: Essential when your application is behind a load balancer or reverse proxy (Nginx, CloudFront, Heroku). Without it, $request->ip() returns the load balancer's IP, not the client's. Also affects HTTPS detection. Configure $proxies and $headers (which proxy header contains the real client IP).

PreventRequestsDuringMaintenance: Checks if the application is in maintenance mode (php artisan down). Returns HTTP 503 to all requests except those from allowed IPs. php artisan down --allow=192.168.1.1 --secret=bypass-token — specific IPs and bypass token.

HandleCors: Handles Cross-Origin Resource Sharing headers. Reads configuration from config/cors.php. Adds Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers to responses. Required for frontend apps on different origins calling your API.

Code Example

php
<?php
// TrustProxies — configure for your load balancer setup
namespace App\Http\Middleware;

use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;

class TrustProxies extends Middleware
{
    // Trust all proxies (for cloud/Docker environments)
    protected $proxies = '*';

    // Which headers contain the real client IP / proto
    protected $headers =
        Request::HEADER_X_FORWARDED_FOR    |   // X-Forwarded-For
        Request::HEADER_X_FORWARDED_HOST   |   // X-Forwarded-Host
        Request::HEADER_X_FORWARDED_PORT   |   // X-Forwarded-Port
        Request::HEADER_X_FORWARDED_PROTO  |   // X-Forwarded-Proto (http/https)
        Request::HEADER_X_FORWARDED_AWS_ELB;   // AWS ELB specific
}

// TrimStrings — skip password fields
class TrimStrings extends \Illuminate\Foundation\Http\Middleware\TrimStrings
{
    protected $except = [
        'current_password',
        'password',
        'password_confirmation',
    ];
}

// ConvertEmptyStringsToNull — skipping specific inputs
class ConvertEmptyStringsToNull extends \Illuminate\Foundation\Http\Middleware\TransformsRequest
{
    protected function transform($key, $value): mixed
    {
        // Skip this field — keep empty string as empty string
        if ($key === 'notes') {
            return $value;
        }
        return is_string($value) && $value === '' ? null : $value;
    }
}

// Maintenance mode
// php artisan down --message="Upgrading database" --retry=60 --secret=my-secret-token
// php artisan down --allow=127.0.0.1

// Access during maintenance via secret URL: /my-secret-token (sets bypass cookie)

// CORS config (config/cors.php)
return [
    'paths'               => ['api/*', 'sanctum/csrf-cookie'],
    'allowed_origins'     => [env('FRONTEND_URL', 'http://localhost:3000')],
    'allowed_methods'     => ['*'],
    'allowed_headers'     => ['*'],
    'exposed_headers'     => ['X-Request-ID', 'X-RateLimit-Remaining'],
    'max_age'             => 0,
    'supports_credentials'=> true,
];