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
pricefield is decoded into amap[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.