Skip to main content

Go Examples

Complete Go examples for integrating OpenFXRates using the standard library and popular HTTP clients.

Installation

Using Go Modules

# Initialize your module (if not already done)
go mod init your-module-name

# Install dependencies
go get github.com/joho/godotenv

For JSON parsing, Go's standard library encoding/json is sufficient.

Example 1: Get Latest Rates

Using Go's standard net/http package:

package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
)

const (
APIKey = "your-api-key-here"
BaseURL = "https://api.openfxrates.com"
)

type LatestRatesResponse struct {
Base string `json:"base"`
Date string `json:"date"`
Rates map[string]float64 `json:"rates"`
}

func getLatestRates(base, targets string) (*LatestRatesResponse, error) {
// Build URL with query parameters
u, err := url.Parse(BaseURL + "/latest_rates")
if err != nil {
return nil, err
}

q := u.Query()
q.Set("base", base)
q.Set("targets", targets)
u.RawQuery = q.Encode()

// Create request
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}

// Set headers
req.Header.Set("X-API-Key", APIKey)
req.Header.Set("Content-Type", "application/json")

// Make request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

// Read response
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

// Check status code
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %s - %s", resp.Status, string(body))
}

// Parse JSON
var data LatestRatesResponse
if err := json.Unmarshal(body, &data); err != nil {
return nil, err
}

// Display results
fmt.Printf("Base: %s\n", data.Base)
fmt.Printf("Date: %s\n", data.Date)
fmt.Println("Rates:")
for currency, rate := range data.Rates {
fmt.Printf(" %s: %.4f\n", currency, rate)
}

return &data, nil
}

func main() {
if _, err := getLatestRates("USD", "EUR,GBP,JPY"); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

Example 2: Convert Currency

package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)

type ConversionResponse struct {
From string `json:"from"`
Amount float64 `json:"amount"`
Conversions map[string]float64 `json:"conversions"`
}

func convertCurrency(from, to string, amount float64) (*ConversionResponse, error) {
// Build URL
u, err := url.Parse(BaseURL + "/convert")
if err != nil {
return nil, err
}

q := u.Query()
q.Set("from", from)
q.Set("to", to)
q.Set("amount", fmt.Sprintf("%.2f", amount))
u.RawQuery = q.Encode()

// Create request
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("X-API-Key", APIKey)

// Make request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

// Read and parse response
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %s", string(body))
}

var data ConversionResponse
if err := json.Unmarshal(body, &data); err != nil {
return nil, err
}

// Display results
fmt.Printf("Converting: %.2f %s\n", data.Amount, data.From)
fmt.Println("Results:")
for currency, converted := range data.Conversions {
fmt.Printf(" %s: %.2f\n", currency, converted)
}

return &data, nil
}

func main() {
if _, err := convertCurrency("USD", "EUR,GBP,JPY", 100.0); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

Example 3: Get Historical Rates

package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)

type HistoricalRatesResponse struct {
Base string `json:"base"`
Date string `json:"date"`
Rates map[string]float64 `json:"rates"`
}

func getHistoricalRates(base, date, targets string) (*HistoricalRatesResponse, error) {
u, err := url.Parse(BaseURL + "/historical_rates")
if err != nil {
return nil, err
}

q := u.Query()
q.Set("base", base)
q.Set("date", date)
q.Set("targets", targets)
u.RawQuery = q.Encode()

req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("X-API-Key", APIKey)

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %s", string(body))
}

var data HistoricalRatesResponse
if err := json.Unmarshal(body, &data); err != nil {
return nil, err
}

fmt.Printf("Historical rates for %s\n", data.Date)
fmt.Printf("Base: %s\n", data.Base)
fmt.Println("Rates:")
for currency, rate := range data.Rates {
fmt.Printf(" %s: %.4f\n", currency, rate)
}

return &data, nil
}

func main() {
// Get rates from January 15, 2024
if _, err := getHistoricalRates("USD", "2024-01-15", "EUR,GBP"); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

Example 4: List All Currencies

package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
)

type Currency struct {
Code string `json:"code"`
Name string `json:"name"`
Symbol *string `json:"symbol,omitempty"`
}

