Integration guide
Add currency exchange rates and conversion to Go
Use net/http and encoding/json with struct tags. Cache latest responses until nextUpdateAt.
1. Get a free API key
Sign up at https://currencyexchangerate-api.com/auth/login, then create a key under API keys in the console. The free plan includes 3,000 requests per month with no credit card. Store the secret as CXR_API_KEY in server-side environment configuration; never expose it in browser code.
.env
shellCXR_API_KEY=cxr_your_secret_key2. Create a reusable client and cache rates
Use the standard HTTP client. Cache every /latest response until nextUpdateAt: rates change at most once by then, and reusing the response saves quota. The helper also exposes /convert.
rates/rates.go
Gopackage rates import ( "encoding/json" "fmt" "net/http" "net/url" "os" "sync" "time") const apiBase = "https://api.currencyexchangerate-api.com/api/v1" type Latest struct { Base string `json:"base"` UpdatedAt time.Time `json:"updatedAt"` NextUpdateAt time.Time `json:"nextUpdateAt"` Rates map[string]float64 `json:"rates"`} type Conversion struct { From string `json:"from"` To string `json:"to"` Amount float64 `json:"amount"` Rate float64 `json:"rate"` Result float64 `json:"result"` UpdatedAt time.Time `json:"updatedAt"`} type APIError struct { Status int RetryAfter string} func (e *APIError) Error() string { return fmt.Sprintf("rates API status %d (Retry-After %q)", e.Status, e.RetryAfter)} var ( mu sync.Mutex cached = map[string]*Latest{}) func request(path string, out any) error { req, err := http.NewRequest("GET", apiBase+path, nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+os.Getenv("CXR_API_KEY")) res, err := http.DefaultClient.Do(req) if err != nil { return err } defer res.Body.Close() if res.StatusCode >= 400 { return &APIError{res.StatusCode, res.Header.Get("Retry-After")} } return json.NewDecoder(res.Body).Decode(out)} func LatestRates(base, symbols string) (*Latest, error) { id := base + ":" + symbols mu.Lock() hit := cached[id] mu.Unlock() if hit != nil && time.Now().Before(hit.NextUpdateAt) { return hit, nil } q := url.Values{"base": {base}, "symbols": {symbols}} data := &Latest{} if err := request("/latest?"+q.Encode(), data); err != nil { return nil, err } mu.Lock() cached[id] = data mu.Unlock() return data, nil} func Convert(from, to string, amount float64) (Conversion, error) { q := url.Values{ "from": {from}, "to": {to}, "amount": {fmt.Sprint(amount)}, } var data Conversion err := request("/convert?"+q.Encode(), &data) return data, err}3. Use it in Go
Call the helper from a server route, controller, view, command, or script. Format converted values for the user’s locale.
main.go
Gopackage main import ( "fmt" "log" "example.com/app/rates") func main() { result, err := rates.Convert("EUR", "GBP", 100) if err != nil { log.Fatal(err) } fmt.Printf("%s %.2f\n", result.To, result.Result)}4. Handle API errors
A 401 means the key is invalid. A 429 can mean a rate limit or quota limit; read Retry-After in seconds before retrying. Other errors use application/problem+json.
errors.go
Gopackage main import ( "errors" "log" "example.com/app/rates") func main() { _, err := rates.Convert("EUR", "GBP", 100) var apiErr *rates.APIError if errors.As(err, &apiErr) { switch apiErr.Status { case 401: log.Fatal("check CXR_API_KEY") case 429: log.Fatalf("retry after %s seconds", apiErr.RetryAfter) } } if err != nil { log.Fatal(err) }}5. Format the amount for display
Use locale-aware currency formatting instead of adding a symbol manually.
format.go
Gopackage main import ( "golang.org/x/text/language" "golang.org/x/text/message") func format(code string, value float64) string { p := message.NewPrinter(language.BritishEnglish) return p.Sprintf("%s %.2f", code, value)}