In this simple Go tutorial we will learn how to get live FX rates from a REST API in seconds. It’s free to sign up and you can start in seconds.

1) Prerequisites

  • Go 1.20+ installed
  • An API key from fxmarketapi.com
  • Set your API key as an environment variable (recommended):
# macOS/Linux
export FXMARKETAPI_KEY="YOUR_API_KEY"

# Windows (Powershell)
$env:FXMARKETAPI_KEY="YOUR_API_KEY"

2) Project Setup

Create a folder and a basic module:

mkdir fxrates && cd fxrates
go mod init example.com/fxrates

3) Code (main.go)

Create main.go with the following code:

package main

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

// Response mirrors the JSON example provided by fxmarketapi.com.
type Response struct {
 Timestamp int64              `json:"timestamp"`
 Price     map[string]float64 `json:"price"`
}

func main() {
     apiKey := os.Getenv("FXMARKETAPI_KEY")
     if apiKey == "" {
      fmt.Println("FXMARKETAPI_KEY env var is not set")
      os.Exit(1)
     }

     // Build the URL: https://fxmarketapi.com/apilive?api_key=YOUR_KEY
     u, err := url.Parse("https://fxmarketapi.com/apilive")
     if err != nil {
      fmt.Println("invalid base URL:", err)
      os.Exit(1)
     }
     q := u.Query()
     q.Set("api_key", apiKey)
     u.RawQuery = q.Encode()

     // Prepare HTTP client with sane timeouts.
     client := &http.Client{
      Timeout: 10 * time.Second,
     }

     // Use context with timeout for extra safety.
     ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
     defer cancel()

     req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
     if err != nil {
      fmt.Println("build request:", err)
      os.Exit(1)
     }

     // Optional: identify your app
     req.Header.Set("User-Agent", "fxrates-go-example/1.0")

     resp, err := client.Do(req)
     if err != nil {
      fmt.Println("request error:", err)
      os.Exit(1)
     }
     defer resp.Body.Close()

     if resp.StatusCode < 200 || resp.StatusCode >= 300 {
      body, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
      fmt.Printf("non-2xx status (%d): %s\n", resp.StatusCode, string(body))
      os.Exit(1)
     }

     var data Response
     if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
      fmt.Println("decode JSON:", err)
      os.Exit(1)
     }

     // Basic validation
     if len(data.Price) == 0 {
      fmt.Println("no prices in response")
      os.Exit(1)
     }

     // Convert unix seconds to time
     ts := time.Unix(data.Timestamp, 0).UTC()

     fmt.Printf("Timestamp: %d (%s)\n", data.Timestamp, ts.Format(time.RFC3339))
     fmt.Println("Prices:")
     for pair, px := range data.Price {
      fmt.Printf("  %s: %.6f\n", pair, px)
     }

     // Example: read a specific pair if present
     want := "EURUSD"
     if v, ok := data.Price[want]; ok {
      fmt.Printf("\n%s mid-price: %.6f\n", want, v)
     } else {
      fmt.Println("\nEURUSD not present in response.")
     }

     // Optional: basic sanity check
     if err := checkPrices(data.Price); err != nil {
      fmt.Println("warning:", err)
     }
}

func checkPrices(m map[string]float64) error {
     // Simple numeric sanity check: non-zero, positive prices
     for k, v := range m {
      if v <= 0 {
       return errors.New("received non-positive price for " + k)
      }
     }
     return nil
}

Run it:

go run main.go

Example output:

Timestamp: 1541012848 (2018-10-31T11:27:28Z)
Prices:
  EURUSD: 1.132300
  GBPUSD: 1.277600
  USDJPY: 112.985500

EURUSD mid-price: 1.132300

4) Notes & Tips

  • Security: keep your API key in an environment variable or a secret manager; don’t hardcode it.
  • Timeouts: the example sets both a client timeout and a context timeout for robustness.
  • JSON shape: the price field is decoded into a map[string]float64, matching the sample. If the provider adds more fields later, your decoder will safely ignore them.
  • Error handling: the sample checks HTTP status codes and guards against empty or invalid data.