type CurrenciesResponse struct {
Currencies []Currency `json:"currencies"`
}

func listCurrencies() ([]Currency, error) {
req, err := http.NewRequest("GET", BaseURL+"/currencies", nil)
if err != nil {
return nil, err
}
req.Header.Set("X-API-Key", APIKey)

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %s", string(body))
}

var data CurrenciesResponse
if err := json.Unmarshal(body, &data); err != nil {
return nil, err
}

fmt.Printf("Available currencies: %d\n\n", len(data.Currencies))

for _, currency := range data.Currencies {
symbol := ""
if currency.Symbol != nil {
symbol = fmt.Sprintf(" (%s)", *currency.Symbol)
}
fmt.Printf("%s - %s%s\n", currency.Code, currency.Name, symbol)
}

return data.Currencies, nil
}

func main() {
if _, err := listCurrencies(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

Example 5: API Client Package

Complete reusable client package:

package openfxrates

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)

type Client struct {
APIKey string
BaseURL string
HTTPClient *http.Client
}

type LatestRatesResponse struct {
Base string `json:"base"`
Date string `json:"date"`
Rates map[string]float64 `json:"rates"`
}

type ConversionResponse struct {
From string `json:"from"`
Amount float64 `json:"amount"`
Conversions map[string]float64 `json:"conversions"`
}

type HistoricalRatesResponse struct {
Base string `json:"base"`
Date string `json:"date"`
Rates map[string]float64 `json:"rates"`
}

type Currency struct {
Code string `json:"code"`
Name string `json:"name"`
Symbol *string `json:"symbol,omitempty"`
}

type CurrenciesResponse struct {
Currencies []Currency `json:"currencies"`
}

type ErrorResponse struct {
Message string `json:"message"`
Code string `json:"code,omitempty"`
}

// NewClient creates a new OpenFXRates API client
func NewClient(apiKey string) *Client {
return &Client{
APIKey: apiKey,
BaseURL: "https://api.openfxrates.com",
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}

// NewClientFromEnv creates a client using environment variables
func NewClientFromEnv() (*Client, error) {
apiKey := os.Getenv("OPENFXRATES_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("OPENFXRATES_API_KEY environment variable not set")
}
return NewClient(apiKey), nil
}

// GetLatestRates fetches the latest exchange rates
func (c *Client) GetLatestRates(base, targets string) (*LatestRatesResponse, error) {
u, err := url.Parse(c.BaseURL + "/latest_rates")
if err != nil {
return nil, err
}

q := u.Query()
q.Set("base", base)
if targets != "" {
q.Set("targets", targets)
}
u.RawQuery = q.Encode()

var response LatestRatesResponse
if err := c.makeRequest("GET", u.String(), &response); err != nil {
return nil, err
}

return &response, nil
}

// GetHistoricalRates fetches historical exchange rates
func (c *Client) GetHistoricalRates(base, date, targets string) (*HistoricalRatesResponse, error) {
u, err := url.Parse(c.BaseURL + "/historical_rates")
if err != nil {
return nil, err
}

q := u.Query()
q.Set("base", base)
q.Set("date", date)
if targets != "" {
q.Set("targets", targets)
}
u.RawQuery = q.Encode()

var response HistoricalRatesResponse
if err := c.makeRequest("GET", u.String(), &response); err != nil {
return nil, err
}

return &response, nil
}

// ConvertCurrency converts an amount from one currency to others
func (c *Client) ConvertCurrency(from, to string, amount float64) (*ConversionResponse, error) {
u, err := url.Parse(c.BaseURL + "/convert")
if err != nil {
return nil, err
}

q := u.Query()
q.Set("from", from)
q.Set("to", to)
q.Set("amount", fmt.Sprintf("%.2f", amount))
u.RawQuery = q.Encode()

var response ConversionResponse
if err := c.makeRequest("GET", u.String(), &response); err != nil {
return nil, err
}

return &response, nil
}

// ListCurrencies fetches all available currencies
func (c *Client) ListCurrencies() ([]Currency, error) {
var response CurrenciesResponse
if err := c.makeRequest("GET", c.BaseURL+"/currencies", &response); err != nil {
return nil, err
}

return response.Currencies, nil
}

// makeRequest performs an HTTP request and handles the response
func (c *Client) makeRequest(method, url string, result interface{}) error {
req, err := http.NewRequest(method, url, nil)
if err != nil {
return err
}

req.Header.Set("X-API-Key", c.APIKey)
req.Header.Set("Content-Type", "application/json")

resp, err := c.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

if resp.StatusCode != http.StatusOK {
var errResp ErrorResponse
if err := json.Unmarshal(body, &errResp); err != nil {
return fmt.Errorf("API error %d: %s", resp.StatusCode, string(body))
}
return fmt.Errorf("API error %d: %s", resp.StatusCode, errResp.Message)
}

return json.Unmarshal(body, result)
}

Using the Client Package

package main

import (
"fmt"
"log"
"os"

"your-module/openfxrates"
)

func main() {
// Create client from environment variable
client, err := openfxrates.NewClientFromEnv()
if err != nil {
log.Fatal(err)
}

// Get latest rates
latest, err := client.GetLatestRates("USD", "EUR,GBP,JPY")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Base: %s, Date: %s\n", latest.Base, latest.Date)
fmt.Printf("Rates: %v\n\n", latest.Rates)

// Convert currency
conversion, err := client.ConvertCurrency("USD", "EUR", 100.0)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Conversions: %v\n\n", conversion.Conversions)

// Get historical rates
historical, err := client.GetHistoricalRates("USD", "2024-01-15", "EUR,GBP")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Historical rates for %s: %v\n\n", historical.Date, historical.Rates)

// List currencies
currencies, err := client.ListCurrencies()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total currencies: %d\n", len(currencies))
}

Example 6: Concurrent Requests

Using goroutines for concurrent API calls:

package main

import (
"fmt"
"sync"
)

func fetchMultipleCurrencies() error {
client := NewClient(APIKey)

bases := []string{"USD", "EUR", "GBP"}
results := make(map[string]*LatestRatesResponse)
errors := make(map[string]error)

var wg sync.WaitGroup
var mu sync.Mutex

for _, base := range bases {
wg.Add(1)
go func(b string) {
defer wg.Done()

data, err := client.GetLatestRates(b, "EUR,GBP,JPY")

mu.Lock()
defer mu.Unlock()

if err != nil {
errors[b] = err
} else {
results[b] = data
}
}(base)
}

wg.Wait()

// Display results
for base, data := range results {
fmt.Printf("\n%s Rates:\n", base)
for currency, rate := range data.Rates {
fmt.Printf(" %s: %.4f\n", currency, rate)
}
}

// Display errors
for base, err := range errors {
fmt.Printf("\nError fetching %s rates: %v\n", base, err)
}

return nil
}

func main() {
if err := fetchMultipleCurrencies(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

Example 7: Error Handling with Custom Types

package main

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)

var (
ErrInvalidAPIKey = errors.New("invalid API key")
ErrRateLimitExceeded = errors.New("rate limit exceeded")
ErrNotFound = errors.New("resource not found")
)

type APIError struct {
StatusCode int
Message string
Code string
}

func (e *APIError) Error() string {
return fmt.Sprintf("API error %d: %s", e.StatusCode, e.Message)
}

func makeRequestWithErrorHandling(method, url, apiKey string, result interface{}) error {
req, err := http.NewRequest(method, url, nil)
if err != nil {
return err
}

req.Header.Set("X-API-Key", apiKey)
req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

// Handle different status codes
switch resp.StatusCode {
case http.StatusOK:
return json.Unmarshal(body, result)
case http.StatusUnauthorized:
return ErrInvalidAPIKey
case http.StatusTooManyRequests:
return ErrRateLimitExceeded
case http.StatusNotFound:
return ErrNotFound
default:
var errResp struct {
Message string `json:"message"`
Code string `json:"code"`
}
json.Unmarshal(body, &errResp)
return &APIError{
StatusCode: resp.StatusCode,
Message: errResp.Message,
Code: errResp.Code,
}
}
}

func main() {
var data LatestRatesResponse
err := makeRequestWithErrorHandling("GET",
"https://api.openfxrates.com/latest_rates?base=USD&targets=EUR",
APIKey, &data)

if err != nil {
switch {
case errors.Is(err, ErrInvalidAPIKey):
fmt.Println("Error: Invalid API key. Please check your credentials.")
case errors.Is(err, ErrRateLimitExceeded):
fmt.Println("Error: Rate limit exceeded. Please try again later.")
case errors.Is(err, ErrNotFound):
fmt.Println("Error: Resource not found.")
default:
fmt.Printf("Error: %v\n", err)
}
os.Exit(1)
}

fmt.Printf("Success: %s rates\n", data.Base)
}

Example 8: Retry Logic with Exponential Backoff

package main

import (
"fmt"
"math"
"time"
)

func retryWithBackoff(maxRetries int, fn func() error) error {
var err error

for i := 0; i < maxRetries; i++ {
err = fn()
if err == nil {
return nil
}

if i < maxRetries-1 {
waitTime := time.Duration(math.Pow(2, float64(i))) * time.Second
fmt.Printf("Request failed, retrying in %v... (attempt %d/%d)\n",
waitTime, i+1, maxRetries)
time.Sleep(waitTime)
}
}

return fmt.Errorf("max retries exceeded: %w", err)
}

func main() {
client := NewClient(APIKey)

err := retryWithBackoff(3, func() error {
_, err := client.GetLatestRates("USD", "EUR,GBP,JPY")
return err
})

if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}

fmt.Println("Success!")
}

Best Practices

1. Environment Variables

Use the godotenv package to manage environment variables:

package main

import (
"log"
"os"

"github.com/joho/godotenv"
)

func init() {
// Load .env file
if err := godotenv.Load(); err != nil {
log.Println("No .env file found")
}
}

func main() {
apiKey := os.Getenv("OPENFXRATES_API_KEY")
if apiKey == "" {
log.Fatal("OPENFXRATES_API_KEY not set")
}

client := NewClient(apiKey)
// Use client...
}

Create a .env file:

OPENFXRATES_API_KEY=your-api-key-here

2. Context for Cancellation

Use context for request cancellation and timeouts:

import (
"context"
"net/http"
"time"
)

func (c *Client) GetLatestRatesWithContext(ctx context.Context, base, targets string) (*LatestRatesResponse, error) {
req, err := http.NewRequestWithContext(ctx, "GET", c.BaseURL+"/latest_rates", nil)
if err != nil {
return nil, err
}

// Set headers and query params...

resp, err := c.HTTPClient.Do(req)
// Handle response...
}

// Usage with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

data, err := client.GetLatestRatesWithContext(ctx, "USD", "EUR")

3. Connection Pooling

Configure HTTP client for connection pooling:

import (
"net"
"net/http"
"time"
)

func NewOptimizedClient(apiKey string) *Client {
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
}

return &Client{
APIKey: apiKey,
BaseURL: "https://api.openfxrates.com",
HTTPClient: &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
},
}
}

4. Response Caching

Simple in-memory cache implementation:

import (
"sync"
"time"
)

type CachedClient struct {
client *Client
cache map[string]cacheEntry
mu sync.RWMutex
ttl time.Duration
}

type cacheEntry struct {
data interface{}
timestamp time.Time
}

func NewCachedClient(apiKey string, ttl time.Duration) *CachedClient {
return &CachedClient{
client: NewClient(apiKey),
cache: make(map[string]cacheEntry),
ttl: ttl,
}
}

func (c *CachedClient) GetLatestRatesCached(base, targets string) (*LatestRatesResponse, error) {
cacheKey := fmt.Sprintf("%s:%s", base, targets)

// Check cache
c.mu.RLock()
if entry, found := c.cache[cacheKey]; found {
if time.Since(entry.timestamp) < c.ttl {
c.mu.RUnlock()
return entry.data.(*LatestRatesResponse), nil
}
}
c.mu.RUnlock()

// Fetch fresh data
data, err := c.client.GetLatestRates(base, targets)
if err != nil {
return nil, err
}

// Update cache
c.mu.Lock()
c.cache[cacheKey] = cacheEntry{
data: data,
timestamp: time.Now(),
}
c.mu.Unlock()

return data, nil
}