TypeScript Tutorial: Creating a Simple Currency Converter Web App

In today’s globalized world, dealing with different currencies is a common occurrence. Whether you’re traveling, shopping online, or managing international finances, having a quick and reliable currency converter can be incredibly useful. This tutorial will guide you through creating a simple, yet functional, currency converter web application using TypeScript. We’ll focus on clarity, practicality, and ease of understanding, making it perfect for developers of all levels looking to enhance their TypeScript skills.

Why Build a Currency Converter?

Building a currency converter is an excellent way to learn and practice several key programming concepts, including:

  • Working with APIs (Application Programming Interfaces) to fetch real-time data.
  • Handling user input and output.
  • Performing calculations.
  • Structuring a web application with TypeScript.

It’s also a project that can be easily expanded upon, allowing you to add features like historical exchange rates, currency charts, and more.

Prerequisites

Before we dive in, ensure you have the following installed:

  • Node.js and npm (Node Package Manager): Required to manage project dependencies and run the application.
  • TypeScript: Globally installed for compiling TypeScript code. You can install it using npm: npm install -g typescript
  • A code editor: such as Visual Studio Code, Sublime Text, or Atom.

Setting Up Your Project

Let’s start by creating a new project directory and initializing it with npm. Open your terminal or command prompt and run the following commands:

mkdir currency-converter
cd currency-converter
npm init -y

This will create a new directory called currency-converter, navigate into it, and initialize a basic package.json file. Next, we’ll initialize a TypeScript configuration file. In the same terminal, run:

tsc --init

This command generates a tsconfig.json file, which allows us to configure how TypeScript compiles our code. Open tsconfig.json in your code editor and make the following changes:

  • Set "outDir": "./dist". This specifies where the compiled JavaScript files will be placed.
  • Set "module": "esnext". This specifies the module system to use.
  • Set "moduleResolution": "node". This tells the compiler how to resolve modules.
  • Set "sourceMap": true. This generates source map files for debugging.
  • Set "strict": true. This enables strict type-checking.

Your tsconfig.json file should look similar to this:

