← All projects

PHP · Vercel Functions · vercel-php

Currency Converter

Converts between currencies using live exchange rates fetched and applied entirely server-side.

The frontend never talks to a rates API directly — a plain PHP backend fetches current exchange rates from a free, keyless API and performs the conversion before returning a single clean result.

Live demo

embed
<?php
header('Content-Type: application/json');

$from = strtoupper($_GET['from'] ?? 'USD');
$to = strtoupper($_GET['to'] ?? 'CAD');
$amount = isset($_GET['amount']) ? filter_var($_GET['amount'], FILTER_VALIDATE_FLOAT) : null;

if ($amount === null || $amount === false || $amount < 0) {
    http_response_code(400);
    echo json_encode(['error' => 'amount must be a non-negative number']);
    exit;
}

$url = 'https://open.er-api.com/v6/latest/' . urlencode($from);
$context = stream_context_create(['http' => ['timeout' => 8]]);
$response = @file_get_contents($url, false, $context);

if ($response === false) {
    http_response_code(502);
    echo json_encode(['error' => 'Upstream exchange rate service failed']);
    exit;
}

$data = json_decode($response, true);

if (($data['result'] ?? null) !== 'success' || !isset($data['rates'][$to])) {
    http_response_code(400);
    echo json_encode(['error' => "Unsupported currency pair {$from}/{$to}"]);
    exit;
}

$rate = $data['rates'][$to];
$result = round($amount * $rate, 4);

echo json_encode([
    'from' => $from,
    'to' => $to,
    'amount' => $amount,
    'rate' => $rate,
    'result' => $result,
    'updatedAt' => $data['time_last_update_utc'] ?? null,
]);
const fromSel = document.getElementById('from');
const toSel = document.getElementById('to');
const amountInput = document.getElementById('amount');
const errorBox = document.getElementById('error');
const result = document.getElementById('result');

async function convert() {
  errorBox.textContent = '';
  result.style.display = 'none';

  const params = new URLSearchParams({
    from: fromSel.value,
    to: toSel.value,
    amount: amountInput.value || '0',
  });
  const res = await fetch(`/api/convert?${params}`);
  const data = await res.json();

  if (!res.ok) {
    errorBox.textContent = data.error || 'Conversion failed.';
    return;
  }

  document.getElementById('amount-out').textContent = `${data.result.toLocaleString()} ${data.to}`;
  document.getElementById('rate-out').textContent =
    `1 ${data.from} = ${data.rate.toFixed(4)} ${data.to} · updated ${data.updatedAt}`;
  result.style.display = 'block';
}

document.getElementById('go').addEventListener('click', convert);
document.getElementById('swap').addEventListener('click', () => {
  const tmp = fromSel.value;
  fromSel.value = toSel.value;
  toSel.value = tmp;
  convert();
});

convert();