Currency Exchange Rate Calculator GUI
Abstract
A currency converter is the perfect “small but real” beginner project: it has a user interface, talks to a live API on the internet, parses structured data (JSON), and produces output the user actually cares about. In this tutorial you will build a desktop GUI in Tkinter that lets the user type an amount, pick a source currency and a target currency from dropdowns, and click Convert to see today’s rate applied.
You will learn:
- How to build a Tkinter window with labels, entries, dropdowns, and buttons.
- How to call an HTTP API with
requestsrequestsand parse JSON responses. - How to handle network errors gracefully so the app never crashes mid-conversation.
- How to organize widget-driven code with callbacks.
- How to extend the converter into a tool you might actually use.
Prerequisites
- Python 3.6 or above.
- A text editor or IDE.
- An internet connection.
- Comfort installing packages with
pippip. - Familiarity with functions and conditionals.
Install Dependencies
pip install requestspip install requeststkintertkinterships with Python — no install needed.requestsrequestsis the de-facto library for HTTP in Python.
Pick an Exchange-Rate API
There are several free public APIs you can use without an API key:
| API | URL pattern | Notes |
|---|---|---|
| exchangerate-api | https://open.er-api.com/v6/latest/USDhttps://open.er-api.com/v6/latest/USD | Free tier, no key, daily updates. |
| frankfurter.app | https://api.frankfurter.app/latest?from=USD&to=EURhttps://api.frankfurter.app/latest?from=USD&to=EUR | Open-source, ECB rates. |
| Fixer.io | https://api.exchangerate.host/latest?base=USDhttps://api.exchangerate.host/latest?base=USD | Larger currency list. |
For this tutorial we will use exchangerate.host (no key required). The response is JSON like:
{
"base": "USD",
"rates": { "EUR": 0.92, "GBP": 0.79, "INR": 83.10, ... }
}{
"base": "USD",
"rates": { "EUR": 0.92, "GBP": 0.79, "INR": 83.10, ... }
}💡 Any API can change its terms. If yours stops working, swap the URL — the parsing code is similar.
Getting Started
Create the project
- Create a folder named
currency-exchange-calculatorcurrency-exchange-calculator. - Inside it, create
currencyexchnageratecalculator.pycurrencyexchnageratecalculator.py. - Open the folder in your editor.
Write the code
Currency Exchange Rate Calculator GUI
Source# Currency Exchange Rate Calculator GUI
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import requests
import json
# Create an instance of tkinter frame
win = Tk()
# Set the geometry of tkinter frame
win.geometry("700x400")
# Set the title of tkinter frame
win.title("Currency Exchange Rate Calculator")
# Disable resizing the GUI by passing in False/False
win.resizable(False, False)
# Create a function to convert currency
def exchange():
# Get the amount from the amount entry box
amount = float(amount_entry.get())
# Get the currency from the currency combobox
currency = currency_combo.get()
# Get the converted currency from the converted currency combobox
converted_currency = converted_currency_combo.get()
# Create a base URL
base_url = "https://api.exchangerate-api.com/v4/latest/"
# Create a full URL
full_url = base_url + currency
# Send a request to the URL
response = requests.get(full_url)
# Convert the response to JSON format
response_json = response.json()
# Get the currency rates from the response
rates = response_json['rates']
# Get the currency rate for the converted currency
converted_currency_rate = rates[converted_currency]
# Calculate the converted currency amount
converted_currency_amount = converted_currency_rate * amount
# Round the converted currency amount to 2 decimal places
converted_currency_amount = round(converted_currency_amount, 2)
# Create a message box to display the converted currency amount
messagebox.showinfo("Converted Currency Amount", f"{converted_currency_amount} {converted_currency}")
# Create a function to clear the GUI
def clear():
# Clear the amount entry box
amount_entry.delete(0, END)
# Clear the currency combobox
currency_combo.set("")
# Clear the converted currency combobox
converted_currency_combo.set("")
# Create a function to exit the GUI
def exit():
# Exit the GUI
win.destroy()
# Create a label for the amount entry box
amount_label = Label(win, text="Amount:", font=("Arial", 10, "bold"))
# Create a label for the currency combobox
currency_label = Label(win, text="Currency:", font=("Arial", 10, "bold"))
# Create a label for the converted currency combobox
converted_currency_label = Label(win, text="Converted Currency:", font=("Arial", 10, "bold"))
# Create a label for the amount entry box
amount_label.place(x=30, y=50)
# Create a label for the currency combobox
currency_label.place(x=30, y=100)
# Create a label for the converted currency combobox
converted_currency_label.place(x=30, y=150)
# Create a variable for the amount entry box
amount = StringVar()
# Create an entry box for the amount
amount_entry = Entry(win, textvariable=amount, font=("Arial", 10, "normal"), width=20)
# Create a variable for the currency combobox
currency = StringVar()
# Create a combobox for the currency
currency_combo = ttk.Combobox(win, textvariable=currency, font=("Arial", 10, "normal"), width=18)
# Create a variable for the converted currency combobox
converted_currency = StringVar()
# Create a combobox for the converted currency
converted_currency_combo = ttk.Combobox(win, textvariable=converted_currency, font=("Arial", 10, "normal"), width=18)
# Create a list of currencies
currencies = ["USD", "EUR", "GBP", "INR", "AUD", "CAD", "SGD", "CHF", "MYR", "JPY", "CNY"]
# Set the default value for the currency combobox
currency_combo.set("Select Currency")
# Set the default value for the converted currency combobox
converted_currency_combo.set("Select Converted Currency")
# Add the currencies to the currency combobox
currency_combo['values'] = currencies
# Add the currencies to the converted currency combobox
converted_currency_combo['values'] = currencies
# Create a button to convert currency
convert_button = Button(win, text="Convert", font=("Arial", 10, "bold"), command=exchange)
# Create a button to clear the GUI
clear_button = Button(win, text="Clear", font=("Arial", 10, "bold"), command=clear)
# Create a button to exit the GUI
exit_button = Button(win, text="Exit", font=("Arial", 10, "bold"), command=exit)
# Create an entry box for the amount
amount_entry.place(x=200, y=50)
# Create a combobox for the currency
currency_combo.place(x=200, y=100)
# Create a combobox for the converted currency
converted_currency_combo.place(x=200, y=150)
# Create a button to convert currency
convert_button.place(x=30, y=200)
# Create a button to clear the GUI
clear_button.place(x=130, y=200)
# Create a button to exit the GUI
exit_button.place(x=230, y=200)
# Run the tkinter event loop
win.mainloop()# Currency Exchange Rate Calculator GUI
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import requests
import json
# Create an instance of tkinter frame
win = Tk()
# Set the geometry of tkinter frame
win.geometry("700x400")
# Set the title of tkinter frame
win.title("Currency Exchange Rate Calculator")
# Disable resizing the GUI by passing in False/False
win.resizable(False, False)
# Create a function to convert currency
def exchange():
# Get the amount from the amount entry box
amount = float(amount_entry.get())
# Get the currency from the currency combobox
currency = currency_combo.get()
# Get the converted currency from the converted currency combobox
converted_currency = converted_currency_combo.get()
# Create a base URL
base_url = "https://api.exchangerate-api.com/v4/latest/"
# Create a full URL
full_url = base_url + currency
# Send a request to the URL
response = requests.get(full_url)
# Convert the response to JSON format
response_json = response.json()
# Get the currency rates from the response
rates = response_json['rates']
# Get the currency rate for the converted currency
converted_currency_rate = rates[converted_currency]
# Calculate the converted currency amount
converted_currency_amount = converted_currency_rate * amount
# Round the converted currency amount to 2 decimal places
converted_currency_amount = round(converted_currency_amount, 2)
# Create a message box to display the converted currency amount
messagebox.showinfo("Converted Currency Amount", f"{converted_currency_amount} {converted_currency}")
# Create a function to clear the GUI
def clear():
# Clear the amount entry box
amount_entry.delete(0, END)
# Clear the currency combobox
currency_combo.set("")
# Clear the converted currency combobox
converted_currency_combo.set("")
# Create a function to exit the GUI
def exit():
# Exit the GUI
win.destroy()
# Create a label for the amount entry box
amount_label = Label(win, text="Amount:", font=("Arial", 10, "bold"))
# Create a label for the currency combobox
currency_label = Label(win, text="Currency:", font=("Arial", 10, "bold"))
# Create a label for the converted currency combobox
converted_currency_label = Label(win, text="Converted Currency:", font=("Arial", 10, "bold"))
# Create a label for the amount entry box
amount_label.place(x=30, y=50)
# Create a label for the currency combobox
currency_label.place(x=30, y=100)
# Create a label for the converted currency combobox
converted_currency_label.place(x=30, y=150)
# Create a variable for the amount entry box
amount = StringVar()
# Create an entry box for the amount
amount_entry = Entry(win, textvariable=amount, font=("Arial", 10, "normal"), width=20)
# Create a variable for the currency combobox
currency = StringVar()
# Create a combobox for the currency
currency_combo = ttk.Combobox(win, textvariable=currency, font=("Arial", 10, "normal"), width=18)
# Create a variable for the converted currency combobox
converted_currency = StringVar()
# Create a combobox for the converted currency
converted_currency_combo = ttk.Combobox(win, textvariable=converted_currency, font=("Arial", 10, "normal"), width=18)
# Create a list of currencies
currencies = ["USD", "EUR", "GBP", "INR", "AUD", "CAD", "SGD", "CHF", "MYR", "JPY", "CNY"]
# Set the default value for the currency combobox
currency_combo.set("Select Currency")
# Set the default value for the converted currency combobox
converted_currency_combo.set("Select Converted Currency")
# Add the currencies to the currency combobox
currency_combo['values'] = currencies
# Add the currencies to the converted currency combobox
converted_currency_combo['values'] = currencies
# Create a button to convert currency
convert_button = Button(win, text="Convert", font=("Arial", 10, "bold"), command=exchange)
# Create a button to clear the GUI
clear_button = Button(win, text="Clear", font=("Arial", 10, "bold"), command=clear)
# Create a button to exit the GUI
exit_button = Button(win, text="Exit", font=("Arial", 10, "bold"), command=exit)
# Create an entry box for the amount
amount_entry.place(x=200, y=50)
# Create a combobox for the currency
currency_combo.place(x=200, y=100)
# Create a combobox for the converted currency
converted_currency_combo.place(x=200, y=150)
# Create a button to convert currency
convert_button.place(x=30, y=200)
# Create a button to clear the GUI
clear_button.place(x=130, y=200)
# Create a button to exit the GUI
exit_button.place(x=230, y=200)
# Run the tkinter event loop
win.mainloop()Run it
python currencyexchnageratecalculator.pypython currencyexchnageratecalculator.pyA window appears. Enter 100100, pick USD → EUR, click Convert, and a popup shows the result.
Step-by-Step Explanation
1. Import what you need
import tkinter as tk
from tkinter import ttk, messagebox
import requestsimport tkinter as tk
from tkinter import ttk, messagebox
import requeststkintertkinterfor the window,ttkttkfor themed widgets (dropdowns look prettier than the legacyOptionMenuOptionMenu), andmessageboxmessageboxfor the result popup.requestsrequestsfor the HTTP call.
2. Create the main window
win = tk.Tk()
win.title("Currency Exchange Calculator")
win.geometry("400x300")win = tk.Tk()
win.title("Currency Exchange Calculator")
win.geometry("400x300")Tk()Tk() is the root window. geometry("WxH")geometry("WxH") sets a fixed size.
3. Build the widgets
tk.Label(win, text="Amount:").pack()
amount_entry = tk.Entry(win)
amount_entry.pack()
CURRENCIES = ["USD", "EUR", "GBP", "INR", "JPY", "AUD", "CAD", "CNY"]
tk.Label(win, text="From:").pack()
from_currency = ttk.Combobox(win, values=CURRENCIES)
from_currency.set("USD")
from_currency.pack()
tk.Label(win, text="To:").pack()
to_currency = ttk.Combobox(win, values=CURRENCIES)
to_currency.set("EUR")
to_currency.pack()tk.Label(win, text="Amount:").pack()
amount_entry = tk.Entry(win)
amount_entry.pack()
CURRENCIES = ["USD", "EUR", "GBP", "INR", "JPY", "AUD", "CAD", "CNY"]
tk.Label(win, text="From:").pack()
from_currency = ttk.Combobox(win, values=CURRENCIES)
from_currency.set("USD")
from_currency.pack()
tk.Label(win, text="To:").pack()
to_currency = ttk.Combobox(win, values=CURRENCIES)
to_currency.set("EUR")
to_currency.pack()EntryEntryis a one-line text input.ComboboxComboboxis a dropdown the user can also type into;.set(value).set(value)chooses an initial selection..pack().pack()is the simplest of Tkinter’s layout managers — it stacks widgets top-to-bottom.
4. The conversion callback
def exchange():
try:
amount = float(amount_entry.get())
except ValueError:
messagebox.showerror("Invalid input", "Amount must be a number.")
return
src = from_currency.get()
dst = to_currency.get()
try:
url = f"https://api.exchangerate.host/latest?base={src}&symbols={dst}"
response = requests.get(url, timeout=10)
response.raise_for_status()
rates = response.json()["rates"]
rate = rates[dst]
except requests.exceptions.RequestException as e:
messagebox.showerror("Network error", str(e))
return
except KeyError:
messagebox.showerror("Bad response", f"No rate for {dst} found.")
return
converted = amount * rate
messagebox.showinfo(
"Result",
f"{amount:.2f} {src} = {converted:.2f} {dst} (rate {rate:.4f})",
)def exchange():
try:
amount = float(amount_entry.get())
except ValueError:
messagebox.showerror("Invalid input", "Amount must be a number.")
return
src = from_currency.get()
dst = to_currency.get()
try:
url = f"https://api.exchangerate.host/latest?base={src}&symbols={dst}"
response = requests.get(url, timeout=10)
response.raise_for_status()
rates = response.json()["rates"]
rate = rates[dst]
except requests.exceptions.RequestException as e:
messagebox.showerror("Network error", str(e))
return
except KeyError:
messagebox.showerror("Bad response", f"No rate for {dst} found.")
return
converted = amount * rate
messagebox.showinfo(
"Result",
f"{amount:.2f} {src} = {converted:.2f} {dst} (rate {rate:.4f})",
)Three layers of error handling:
ValueErrorValueErrorif the amount field is not a number.RequestExceptionRequestExceptionif the network is down, the server is unreachable, the request times out, or the API returns 4xx/5xx (because ofraise_for_status()raise_for_status()).KeyErrorKeyErrorif the API responds successfully but does not include the requested currency.
The timeout=10timeout=10 keeps the GUI responsive even if the API hangs.
5. Wire the button and start the loop
tk.Button(win, text="Convert", command=exchange).pack(pady=10)
win.mainloop()tk.Button(win, text="Convert", command=exchange).pack(pady=10)
win.mainloop()command=exchangecommand=exchangeregisters the callback.win.mainloop()win.mainloop()enters Tkinter’s event loop, the program lives here until the window closes.
Common Mistakes
| Problem | Cause | Fix |
|---|---|---|
| GUI freezes after click | Long HTTP call running on the main thread | Use threading.Threadthreading.Thread for the request, update the UI via win.afterwin.after |
requests.exceptions.ConnectionErrorrequests.exceptions.ConnectionError | No internet, or API down | Show a friendly message; consider caching the last rate |
KeyError: 'EUR'KeyError: 'EUR' | Misspelled currency code in the dropdown | Validate codes against a known list |
Result shows 9.9999999999999999.999999999999999 | Floating-point display | Format with :.2f:.2f |
| Multiple Convert clicks stack popups | User clicks while one is showing | Use messagebox.showinfomessagebox.showinfo, which is modal — but consider disabling the button until the call completes |
Variations to Try
1. Real-time updates
Use win.after(60_000, refresh_rates)win.after(60_000, refresh_rates) to re-fetch every minute and show a live “last updated” label.
2. Live result label instead of popup
Replace the messageboxmessagebox with a LabelLabel in the window that updates after each conversion. Better UX.
3. Conversion history
Append each result to a ListboxListbox:
history_box.insert("end", f"{amount} {src} → {converted:.2f} {dst}")history_box.insert("end", f"{amount} {src} → {converted:.2f} {dst}")Bonus: write the history to history.csvhistory.csv on exit.
4. Two-way swap button
A ”⇄” button that swaps the From and To selections.
5. Charts
Use matplotlib to plot a 30-day history. The API typically supports /timeseries/timeseries:
url = "https://api.exchangerate.host/timeseries?start_date=2025-01-01&end_date=2025-01-31&base=USD&symbols=EUR"url = "https://api.exchangerate.host/timeseries?start_date=2025-01-01&end_date=2025-01-31&base=USD&symbols=EUR"6. Larger currency list
Pull the full list from the API itself:
all_currencies = requests.get("https://api.exchangerate.host/symbols").json()["symbols"]all_currencies = requests.get("https://api.exchangerate.host/symbols").json()["symbols"]7. Crypto support
Use CoinGecko’s free API (no key required) for BTC, ETH, etc.
8. Calculator-style entry
Replace the single Entry with a numeric keypad of buttons. Helpful for touch-screen kiosks.
9. Convert to/from multiple currencies at once
Instead of a single “To”, show a table of values across ten major currencies.
10. Offline mode
Cache the last successful response to last_rates.jsonlast_rates.json. If the network fails, fall back to the cache and label the result as “stale”.
Concept Spotlight: HTTP Errors
requestsrequests returns a ResponseResponse object that does not raise on 4xx/5xx by default. Explicit checking is essential:
r = requests.get(url, timeout=5)
print(r.status_code) # 200 = OK, 404 = not found, 500 = server error
r.raise_for_status() # raises HTTPError for 4xx/5xx
data = r.json() # raises JSONDecodeError if body isn't JSONr = requests.get(url, timeout=5)
print(r.status_code) # 200 = OK, 404 = not found, 500 = server error
r.raise_for_status() # raises HTTPError for 4xx/5xx
data = r.json() # raises JSONDecodeError if body isn't JSONThe error categories you should plan for:
- Connection errors — DNS failure, no internet, server unreachable.
- Timeout errors — server took too long.
- HTTP errors — server replied but with an error status.
- Decode errors — server replied with non-JSON content.
requests.exceptions.RequestExceptionrequests.exceptions.RequestException is the base of all of these, so a single except RequestExceptionexcept RequestException catches everything network-related.
Best Practices Demonstrated
- Validate input before making a network call. Cheap checks first, expensive operations second.
- Always set a timeout. Default is no limit — a hanging server can freeze your GUI forever.
- Use
raise_for_status()raise_for_status()to turn HTTP errors into exceptions you canexceptexcept. - Show errors in the UI, not the terminal. End users do not read terminals.
- Format numbers explicitly.
:.2f:.2ffor currency always.
Real-World Applications
- Travel apps that show prices in the user’s home currency.
- E-commerce checkout flows handling multiple currencies.
- Internal finance dashboards showing daily P&L in a base currency.
- Crypto portfolio trackers.
- Currency-aware invoice generators.
Educational Value
This project teaches:
- GUI fundamentals — windows, widgets, layouts, callbacks, event loops.
- HTTP and JSON — talking to the wider web from Python.
- Robust error handling — three layers, friendly messages.
- Separation of concerns — UI building vs. business logic vs. networking.
- Working with currency — formatting, decimal precision.
Next Steps
- Replace
messageboxmessageboxpopups with an inline result label that updates immediately. - Add a “favorites” panel for currency pairs the user converts often.
- Plug into a historical-rates endpoint and draw a chart with matplotlib.
- Wrap the conversion logic in a class for easier testing.
- Port the GUI to CustomTkinter for a modern look.
- Add a command-line mode:
python currencyexchnageratecalculator.py --amount 100 --from USD --to EURpython currencyexchnageratecalculator.py --amount 100 --from USD --to EUR.
Conclusion
You built a real desktop application that talks to a live web service — the same architecture every modern app uses, just smaller. You handled bad input, network failures, and missing data without crashing the GUI. The full source is on GitHub. Explore more GUI and API projects on Python Central Hub.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