{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "moduleResolution": "node",
    "outDir": "./dist",
    "sourceMap": true,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

Project Structure

Create a directory named src in your project root. Inside the src directory, create the following files:

  • index.html: The HTML file for the web application.
  • index.ts: The main TypeScript file where we’ll write our code.
  • style.css: The CSS file for styling the application.

Your project structure should look like this:

currency-converter/
├── src/
│   ├── index.html
│   ├── index.ts
│   └── style.css
├── package.json
├── tsconfig.json
└── ...

Building the HTML (index.html)

Let’s create the basic HTML structure for our currency converter. Open index.html and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Currency Converter</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Currency Converter</h1>
        <div class="converter-box">
            <div class="input-group">
                <label for="amount">Amount:</label>
                <input type="number" id="amount" placeholder="Enter amount">
            </div>
            <div class="input-group">
                <label for="fromCurrency">From:</label>
                <select id="fromCurrency">
                    <!-- Currencies will be added here dynamically -->
                </select>
            </div>
            <div class="input-group">
                <label for="toCurrency">To:</label>
                <select id="toCurrency">
                    <!-- Currencies will be added here dynamically -->
                </select>
            </div>
            <button id="convertButton">Convert</button>
            <div id="result"></div>
        </div>
    </div>
    <script src="dist/index.js"></script>
</body>
</html>

This HTML provides the basic structure: a title, input fields for the amount, from and to currencies (using select dropdowns), a convert button, and a result display area. Note the <script src="dist/index.js"></script> tag; this will link to our compiled TypeScript code.

Styling with CSS (style.css)

To make the application visually appealing, add some basic styling to style.css:

body {
    font-family: sans-serif;
    background-color: #f4f4f4;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
}

.container {
    background-color: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.converter-box {
    display: flex;
    flex-direction: column;
    gap: 15px;
}

.input-group {
    display: flex;
    flex-direction: column;
}

label {
    margin-bottom: 5px;
    font-weight: bold;
}

input[type="number"], select {
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 16px;
}

button {
    padding: 10px 20px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 16px;
}

button:hover {
    background-color: #3e8e41;
}

#result {
    margin-top: 15px;
    font-size: 18px;
    font-weight: bold;
}

This CSS styles the layout, input fields, button, and result display to create a clean and user-friendly interface.

Writing the TypeScript Code (index.ts)

Now, let’s write the core logic in index.ts. This is where we’ll fetch currency exchange rates, handle user input, and perform the conversion.

First, we need to define some types to improve code readability and maintainability. Add the following code at the beginning of index.ts:


// Define types
interface ExchangeRates {
    [currencyCode: string]: number;
}

interface ApiResponse {
    rates: ExchangeRates;
    base: string;
    date: string;
}

These interfaces define the structure of the data we’ll be working with. ExchangeRates will store the exchange rates, and ApiResponse represents the response from the API.

Next, let’s define a function to fetch the exchange rates from an API. We’ll use the free and open-source API at exchangerate-api.com. It is important to note that you will need an API key for this, which you can obtain for free by signing up on their website. Replace “YOUR_API_KEY” with your actual API key. If you don’t have an API key, you can find other free APIs online, or use a mock API for testing.


async function getExchangeRates(baseCurrency: string): Promise<ApiResponse | null> {
    const apiKey = "YOUR_API_KEY"; // Replace with your API key
    const apiUrl = `https://api.exchangerate-api.com/v4/latest/${baseCurrency}`;
    try {
        const response = await fetch(apiUrl, {
            headers: {
                'X-RapidAPI-Key': apiKey,
                'X-RapidAPI-Host': 'exchange-rate-api.p.rapidapi.com'
            }
        });
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return await response.json() as ApiResponse;
    } catch (error: any) {
        console.error("Error fetching exchange rates:", error);
        return null;
    }
}

This function fetches the latest exchange rates for a specified base currency. It uses the fetch API to make the request and handles potential errors.

Now, let’s create a function to populate the currency dropdowns. We will use a predefined list of currencies for this example. In a real-world application, you might fetch this list from an API as well.


const currencies = ['USD', 'EUR', 'GBP', 'JPY', 'CAD', 'AUD', 'CHF', 'CNY', 'SEK', 'NZD'];

function populateCurrencies(selectElement: HTMLSelectElement, selectedCurrency?: string) {
    currencies.forEach(currency => {
        const option = document.createElement('option');
        option.value = currency;
        option.textContent = currency;
        if (selectedCurrency && currency === selectedCurrency) {
            option.selected = true;
        }
        selectElement.appendChild(option);
    });
}

This function takes a select element and populates it with currency options. It also allows us to pre-select a currency.

Next, we need a function to handle the conversion:


async function convertCurrency() {
    const amount = (document.getElementById('amount') as HTMLInputElement).value;
    const fromCurrency = (document.getElementById('fromCurrency') as HTMLSelectElement).value;
    const toCurrency = (document.getElementById('toCurrency') as HTMLSelectElement).value;
    const resultElement = document.getElementById('result') as HTMLDivElement;

    if (!amount || isNaN(Number(amount))) {
        resultElement.textContent = 'Please enter a valid amount.';
        return;
    }

    if (fromCurrency === toCurrency) {
        resultElement.textContent = 'Currencies are the same.';
        return;
    }

    const exchangeRates = await getExchangeRates(fromCurrency);

    if (!exchangeRates) {
        resultElement.textContent = 'Could not fetch exchange rates.';
        return;
    }

    const rate = exchangeRates.rates[toCurrency];

    if (!rate) {
        resultElement.textContent = 'Exchange rate not found.';
        return;
    }

    const convertedAmount = Number(amount) * rate;
    resultElement.textContent = `${amount} ${fromCurrency} = ${convertedAmount.toFixed(2)} ${toCurrency}`;
}

This function retrieves the amount, from and to currencies, fetches the exchange rates, performs the conversion, and displays the result.

Finally, let’s add the event listeners and initialize the application:


function main() {
    const fromCurrencySelect = document.getElementById('fromCurrency') as HTMLSelectElement;
    const toCurrencySelect = document.getElementById('toCurrency') as HTMLSelectElement;
    const convertButton = document.getElementById('convertButton') as HTMLButtonElement;

    populateCurrencies(fromCurrencySelect, 'USD'); // Default to USD
    populateCurrencies(toCurrencySelect, 'EUR'); // Default to EUR

    convertButton.addEventListener('click', convertCurrency);
}

main();

This initializes the currency dropdowns and attaches an event listener to the convert button.

Here’s the complete index.ts file:


// Define types
interface ExchangeRates {
    [currencyCode: string]: number;
}

interface ApiResponse {
    rates: ExchangeRates;
    base: string;
    date: string;
}

async function getExchangeRates(baseCurrency: string): Promise<ApiResponse | null> {
    const apiKey = "YOUR_API_KEY"; // Replace with your API key
    const apiUrl = `https://api.exchangerate-api.com/v4/latest/${baseCurrency}`;
    try {
        const response = await fetch(apiUrl, {
            headers: {
                'X-RapidAPI-Key': apiKey,
                'X-RapidAPI-Host': 'exchange-rate-api.p.rapidapi.com'
            }
        });
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return await response.json() as ApiResponse;
    } catch (error: any) {
        console.error("Error fetching exchange rates:", error);
        return null;
    }
}

const currencies = ['USD', 'EUR', 'GBP', 'JPY', 'CAD', 'AUD', 'CHF', 'CNY', 'SEK', 'NZD'];

function populateCurrencies(selectElement: HTMLSelectElement, selectedCurrency?: string) {
    currencies.forEach(currency => {
        const option = document.createElement('option');
        option.value = currency;
        option.textContent = currency;
        if (selectedCurrency && currency === selectedCurrency) {
            option.selected = true;
        }
        selectElement.appendChild(option);
    });
}

async function convertCurrency() {
    const amount = (document.getElementById('amount') as HTMLInputElement).value;
    const fromCurrency = (document.getElementById('fromCurrency') as HTMLSelectElement).value;
    const toCurrency = (document.getElementById('toCurrency') as HTMLSelectElement).value;
    const resultElement = document.getElementById('result') as HTMLDivElement;

    if (!amount || isNaN(Number(amount))) {
        resultElement.textContent = 'Please enter a valid amount.';
        return;
    }

    if (fromCurrency === toCurrency) {
        resultElement.textContent = 'Currencies are the same.';
        return;
    }

    const exchangeRates = await getExchangeRates(fromCurrency);

    if (!exchangeRates) {
        resultElement.textContent = 'Could not fetch exchange rates.';
        return;
    }

    const rate = exchangeRates.rates[toCurrency];

    if (!rate) {
        resultElement.textContent = 'Exchange rate not found.';
        return;
    }

    const convertedAmount = Number(amount) * rate;
    resultElement.textContent = `${amount} ${fromCurrency} = ${convertedAmount.toFixed(2)} ${toCurrency}`;
}

function main() {
    const fromCurrencySelect = document.getElementById('fromCurrency') as HTMLSelectElement;
    const toCurrencySelect = document.getElementById('toCurrency') as HTMLSelectElement;
    const convertButton = document.getElementById('convertButton') as HTMLButtonElement;

    populateCurrencies(fromCurrencySelect, 'USD'); // Default to USD
    populateCurrencies(toCurrencySelect, 'EUR'); // Default to EUR

    convertButton.addEventListener('click', convertCurrency);
}

main();

Compiling and Running the Application

Now that we have our code, let’s compile it and run the application. In your terminal, navigate to your project directory and run the following command:

tsc

This command will compile your index.ts file and create a dist/index.js file. Next, open index.html in your web browser. You should see the currency converter interface. Enter an amount, select currencies, and click the convert button to see the converted amount.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid them:

  • Incorrect API Key: Make sure you have a valid API key from the exchange rate API provider. Double-check your key and ensure it’s correctly placed in your code.
  • CORS Errors: If you encounter CORS (Cross-Origin Resource Sharing) errors, it means the API server is not allowing requests from your domain. This is a common issue when developing locally. You might need to use a proxy server or configure your development server to handle CORS. Many modern browsers offer extensions to disable CORS for development purposes, but use them with caution.
  • Type Errors: TypeScript’s type checking can help catch errors early. Ensure you’re using the correct types for variables and function parameters. Review the console output for any type-related errors.
  • Incorrect Element IDs: Make sure the element IDs in your TypeScript code match the IDs in your HTML. For example, if you have document.getElementById('amount') in your TypeScript, make sure there’s an input element with id="amount" in your HTML.
  • Handling API Errors: Always handle errors when making API calls. Check for network errors, invalid responses, and other potential issues. Provide informative error messages to the user.

Enhancements and Next Steps

This is a basic currency converter. Here are some ideas for enhancements:

  • Add more currencies: Expand the list of available currencies.
  • Implement error handling: Provide more user-friendly error messages for API failures or invalid input.
  • Add a loading indicator: Display a loading indicator while fetching exchange rates.
  • Store historical exchange rates: Allow users to view historical exchange rates.
  • Implement currency selection with search: Use a library or custom component for a searchable currency selection.
  • Improve the UI: Enhance the user interface with more advanced styling.
  • Add unit tests: Write unit tests to ensure the correctness of your code.

Key Takeaways

  • TypeScript enhances code quality and maintainability.
  • Working with APIs is a fundamental skill for web development.
  • Error handling is crucial for creating robust applications.
  • Web applications can be built with relatively few lines of code.

FAQ

Here are some frequently asked questions:

  1. Why use TypeScript? TypeScript provides static typing, which helps catch errors early, improves code readability, and makes it easier to maintain large codebases.
  2. How do I get an API key? You can obtain a free API key from various currency exchange rate API providers. Sign up on their website and follow their instructions. Remember to replace “YOUR_API_KEY” with your actual key in the code.
  3. What are CORS errors? CORS errors occur when a web browser restricts a web page from making requests to a different domain than the one that served the web page. This is a security measure. You might need to configure your server or use a proxy to bypass this during development.
  4. How can I deploy this application? You can deploy your application to a web hosting service like Netlify, Vercel, or GitHub Pages. You’ll need to build your TypeScript code (tsc) and deploy the generated HTML, CSS, and JavaScript files.
  5. Can I use a different API? Yes, you can use any currency exchange rate API. You’ll need to adjust the API endpoint and parsing logic accordingly. Be sure to check the API documentation for proper usage.

By following this tutorial, you’ve created a functional currency converter using TypeScript. You’ve learned how to integrate with an API, handle user input, and structure a basic web application. The concepts covered here are applicable to many other web development projects. Embrace the power of TypeScript and the exciting possibilities it offers for building reliable and maintainable web applications. The journey of learning never truly concludes, and each project undertaken is a step further into mastering this versatile language and the vast landscape of web development.