Torna al Blog

Come Costruire un Convertitore di Valute con Vue.js e un'API di Tassi di Cambio Live (Guida 2026)

V
Vlado Grigirov
April 28, 2026
Currency API Vue.js Tutorial Exchange Rates JavaScript Finexly Composition API

Se ti serve un convertitore di valute Vue che recuperi i tassi di cambio in tempo reale da un'API live, questo tutorial ti accompagna dall'inizio alla fine. Costruirai un componente Vue 3 pronto per la produzione con la Composition API, TypeScript e un'API gratuita di tassi di cambio — con debouncing adeguato, caching, gestione degli errori e pattern compatibili con SSR per Nuxt.

Alla fine avrai un componente <CurrencyConverter /> riutilizzabile, un composable useCurrencyRates da inserire in qualunque progetto Vue 3 e una comprensione chiara dei compromessi che contano quando si porta la conversione di valute davanti a utenti reali.

Cosa costruirai

Un convertitore di valute Vue 3 con queste funzionalità:

  • Tassi di cambio live per 170+ valute tramite l'API Finexly
  • Conversione reattiva che si aggiorna mentre l'utente digita
  • Pulsante di scambio valute (USD → EUR diventa EUR → USD con un clic)
  • Chiamate API con debounce per non ingolfare la rete
  • Cache in memoria per mantenere bassa la latenza e rispettare le quote del piano gratuito
  • Gestione degli errori, stati di caricamento e tipi TypeScript puliti
  • Compatibile con SSR per funzionare in Nuxt 3 senza mismatch di idratazione

Il componente finale è di circa 80 righe. Il composable, altre 60. Tutto qui.

Prerequisiti

Dovresti essere a tuo agio con:

  • Sintassi Vue 3 (la forma <script setup>)
  • TypeScript di base
  • Chiamate API REST con fetch

Ti servirà anche una chiave API Finexly. Prendine una dalla dashboard — ci vogliono circa 30 secondi e il piano gratuito offre 1.000 richieste al mese senza carta di credito. Se non hai mai usato il servizio, la documentazione dell'API Finexly ha un quickstart di 5 minuti.

Passo 1: Avviare un progetto Vue 3 con Vite

Se parti da zero:

npm create vite@latest finexly-converter -- --template vue-ts
cd finexly-converter
npm install
npm run dev

Vite ti dà Vue 3, TypeScript e hot module reload pronti. Apri src/App.vue e svuota il boilerplate — lo sostituiremo con il convertitore a breve.

Se stai integrando in un progetto Nuxt 3 esistente, puoi saltare questo passo. Il composable qui sotto funziona allo stesso modo in Nuxt perché usa ref e computed standard di vue.

Passo 2: Conservare la chiave API in sicurezza

Non incollare mai la chiave API Finexly direttamente in un componente. Mettila in .env.local:

VITE_FINEXLY_API_KEY=la_tua_chiave_qui

Vite espone al client qualunque variabile prefissata VITE_. Per Nuxt usa NUXT_PUBLIC_FINEXLY_API_KEY e leggila da useRuntimeConfig().public.finexlyApiKey.

Se temi di esporre la chiave nel bundle client, instrada la richiesta tramite una route backend o una funzione serverless. Mostriamo questo pattern al Passo 7.

Passo 3: Costruire il composable useCurrencyRates

Il composable è il cuore del convertitore. Si occupa di fetch, cache e stato di caricamento/errore — così il componente resta presentazionale.

Crea src/composables/useCurrencyRates.ts:

import { ref, type Ref } from 'vue'

const API_KEY = import.meta.env.VITE_FINEXLY_API_KEY
const BASE_URL = 'https://api.finexly.com/v1'

// Cache a livello di modulo: condivisa tra tutti i componenti che chiamano questo composable.
// Ogni voce vive 5 minuti — abbastanza da sembrare istantaneo, abbastanza poco
// da restare preciso durante movimenti FX volatili.
const cache = new Map<string, { rates: Record<string, number>; expires: number }>()
const TTL_MS = 5 * 60 * 1000

interface RatesResponse {
  base: string
  date: string
  rates: Record<string, number>
}

