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

shell
CXR_API_KEY=cxr_your_secret_key

2. 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

Go
package 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

Go
package 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

Go
package 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

Go
package 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)}