Tutorial/React currency converter

Build a Currency Converter in React

Build the interface in React, but call the exchange-rate provider from your server so credentials, caching, and fallback behavior stay outside the browser.

ERexchangerate.dev·Sep 20, 2026·8 min read

The safest React currency converter has two layers: a small component that requests a conversion from your own route, and a server handler that calls the upstream API with a timeout and server-side key.

Key points
Never put a production exchange-rate API key in a React client bundle.
Return rate, converted amount, source, and observation time from your server route.
Handle loading, invalid amounts, upstream errors, and stale fallback explicitly.
Debounce typing or submit deliberately instead of fetching on every keystroke.

Create the server-side conversion route

This Next.js route validates the three inputs, calls exchangerate.dev on the server, and forwards the documented JSON response. The API key remains in the deployment environment.

typescript · app/api/convert/route.tscopy
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl;
  const from = searchParams.get("from")?.toUpperCase();
  const to = searchParams.get("to")?.toUpperCase();
  const amount = Number(searchParams.get("amount"));
  if (!from || !to || !Number.isFinite(amount) || amount < 0) {
    return NextResponse.json({ error: "invalid_input" }, { status: 400 });
  }

  const response = await fetch(
    `https://api.exchangerate.dev/v1/convert/${from}/${to}/${amount}`,
    {
      headers: { Authorization: `Bearer ${process.env.EXCHANGERATE_API_KEY}` },
      signal: AbortSignal.timeout(5000),
      next: { revalidate: 60 },
    },
  );
  return NextResponse.json(await response.json(), { status: response.status });
}

Build the React form and result state

tsx · CurrencyConverter.tsxcopy
"use client";

import { FormEvent, useState } from "react";

type ConversionResult = {
  converted: string;
  source: string;
  data_updated_at: string;
};

export function CurrencyConverter() {
  const [amount, setAmount] = useState("100");
  const [result, setResult] = useState<ConversionResult | null>(null);
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);

  async function submit(event: FormEvent) {
    event.preventDefault();
    setLoading(true); setError("");
    try {
      const params = new URLSearchParams({ from: "USD", to: "EUR", amount });
      const response = await fetch(`/api/convert?${params}`);
      const body = await response.json();
      if (!response.ok) throw new Error(body.error ?? "conversion_failed");
      setResult(body);
    } catch (caught) {
      setError(caught instanceof Error ? caught.message : "conversion_failed");
    } finally { setLoading(false); }
  }

  return <form onSubmit={submit}>
    <input value={amount} onChange={(event) => setAmount(event.target.value)} inputMode="decimal" />
    <button disabled={loading}>{loading ? "Converting…" : "Convert"}</button>
    {error && <p role="alert">{error}</p>}
    {result && <output>{result.converted} EUR · {result.source} · {result.data_updated_at}</output>}
  </form>;
}

Add production behavior before styling

  • Validate currency codes against a server-owned allowlist.
  • Cache successful observations for the cadence your product actually needs.
  • Show the last observation time rather than pretending every render fetched a new rate.
  • Keep the last successful value during a short outage and label it stale.
  • Use decimal-safe money handling when the converted value enters accounting or billing.
Client-side anonymous calls are for demos
A keyless browser call can prove the interface works, but production traffic needs a server boundary for quotas, abuse controls, caching, and credential management.
ER
exchangerate.dev
Practical integration guides for developers working with currency data.

Keep reading

GuideCurrency Converter API for DevelopersRead TutorialJavaScript currency conversion with Node.jsRead ReferenceAPI errors and response fieldsRead