Cache External API Calls to Save Money and Time

📖 3 minutes read

External API calls are expensive—in money, time, and rate limits. Whether you’re hitting a weather service, geocoding API, or AI model, repeated requests for the same data waste resources.

Laravel’s Cache::remember() is your friend here. Wrap API calls to cache results automatically:

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

class WeatherService
{
    private const CACHE_TTL = 3600; // 1 hour

    public function getWeather(string $city): ?array
    {
        $cacheKey = "weather_{$city}";

        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($city) {
            $response = Http::get("https://api.weather.com/v1/current", [
                'city' => $city,
                'apiKey' => config('services.weather.key'),
            ]);

            if ($response->failed()) {
                return null;
            }

            return $response->json();
        });
    }
}

Why This Matters

  • Cost savings: Paid APIs (especially AI/LLM services) charge per request. Caching can reduce costs by 90%+.
  • Speed: Cached responses return instantly instead of waiting for network round-trips.
  • Reliability: If the API goes down, cached data keeps your app running.
  • Rate limits: Stay under API quotas without complex request tracking.

Choosing the Right Cache Driver

Laravel supports multiple cache backends. Pick based on your needs:

  • Redis: Fast, shared across servers, but ephemeral (data lost on restart).
  • File: Survives deployments, great for expensive AI API results that should persist.
  • Database: When you need queryable cached data or longer retention.

For AI API responses that are expensive to regenerate, file cache is ideal:

// In config/cache.php, add a dedicated store
'stores' => [
    'ai_responses' => [
        'driver' => 'file',
        'path' => storage_path('cache/ai'),
    ],
],

// Use it explicitly
Cache::store('ai_responses')->remember($key, 86400 * 7, function () {
    return $this->callExpensiveAI();
});

Cache Key Best Practices

Make keys descriptive and collision-resistant:

// ❌ Too generic
$key = "data_{$id}";

// ✅ Namespaced and specific
$key = "weather:current:{$city}:" . date('Y-m-d-H');

// ✅ For complex parameters, hash them
$key = "report:" . md5(json_encode($filters));

Handling Failures

When the API fails, you have options:

return Cache::remember($key, $ttl, function () {
    $response = Http::get($url);

    if ($response->failed()) {
        // Option 1: Return null (cache the failure briefly to avoid hammering)
        Cache::put($key, null, 60); // 1 min
        return null;

        // Option 2: Throw exception (let it propagate, don't cache)
        throw new ApiUnavailableException();

        // Option 3: Return stale data if available
        return Cache::get($key . ':stale');
    }

    return $response->json();
});

The right choice depends on your app—some can tolerate null, others need exceptions to trigger fallback logic.

When NOT to Cache

Don’t cache if the data:

  • Changes constantly (real-time stock prices)
  • Is user-specific and high-cardinality (unique per user ID)
  • Is already fast (sub-10ms database queries)

Otherwise, cache liberally. Your API bill will thank you.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *