Skip to main content
Laravel, shipping fast.

Let client-side caching work for you. Browsers and API clients respect cache headers:

class AddCachingHeadersMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        // GET requests can be cached by clients
        if ($request->getMethod() === 'GET' && $response->status() === 200) {
            $response->header('Cache-Control', 'public, max-age=3600');
            $response->header('ETag', hash('sha256', $response->getContent()));
        }

        // Write operations: don't cache
        if (in_array($request->getMethod(), ['POST', 'PUT', 'DELETE'])) {
            $response->header('Cache-Control', 'no-cache, no-store, must-revalidate');
        }

        return $response;
    }
}

HTTP caching lets clients avoid making the same request twice. They check the ETag, see the response hasn't changed, and use their cached copy.

Database Connection Pooling

For high-concurrency APIs, connection pooling helps:

// config/database.php
'mysql' => [
    'driver' => 'mysql',
    'host' => env('DB_HOST'),
    'port' => env('DB_PORT'),
    'database' => env('DB_DATABASE'),
    'username' => env('DB_USERNAME'),
    'password' => env('DB_PASSWORD'),

    // Connection pooling
    'options' => [
        PDO::ATTR_PERSISTENT => true,
        PDO::MYSQL_ATTR_INIT_COMMAND => "SET sql_mode='STRICT_TRANS_TABLES'",
    ],

    'pool' => [
        'min' => 2,
        'max' => 10,
    ],
],

Request/Response Truncation

Store large payloads with compression:

class ApiRequestLog extends Model
{
    protected $table = 'api_request_logs';

    protected function requestBody(): Attribute
    {
        return Attribute::make(
            get: fn (?string $value) => $value ? gzuncompress($value) : null,
            set: fn (?string $value) => $value ? gzcompress($value) : null,
        );
    }

    // Truncate large bodies before storing
    protected static function booted()
    {
        static::creating(function ($model) {
            if (strlen($model->request_body ?? '') > 10000) {
                $model->request_body = substr($model->request_body, 0, 10000);
            }
        });
    }
}

Pagination for Large Results

Never return all results at once:

// config/pagination.php defaults
'per_page' => 15,

// In controller
public function index(Request $request): Response
{
    $domains = Domain::paginate($request->input('per_page', 15));

    return response()->json([
        'data' => $domains->items(),
        'pagination' => [
            'current_page' => $domains->currentPage(),
            'per_page' => $domains->perPage(),
            'total' => $domains->total(),
            'last_page' => $domains->lastPage(),
        ],
    ]);
}

// Usage: GET /api/licenses?page=2&per_page=25

Performance Monitoring

Track slow queries and requests:

// In boot method
DB::listen(function (QueryExecuted $query) {
    // Log queries taking >1 second
    if ($query->time > 1000) {
        Log::warning('Slow query detected', [
            'query' => $query->sql,
            'bindings' => $query->bindings,
            'time_ms' => $query->time,
        ]);
    }
});

// Track request duration
class LogApiRequestsMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        $start = microtime(true);
        $response = $next($request);
        $duration = round((microtime(true) - $start) * 1000);

        // Log slow requests
        if ($duration > 500) {
            Log::warning('Slow API request', [
                'path' => $request->getPathInfo(),
                'duration_ms' => $duration,
            ]);
        }

        return $response;
    }
}

Performance Checklist

  • ✅ Use eager loading (with())
  • ✅ Index frequently queried columns
  • ✅ Cache expensive operations
  • ✅ Compress responses (gzip)
  • ✅ Paginate large result sets
  • ✅ Use database connection pooling
  • ✅ Monitor slow queries
  • ✅ Select only needed columns
  • ✅ Use CDN for static assets
  • ✅ Profile regularly

Chapter 5 Summary

Performance fundamentals:

  1. Compression - Gzip responses for 90% size reduction
  2. Eager Loading - Prevent N+1 database queries
  3. Caching - Cache expensive operations with expiration
  4. HTTP Caching Headers - Let clients cache responses
  5. Monitoring - Catch slow queries and endpoints
  6. Database Indexing - Index frequently filtered columns
  7. Pagination - Never load all records at once

Pay attention to the slowest part of your request, whether it is the database, an external API, or computation, and optimize the bottleneck first.

Performance is not a feature you add later. It's a characteristic of good architecture.