export function useCurrencyRates() {
  const rates: Ref<Record<string, number>> = ref({})
  const loading = ref(false)
  const error = ref<string | null>(null)

  async function fetchRates(base: string) {
    const cached = cache.get(base)
    if (cached && cached.expires > Date.now()) {
      rates.value = cached.rates
      return
    }

    loading.value = true
    error.value = null
    try {
      const url = `${BASE_URL}/latest?base=${base}&apikey=${API_KEY}`
      const res = await fetch(url)
      if (!res.ok) throw new Error(`Finexly returned ${res.status}`)
      const data: RatesResponse = await res.json()

      rates.value = data.rates
      cache.set(base, { rates: data.rates, expires: Date.now() + TTL_MS })
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Failed to load rates'
    } finally {
      loading.value = false
    }
  }

  function convert(amount: number, from: string, to: string): number {
    if (from === to) return amount
    const fromRate = rates.value[from]
    const toRate = rates.value[to]
    if (!fromRate || !toRate) return 0
    return (amount / fromRate) * toRate
  }

  return { rates, loading, error, fetchRates, convert }
}

Alcuni dettagli da segnalare:

  • La cache vive a livello di modulo di proposito. Due componenti che chiamano il composable condividono la stessa cache, quindi cambiare rotta non rifa il fetch della stessa valuta base.
  • rates è tipizzato come Record<string, number> per poterlo passare direttamente a un <select> più avanti.
  • La funzione convert fa matematica di tasso incrociato, quindi non devi rifare il fetch ogni volta che cambia la valuta "from". Più dettagli al Passo 5.

Passo 4: Il componente convertitore

Crea src/components/CurrencyConverter.vue:

<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { useCurrencyRates } from '@/composables/useCurrencyRates'

const { rates, loading, error, fetchRates, convert } = useCurrencyRates()

const amount = ref(100)
const from = ref('USD')
const to = ref('EUR')

const converted = computed(() =>
  convert(amount.value, from.value, to.value).toFixed(2)
)

const currencies = computed(() => Object.keys(rates.value).sort())

function swap() {
  ;[from.value, to.value] = [to.value, from.value]
}

let timeout: ReturnType<typeof setTimeout> | null = null
watch(from, (newBase) => {
  if (timeout) clearTimeout(timeout)
  timeout = setTimeout(() => fetchRates(newBase), 300)
})

onMounted(() => fetchRates(from.value))
</script>

<template>
  <div class="converter">
    <h2>Currency Converter</h2>

    <div class="row">
      <input v-model.number="amount" type="number" min="0" />
      <select v-model="from">
        <option v-for="c in currencies" :key="c" :value="c">{{ c }}</option>
      </select>
    </div>

    <button @click="swap" aria-label="Swap currencies">⇅</button>

    <div class="row">
      <output>{{ loading ? '...' : converted }}</output>
      <select v-model="to">
        <option v-for="c in currencies" :key="c" :value="c">{{ c }}</option>
      </select>
    </div>

    <p v-if="error" class="error">{{ error }}</p>
  </div>
</template>

Inserisci <CurrencyConverter /> in App.vue e hai un convertitore funzionante. La lista delle valute si popola appena arriva il primo fetch. Digitare nel campo importo aggiorna il risultato in modo reattivo senza toccare la rete. Lo scambio riordina le tendine. Cambiare la valuta "from" innesca un refetch con debounce.

Passo 5: Conversione a tasso incrociato (per non bruciare richieste)

Se converti solo da una base, puoi saltare questa sezione. Ma la maggior parte dei convertitori lascia all'utente scegliere entrambi i lati — e un'implementazione ingenua rifa il fetch a ogni cambio del "from".

La matematica del tasso incrociato risolve il problema. Se hai USD come base e i tassi di EUR e JPY rispetto a USD, il tasso EUR → JPY è semplicemente (1 / EUR_rate) * JPY_rate. È esattamente ciò che fa la funzione convert del Passo 3:

return (amount / fromRate) * toRate

Questo significa che ti basta un solo fetch per sessione. Grande vantaggio per le quote del piano gratuito. Il watcher su from diventa una misura difensiva — se l'utente sceglie una valuta esotica non presente nella tabella in cache, rifare il fetch con quella base garantisce che la conversione continui a funzionare.

