DEV Community

Cover image for Currency Converter In Go
Jordan Tingling
Jordan Tingling

Posted on

Currency Converter In Go

Hey Guys!

Yet another small mini-project for making a currency converter in Go!

It's a simple but fun project that should take someone about an hour or 2 depending on their experience. It takes a currency type from one end, the currency which we intend to convert to and the amount to be converted.

I'm making use of a third-party service (https://openexchangerates.org) to retrieve the latest currency data.

My Main base currencies are:

1) USD
2) EUR
3) GBP
4) JPY
Enter fullscreen mode Exit fullscreen mode

and I also have support for "other" currencies through input in the TUI. Both for base currencies and currencies to be converted to.

~ Source Code: Found here


Let's Begin

What is your base currency?

                              List             

$ USD               
United States Dollar

£ GBP               
British Pound

 EUR       
Euro        

¥ JPY       
Japanese Yen       


  •••          
Enter fullscreen mode Exit fullscreen mode

The main functionalities of the application are:

  1. Get Conversion details entered by the user
  2. Use those details and send an API request to Openxchangerates (Third-Party service with the latest currency conversion rates)
  3. Convert currencies & amount
  4. Output currencies & amount data to the user

The packages used:

  1. net/http - for http requests to the currency exchange api
  2. github.com/charmbracelet/huh - for the TUI interface form
  3. github.com/charmbracelet/bubbles/list - feature-rich for browsing a general-purpose list of items
  4. encoding/json - in order to marshal the data for the API
  5. github.com/charmbracelet/lipgloss - Style definitions for terminal layouts

How does it work?

So let's discuss the first bit of functionality, which is getting conversion details from the user.

A view method for getting this user-provided data had to be made and it asks the user questions on what currency to convert, which currency to be converted to, and more.

func (m model) View() string {
    if m.err != nil {
        return fmt.Sprintf("Error: %v\n\nPress any key to continue.\n", m.err)
    }

    if m.finished {
        // Return an empty string when finished to avoid redundant output.
        return ""
    }

    switch m.stage {
    case 0:
        if m.isCustomInput {
            return questionStyle.Render("Enter your custom base currency code (e.g., USD):\n\n") + m.textInput.View()
        }
        return questionStyle.Render("What is your base currency?\n\n") + m.list.View()
    case 1:
        if m.isCustomInput {
            return questionStyle.Render("Enter your custom target currency code (e.g., EUR):\n\n") + m.textInput.View()
        }
        return questionStyle.Render("What do you want to convert to?\n\n") + m.list.View()
    case 2:
        return questionStyle.Render("How much to convert?\n\n") + m.textInput.View()
    default:
        return ""
    }
}
Enter fullscreen mode Exit fullscreen mode
What do you want to convert to?

                                  List             

$ USD               
United States Dollar

£ GBP               
British Pound       


  •••        
Enter fullscreen mode Exit fullscreen mode
How much to convert?

                    > 200 
Enter fullscreen mode Exit fullscreen mode

Now let's discuss the second point, using the currency conversion details and sending an API request to Openxchangerates.

Here I'm getting/fetching for the latest currency rates from Openxchangerates.org via an API key provided by the third-party currency exchange platform. In my case, I made use of .env's for secret management but there are a multitude of other ways to better handle this, especially if it were a production app.

~ Openxchangerates.org docs used for this: located here

//api.go

package api

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

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

func FetchRates(apiKey string) (CurrencyData, error) {
    url := fmt.Sprintf("https://openexchangerates.org/api/latest.json?app_id=%s&prettyprint=false", apiKey)
    resp, err := http.Get(url)
    if err != nil {
        return CurrencyData{}, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        return CurrencyData{}, fmt.Errorf("API request failed with status: %s", resp.Status)
    }

    var data CurrencyData
    err = json.NewDecoder(resp.Body).Decode(&data)
    if err != nil {
        return CurrencyData{}, err
    }

    return data, nil
}
Enter fullscreen mode Exit fullscreen mode

Then we'll proceed with converting the currencies and amounts:

//conversion.go

package conversion

func Convert(amount float64, rateFrom, rateTo float64) float64 {
    return amount * (rateTo / rateFrom)
}
Enter fullscreen mode Exit fullscreen mode

which is simply taking in a base amount (amount), a rate from the base currency and the rate to be converted to and returns a final converted amount.


Lastly, providing the converted currencies and amount-related data back to the user.

Controlling the logic behind these selections is an update function that serves as the main state transition handler for the application's model.

func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    var cmd tea.Cmd

    switch msg := msg.(type) {
    case tea.KeyMsg:
        key := msg.String()
        switch key {
        case "enter":
            if m.isCustomInput {
                // Handle custom currency input
                input := strings.ToUpper(strings.TrimSpace(m.textInput.Value()))
                if !isAlphabetic(input) || len(input) != 3 {
                    m.err = fmt.Errorf("invalid currency code: must be 3 letters")
                    return m, nil
                }
                if m.stage == 0 {
                    m.currencyFrom = input
                    m.isCustomInput = false
                    m.textInput.Reset()
                    m.stage++
                    m.list.ResetSelected()
                    return m, nil
                } else if m.stage == 1 {
                    m.currencyTo = input
                    m.isCustomInput = false
                    m.textInput.Reset()
                    m.stage++
                    m.textInput.Placeholder = "Enter amount (e.g., 100)"
                    m.textInput.Focus()
                    return m, nil
                }
            } else if m.stage == 2 {
                // Handle amount input
                amountStr := strings.TrimSpace(m.textInput.Value())
                amount, err := strconv.ParseFloat(amountStr, 64)
                if err != nil {
                    m.err = fmt.Errorf("invalid amount: %v", err)
                    return m, nil
                }
                m.amount = amount
                m.finished = true
                return m, tea.Quit
            } else {
                // Handle list selection
                selectedItem := m.list.SelectedItem().(Item)
                if selectedItem.Code == "OTHER" {
                    m.isCustomInput = true
                    m.textInput.Placeholder = "Enter currency code (e.g., USD)"
                    m.textInput.Focus()
                    return m, textinput.Blink
                }
                if m.stage == 0 {
                    m.currencyFrom = selectedItem.Code
                    m.stage++
                    m.list.ResetSelected()
                    return m, nil
                } else if m.stage == 1 {
                    m.currencyTo = selectedItem.Code
                    m.stage++
                    m.textInput.Placeholder = "Enter amount (e.g., 100)"
                    m.textInput.Focus()
                    return m, nil
                }
            }
        case "ctrl+c", "esc":
            return m, tea.Quit
        }
    }

    // Handle text input for custom currency or amount input
    if m.isCustomInput || m.stage == 2 {
        m.textInput, cmd = m.textInput.Update(msg)
        return m, cmd
    }

    // Handle list updates for currency selection
    if m.stage == 0 || m.stage == 1 {
        m.list, cmd = m.list.Update(msg)
        return m, cmd
    }

    return m, nil
}
Enter fullscreen mode Exit fullscreen mode
// go run main.go
200.00 EUR = 166.03 GBP
Enter fullscreen mode Exit fullscreen mode

Conclusion

That pretty much wraps up this relatively quick currency converter. I hope you've enjoyed the quick read and feel free to give a shot also, it's not that bad! 😁.

Feel free to also experiment with other third-party currency exchange providers out there, there are many. Hopefully, they got a decent API too!

See you guys on the next one! 👋🏼

Top comments (0)