Back to Documentation
Vue.js Integration Guide
Learn how to integrate FXRateSync API with Vue.js using the Composition API and best practices.
Composable for Currency Operations
Create a reusable Vue composable for currency conversion:
// composables/useCurrencyConverter.js
import { ref, reactive } from 'vue'
export function useCurrencyConverter() {
const loading = ref(false)
const error = ref(null)
const result = ref(null)
const convert = async (from, to, amount) => {
loading.value = true
error.value = null
result.value = null
try {
const response = await fetch(
`https://api.fxratesync.io/v1/convert?from=${from}&to=${to}&amount=${amount}`,
{
headers: {
'X-API-Key': process.env.NEXT_PUBLIC_FX_API_KEY
}
}
)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
result.value = data
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
const reset = () => {
result.value = null
error.value = null
}
return {
loading: readonly(loading),
error: readonly(error),
result: readonly(result),
convert,
reset
}
}Vue Component Example
<!-- CurrencyConverter.vue -->
<template>
<div class="max-w-md mx-auto p-6 bg-background rounded-xl shadow-lg">
<h2 class="text-2xl font-bold text-center mb-6">Currency Converter</h2>
<form @submit.prevent="handleSubmit" class="space-y-4">
<!-- Amount Input -->
<div>
<label for="amount" class="block text-sm font-medium text-foreground mb-2">
Amount
</label>
<input
id="amount"
v-model.number="form.amount"
type="number"
min="0"
step="0.01"
class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
placeholder="Enter amount"
required
/>
</div>
<!-- Currency Selectors -->
<div class="grid grid-cols-2 gap-4">
<div>
<label for="from" class="block text-sm font-medium text-foreground mb-2">
From
</label>
<select
id="from"
v-model="form.from"
class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
>
<option v-for="currency in currencies" :key="currency" :value="currency">
{{ currency }}
</option>
</select>
</div>
<div>
<label for="to" class="block text-sm font-medium text-foreground mb-2">
To
</label>
<select
id="to"
v-model="form.to"
class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
>
<option v-for="currency in currencies" :key="currency" :value="currency">
{{ currency }}
</option>
</select>
</div>
</div>
<!-- Swap Button -->
<div class="text-center">
<button
type="button"
@click="swapCurrencies"
class="px-4 py-2 text-primary hover:bg-primary/10 rounded-lg transition-colors"
>
⇄ Swap Currencies
</button>
</div>
<!-- Submit Button -->
<button
type="submit"
:disabled="loading"
class="w-full bg-primary hover:bg-primary/90 disabled:opacity-50 text-background font-medium py-3 px-6 rounded-lg transition-colors"
>
{{ loading ? 'Converting...' : 'Convert Currency' }}
</button>
</form>
<!-- Results -->
<div v-if="result" class="mt-6 p-4 bg-success/10 border border-success/30 rounded-lg">
<div class="text-center">
<div class="text-2xl font-bold text-success-foreground mb-1">
{{ formatCurrency(result.converted_amount, result.to) }}
</div>
<div class="text-sm text-success">
{{ result.amount }} {{ result.from }} =
{{ result.converted_amount.toFixed(2) }} {{ result.to }}
</div>
<div class="text-xs text-success mt-2">
Rate: {{ result.rate.toFixed(4) }} •
{{ new Date(result.timestamp).toLocaleString() }}
</div>
</div>
</div>
<!-- Error Display -->
<div v-if="error" class="mt-6 p-4 bg-destructive/10 border border-destructive/30 rounded-lg">
<div class="text-destructive-foreground text-center">
{{ error }}
</div>
</div>
</div>
</template>
<script setup>
import { reactive } from 'vue'
import { useCurrencyConverter } from '@/composables/useCurrencyConverter'
const { loading, error, result, convert, reset } = useCurrencyConverter()
const currencies = ['USD', 'EUR', 'GBP', 'JPY', 'CAD', 'AUD', 'CHF', 'CNY']
const form = reactive({
amount: 100,
from: 'USD',
to: 'EUR'
})
const handleSubmit = async () => {
if (form.amount <= 0) {
return
}
await convert(form.from, form.to, form.amount)
}
const swapCurrencies = () => {
const temp = form.from
form.from = form.to
form.to = temp
reset()
}
const formatCurrency = (amount, currency) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(amount)
}
</script>Pinia Store for State Management
// stores/currency.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCurrencyStore = defineStore('currency', () => {
// State
const rates = ref({})
const lastUpdated = ref(null)
const baseCurrency = ref('USD')
const loading = ref(false)
const error = ref(null)
// Getters
const isDataFresh = computed(() => {
if (!lastUpdated.value) return false
const fiveMinutesAgo = Date.now() - 5 * 60 * 1000
return new Date(lastUpdated.value).getTime() > fiveMinutesAgo
})
const getRate = computed(() => {
return (from, to) => {
if (from === to) return 1
// Direct rate available
if (rates.value[to]) {
return rates.value[to]
}
// Calculate cross rate
const fromRate = rates.value[from]
const toRate = rates.value[to]
if (fromRate && toRate) {
return toRate / fromRate
}
return null
}
})
// Actions
const fetchRates = async (base = 'USD') => {
loading.value = true
error.value = null
try {
const response = await fetch(
`https://api.fxratesync.io/v1/rates/${base}`,
{
headers: {
'X-API-Key': process.env.NEXT_PUBLIC_FX_API_KEY
}
}
)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
rates.value = data.rates
lastUpdated.value = data.timestamp
baseCurrency.value = base
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
const convertAmount = (amount, from, to) => {
const rate = getRate.value(from, to)
return rate ? amount * rate : null
}
return {
// State
rates,
lastUpdated,
baseCurrency,
loading,
error,
// Getters
isDataFresh,
getRate,
// Actions
fetchRates,
convertAmount
}
})Best Practices
- • Use environment variables for API keys (NEXT_PUBLIC_FX_API_KEY)
- • Implement proper error boundaries
- • Cache data in Pinia for better performance
- • Use Vue's reactivity system effectively
- • Consider using vue-query for advanced data fetching
- • Implement proper TypeScript types for better DX
Environment Setup
.env File
NEXT_PUBLIC_FX_API_KEY=your_api_key_here