Passo 6: Skeleton di caricamento e stati di errore

Gli spinner vanno bene, ma uno shift di layout durante il caricamento dei tassi è fastidioso. Renderizza opzioni placeholder al primo fetch:

<select v-model="from" :disabled="loading && currencies.length === 0">
  <option v-if="currencies.length === 0">Loading...</option>
  <option v-for="c in currencies" :key="c" :value="c">{{ c }}</option>
</select>

Per il percorso di errore, mostra un pulsante riprova invece di un messaggio cieco:

<div v-if="error" class="error-box">
  <p>{{ error }}</p>
  <button @click="fetchRates(from)">Retry</button>
</div>

Tratta 429 (rate limited) e 5xx separatamente se vuoi essere educato. Con le 1.000 richieste mensili gratuite di Finexly e la cache di 5 minuti, serve un picco vero per toccare il limite — ma un percorso di retry pulito rende la UI solida.

Passo 7: Nascondere la chiave API con un proxy server

Tutto ciò che è in import.meta.env.VITE_* finisce nel bundle client. Per la maggior parte dei widget di valute in sola lettura è accettabile — il caso peggiore è qualcuno che fa scraping dei tassi con la tua chiave. Se vuoi una difesa in profondità, instrada la richiesta lato server.

Con una server route Nuxt 3:

// server/api/rates.get.ts
export default defineEventHandler(async (event) => {
  const { base } = getQuery(event)
  const config = useRuntimeConfig()
  return await $fetch(
    `https://api.finexly.com/v1/latest?base=${base}&apikey=${config.finexlyApiKey}`
  )
})

Poi punta il composable a /api/rates?base=${base} invece dell'origin Finexly. La chiave non lascia mai il server. Lo stesso pattern funziona in qualunque meta-framework Vue — Nuxt, Quasar o un semplice backend Express.

Passo 8: Checklist di produzione

Prima di lanciare:

  • Caching aggressivo. I tassi FX non cambiano ogni secondo. TTL di 5 minuti sul client e 1 minuto sul server bastano e avanzano per quasi ogni caso di visualizzazione. Vedi la nostra guida a caching e gestione errori per pattern testati in produzione.
  • Arrotonda correttamente. La visualizzazione delle valute si arrotonda all'unità minore della valuta (2 decimali per USD, 0 per JPY, 3 per KWD). Usa Intl.NumberFormat(locale, { style: 'currency', currency: to.value }) invece di .toFixed(2).
  • Formatta con Intl. new Intl.NumberFormat('en-US', { style: 'currency', currency: 'EUR' }).format(123.45) ti dà "€123.45" e rispetta il locale dell'utente.
  • Fetch SSR-safe. In Nuxt preferisci useFetch a un fetch grezzo dentro onMounted per avere i tassi disponibili durante il rendering server e non scatenare mismatch di idratazione.
  • Monitora la quota. Aggiungi un piccolo logger che avvisa quando hai bruciato l'80% del budget mensile di richieste. Per volumi più alti, i piani tariffari iniziano dove finisce il piano gratuito.

Variante Vue 2 (Options API)

Se sei su Vue 2, la stessa logica si traduce quasi riga per riga:

export default {
  data() {
    return { rates: {}, loading: false, error: null, amount: 100, from: 'USD', to: 'EUR' }
  },
  computed: {
    converted() {
      const fr = this.rates[this.from]
      const tr = this.rates[this.to]
      return fr && tr ? ((this.amount / fr) * tr).toFixed(2) : '0.00'
    },
    currencies() {
      return Object.keys(this.rates).sort()
    },
  },
  mounted() { this.fetchRates(this.from) },
  watch: {
    from(newBase) { this.fetchRates(newBase) },
  },
  methods: {
    async fetchRates(base) {
      this.loading = true
      try {
        const res = await fetch(
          `https://api.finexly.com/v1/latest?base=${base}&apikey=${process.env.VUE_APP_FINEXLY_KEY}`
        )
        const data = await res.json()
        this.rates = data.rates
      } catch (e) {
        this.error = e.message
      } finally {
        this.loading = false
      }
    },
  },
}

