A small Laravel service can call exchangerate.dev, reject failed HTTP responses, and cache a complete FX observation for 60 seconds. Keep the API key in configuration and return source, market_session, and data_updated_at with the rates.
throw() for non-success responses.config/services.php through EXCHANGERATE_API_KEY, not in application code.Build one cached FX service
The service below creates a stable cache key from the base and symbol list. The callback stores only a successful JSON response because throw() raises before Laravel writes a failed request into the cache.
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
final class ExchangeRateService
{
public function latest(string $base, array $symbols): array
{
sort($symbols);
$symbolList = implode(',', $symbols);
$cacheKey = "fx:latest:{$base}:{$symbolList}";
return Cache::remember($cacheKey, now()->addSeconds(60), function () use (
$base,
$symbolList,
): array {
return Http::acceptJson()
->withToken(config('services.exchangerate.key'))
->timeout(5)
->get(
"https://api.exchangerate.dev/v1/latest/{$base}",
['symbols' => $symbolList],
)
->throw()
->json();
});
}
}Configure the key outside source control
// config/services.php
'exchangerate' => [
'key' => env('EXCHANGERATE_API_KEY'),
],
// .env (never commit the real value)
EXCHANGERATE_API_KEY=exr_live_...Read the rate and its observation fields
Call $service->latest("USD", ["EUR", "GBP"]), then read $result["rates"]["EUR"]. Keep $result["source"], $result["market_session"], and $result["data_updated_at"] in the view model or stored record so the value never loses its context.
- Sort symbols before building the cache key.
- Set a bounded HTTP timeout.
- Call throw before reading and caching JSON.
- Round the final amount for display, not the API rate.