La Composition API è più pulita per condividere logica tra componenti, ma l'Options API funziona bene per un singolo widget convertitore. Se il tuo team preferisce un approccio più funzionale, la guida all'integrazione con JavaScript copre pattern indipendenti dal framework.

Trabocchetti comuni (e come evitarli)

Mismatch di idratazione in Nuxt. Chiamare fetch in onMounted funziona client-side ma rompe la consistenza SSR. Usa useFetch o useAsyncData.

Tassi obsoleti dopo una sessione lunga. Il TTL di 5 minuti sopra significa che una scheda aperta tutto il giorno mostra i tassi dell'ultima ora. Aggiorna su visibilitychange:

document.addEventListener('visibilitychange', () => {
  if (!document.hidden) fetchRates(from.value)
})

Drift in virgola mobile. I numeri in JavaScript sono double. 0.1 + 0.2 !== 0.3. Per importi monetari, moltiplica in centesimi interi (amount * 100), fai i conti, poi dividi. Oppure usa una libreria come dinero.js per qualunque cosa di checkout.

Errori CORS in sviluppo. Alcune API di valute non permettono chiamate dirette dal browser. Finexly consente origin browser per uso client-side; il proxy del Passo 7 risolve gli altri casi.

Perché Finexly per progetti Vue

Alcune cose contano scegliendo un'API di tassi di cambio per un'app frontend: tempo di risposta, accuratezza, generosità del piano gratuito e JSON pulito. Finexly punta a tutti e quattro — latenza p95 sotto 50ms, tassi mid-market aggiornati ogni 60 secondi, 170+ valute e un formato JSON che cade direttamente in una ref di Vue senza ritocchi.

Per vedere come si confronta con le alternative, la nostra comparazione di API per valute entra nel dettaglio dei trade-off. Oppure usa la pagina di confronto fianco a fianco.

Domande frequenti

Posso usare questo tutorial con Vue 2?

Sì. Il pattern composable è solo Vue 3, ma la logica sottostante — fare fetch dei tassi, salvarli in data, calcolare la conversione — funziona identica in Options API. L'esempio Vue 2 sopra è un sostituto diretto.

L'API Finexly è gratuita per progetti Vue?

Il piano gratuito offre 1.000 richieste al mese, più che sufficienti per un side project, un pezzo di portfolio o un piccolo SaaS. Con cache di 5 minuti, regge circa 200 utenti attivi giornalieri. Vedi i piani tariffari per volumi maggiori.

Come evito di esporre la chiave API nel bundle client?

Instrada la richiesta tramite una server route come al Passo 7. I prefissi VITE_* e NUXT_PUBLIC_* rendono entrambi le variabili visibili al client. Tutto ciò che è sensibile dovrebbe stare dietro una funzione server.

Quanto sono accurati i tassi?

Finexly aggrega tassi mid-market da diversi liquidity provider Tier-1 e li aggiorna ogni 60 secondi. Sufficientemente accurati per visualizzazione e la maggior parte delle applicazioni di pricing. Per esecuzione di trade vorrai un feed in streaming — vedi la nostra guida REST vs WebSocket.

Posso convertire importi storici?

Sì — l'endpoint /historical di Finexly accetta un parametro data e restituisce i tassi di qualunque giorno lavorativo. Il pattern è identico all'endpoint /latest sopra; basta cambiare l'URL. La guida all'API dei tassi storici lo copre nel dettaglio.

Conclusione

Ora hai un convertitore di valute Vue 3 che gestisce le preoccupazioni del mondo reale — caching, debouncing, stati di errore, SSR e sicurezza della chiave API. Il pattern composable significa che puoi inserire lo stesso useCurrencyRates in un widget di navbar, una pagina di checkout o un grafico di dashboard senza riscrivere la logica di fetch.

Pronto a provarlo nel tuo progetto? Ottieni la tua chiave API Finexly gratuita — senza carta di credito. Inizia con 1.000 richieste gratuite al mese e fai upgrade solo quando il traffico cresce oltre il piano gratuito.

Vlado Grigirov

Senior Currency Markets Analyst & Financial Strategist

Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...

View full profile →

Condividi questo articolo