TypeScript Tutorial: Building a Simple Web Application for a Currency Converter

In today’s globalized world, dealing with different currencies is a common occurrence. Whether you’re traveling, shopping online, or managing international finances, knowing the current exchange rates is crucial. Manually converting currencies can be time-consuming and prone to errors. This tutorial will guide you through building a simple, yet functional, web application using TypeScript that allows users to convert currencies in real-time. This project is ideal for beginners and intermediate developers looking to enhance their TypeScript skills and understand practical application development.

Why Build a Currency Converter?

Creating a currency converter application offers several benefits:

  • Practical Skill Development: You’ll gain hands-on experience with fundamental TypeScript concepts like types, interfaces, asynchronous operations, and DOM manipulation.
  • Real-World Application: This project provides a tangible application of your programming knowledge, solving a real-world problem.
  • API Integration: You will learn how to fetch data from external APIs, a critical skill for modern web development.
  • Portfolio Piece: A functional currency converter is a great addition to your portfolio, showcasing your ability to build interactive web applications.

Setting Up Your Development Environment

Before we dive into the code, let’s set up the necessary tools:

  1. Node.js and npm: Ensure you have Node.js and npm (Node Package Manager) installed on your system. You can download them from nodejs.org.
  2. TypeScript Compiler: Install the TypeScript compiler globally using npm: npm install -g typescript
  3. Code Editor: Choose a code editor like Visual Studio Code, Atom, or Sublime Text.

Project Structure

Let’s define our project structure. Create a new directory for your project and navigate into it using your terminal. Inside the project directory, create the following files and directories:

  • index.html: The HTML file for the user interface.
  • src/: A directory to hold our TypeScript files.
  • src/app.ts: The main TypeScript file for the application logic.
  • tsconfig.json: The TypeScript configuration file.
  • package.json: The file to manage project dependencies.

Creating the HTML (index.html)

Let’s create the basic structure for our HTML file. This will include input fields for currency amounts and dropdowns for currency selection, and a button to trigger the conversion. Also, we will include a section to display the converted amount.

<!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="input-group">
            <label for="amount">Amount:</label>
            <input type="number" id="amount" value="1">
        </div>
        <div class="select-group">
            <label for="fromCurrency">From:</label>
            <select id="fromCurrency">
                <!-- Currencies will be dynamically populated here -->
            </select>
        </div>
        <div class="select-group">
            <label for="toCurrency">To:</label>
            <select id="toCurrency">
                <!-- Currencies will be dynamically populated here -->
            </select>
        </div>
        <button id="convertButton">Convert</button>
        <div id="result"></div>
    </div>
    <script src="bundle.js"></script>
</body>
<style>
  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);
    width: 300px;
  }

  h1 {
    text-align: center;
    color: #333;
  }

  .input-group {
    margin-bottom: 15px;
  }

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

  input[type="number"], select {
    width: 100%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
    box-sizing: border-box;
    margin-bottom: 10px;
  }

  button {
    background-color: #4CAF50;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    width: 100%;
  }

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

  #result {
    margin-top: 20px;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
    text-align: center;
    font-weight: bold;
  }

  .select-group {
    margin-bottom: 15px;
  }
</style>
</html>

This HTML provides a basic structure for our currency converter. We’ll add the dynamic behavior using TypeScript in the next steps.

Configuring TypeScript (tsconfig.json)

The tsconfig.json file configures how TypeScript compiles your code. Create this file in the root of your project and add the following configuration:

{
  "compilerOptions": {
    "target": "es5",
    "module": "es6",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "src/**/*"
  ]
}

This configuration specifies that the TypeScript compiler should target ES5 (for broader browser compatibility), use ES6 modules, and output the compiled JavaScript to a dist directory. The strict: true option enables strict type checking, which is highly recommended for writing robust code.

Writing the TypeScript Code (src/app.ts)

This is where the core logic of our application resides. We’ll fetch currency exchange rates from a public API, populate the dropdowns, handle user input, and display the converted amount. We’ll use the free API from exchangerate-api.com.

First, let’s define some types and interfaces to represent our data:


interface ExchangeRates {
    [currencyCode: string]: number;
}

interface ExchangeRateApiResponse {
    result: string;
    time_last_update_unix: number;
    time_next_update_unix: number;
    base_code: string;
    rates: ExchangeRates;
}

Now, let’s write the main logic:


// Define the API endpoint
const API_URL = 'https://v6.exchangerate-api.com/v6/YOUR_API_KEY/latest/USD'; // Replace YOUR_API_KEY with your actual API key

// Get references to HTML elements
const amountInput = document.getElementById('amount') as HTMLInputElement;
const fromCurrencySelect = document.getElementById('fromCurrency') as HTMLSelectElement;
const toCurrencySelect = document.getElementById('toCurrency') as HTMLSelectElement;
const convertButton = document.getElementById('convertButton') as HTMLButtonElement;
const resultDiv = document.getElementById('result') as HTMLDivElement;

// Function to fetch exchange rates
async function getExchangeRates(): Promise<ExchangeRateApiResponse | null> {
    try {
        const response = await fetch(API_URL);
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data: ExchangeRateApiResponse = await response.json();
        return data;
    } catch (error) {
        console.error('Failed to fetch exchange rates:', error);
        return null;
    }
}

// Function to populate currency options
function populateCurrencies(rates: ExchangeRates): void {
    if (!rates) return;

    const currencyCodes = Object.keys(rates);

    function addOptionToSelect(selectElement: HTMLSelectElement, currencyCode: string) {
        const option = document.createElement('option');
        option.value = currencyCode;
        option.textContent = currencyCode;
        selectElement.appendChild(option);
    }

    currencyCodes.forEach(currencyCode => {
        addOptionToSelect(fromCurrencySelect, currencyCode);
        addOptionToSelect(toCurrencySelect, currencyCode);
    });
}

// Function to convert currencies
async function convertCurrency(): Promise<void> {
    const amount = parseFloat(amountInput.value);
    const fromCurrency = fromCurrencySelect.value;
    const toCurrency = toCurrencySelect.value;

    if (isNaN(amount) || !fromCurrency || !toCurrency) {
        resultDiv.textContent = 'Please enter a valid amount and select currencies.';
        return;
    }

    const data = await getExchangeRates();
    if (!data || !data.rates) {
        resultDiv.textContent = 'Failed to fetch exchange rates.';
        return;
    }

    const fromRate = data.rates[fromCurrency];
    const toRate = data.rates[toCurrency];

    if (!fromRate || !toRate) {
        resultDiv.textContent = 'Currency rates not available.';
        return;
    }

    const convertedAmount = (amount / fromRate) * toRate;
    resultDiv.textContent = `${amount} ${fromCurrency} = ${convertedAmount.toFixed(2)} ${toCurrency}`;
}

// Event listener for the convert button
convertButton.addEventListener('click', convertCurrency);

// Initialize the app
async function initializeApp(): Promise<void> {
    const data = await getExchangeRates();
    if (data && data.rates) {
        populateCurrencies(data.rates);
    }
}

initializeApp();

Here’s a breakdown of the code:

  • API_URL: This constant stores the API endpoint for fetching exchange rates. Remember to replace YOUR_API_KEY with your actual API key from exchangerate-api.com. Sign up for a free API key if you don’t already have one.
  • Element References: We get references to the HTML elements we’ll be interacting with. The as HTMLInputElement and similar type assertions tell TypeScript the expected type of the HTML elements, enabling type safety.
  • getExchangeRates(): This asynchronous function fetches exchange rates from the API. It handles potential errors using a try-catch block and returns null if there is an issue.
  • populateCurrencies(): This function takes the exchange rates data and dynamically populates the currency dropdowns. It iterates through the available currency codes and creates <option> elements for each.
  • convertCurrency(): This function converts the currency based on user input. It retrieves the amount and selected currencies, fetches the exchange rates, performs the calculation, and displays the result. It also includes error handling for invalid input and missing rates.
  • Event Listener: An event listener is attached to the convert button to trigger the convertCurrency function when clicked.
  • initializeApp(): This asynchronous function is responsible for initializing the application. It fetches the exchange rates and then populates the currency dropdowns.

Compiling and Running the Application

Now that we have our TypeScript code and HTML, let’s compile the TypeScript code into JavaScript using the TypeScript compiler. Open your terminal, navigate to your project directory, and run the following command:

tsc

This command will read the tsconfig.json file and compile all TypeScript files in the src directory, creating the compiled JavaScript files in the dist directory. If you have configured your tsconfig.json correctly, you should now have a bundle.js file in a dist directory.

To run the application, open index.html in your web browser. You may need to serve the files using a local server, such as the one provided by VS Code’s Live Server extension, or by using a simple HTTP server like `http-server` (installable via npm: `npm install -g http-server`). This is necessary because web browsers often block requests to external APIs from local file paths due to security restrictions.

Handling Common Mistakes

Here are some common mistakes and how to avoid them:

  • API Key Errors: Make sure you replace YOUR_API_KEY with your actual API key. Without a valid API key, the API calls will fail.
  • Cross-Origin Issues: If you’re running the HTML file directly from your file system (e.g., by double-clicking it), the browser might block requests to the API due to cross-origin restrictions. Use a local server to serve your files.
  • Type Errors: TypeScript’s type checking helps catch errors early. Carefully check the console for any type-related errors and fix them. For example, make sure you’re using the correct types for the variables and function parameters.
  • Incorrect Element References: Make sure your element IDs in the TypeScript code match the IDs in your HTML. Use the browser’s developer tools to check for any errors related to element selection.
  • Asynchronous Operations: Remember that fetching data from an API is an asynchronous operation. Use async/await or Promises to handle the asynchronous calls correctly to prevent issues with the application not waiting for the data to load.

Enhancements and Next Steps

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

  • Error Handling: Improve error handling to provide more informative error messages to the user.
  • Currency Symbols: Display currency symbols alongside the converted amounts.
  • User Interface Improvements: Enhance the user interface with CSS styling and potentially use a framework like Bootstrap or Tailwind CSS for a more polished look.
  • Local Storage: Save the user’s preferred currencies to local storage so they persist between sessions.
  • More Currencies: Expand the list of supported currencies.
  • Rate Updates: Implement a mechanism to periodically update the exchange rates to ensure accuracy. The API provides the time of the last update and the time for the next update.
  • Unit Testing: Write unit tests to ensure the functionality of your functions.

Summary / Key Takeaways

In this tutorial, you’ve learned how to build a simple currency converter web application using TypeScript. You’ve covered fundamental concepts such as setting up a TypeScript project, working with HTML, handling user input, making API requests, and displaying data. Remember that the core of this application is the asynchronous fetching of data and the manipulation of the DOM. These are essential skills for any web developer. By building this project, you’ve not only created a useful tool but also strengthened your understanding of TypeScript and web development principles. This project provides a solid foundation for more complex web applications. The clear separation of concerns, the use of types, and the handling of asynchronous operations are all critical aspects of building maintainable and scalable web applications. Keep experimenting, exploring the available APIs, and expanding your knowledge to create even more sophisticated applications.

FAQ

Q: How do I get an API key?

A: You can obtain a free API key from exchangerate-api.com. You’ll need to sign up for an account. Replace ‘YOUR_API_KEY’ in the code with your actual API key.

Q: Why am I getting a CORS error?

A: CORS (Cross-Origin Resource Sharing) errors occur when your web application tries to access a resource (like an API) from a different domain. This is a security feature of web browsers. To fix this, serve your HTML file using a local development server (e.g., Live Server in VS Code, or http-server) instead of opening it directly in your browser from your file system. The server will handle the CORS requests correctly.

Q: How can I debug my TypeScript code?

A: You can use your browser’s developer tools (usually accessed by right-clicking on the page and selecting “Inspect” or “Inspect Element”) to debug your JavaScript code. You can set breakpoints, step through the code, and inspect variables. Make sure your browser is loading the compiled JavaScript file (e.g., `bundle.js`) and not the TypeScript file directly.

Q: How can I improve the user interface?

A: You can improve the user interface by adding CSS styles to your HTML file. Consider using a CSS framework like Bootstrap or Tailwind CSS to quickly create a more visually appealing design. You could also add features like currency symbols, animations, and more user-friendly input controls.

Building this currency converter is more than just a coding exercise; it’s a step toward mastering practical web development. The ability to fetch data from APIs, handle user interactions, and dynamically update the user interface are core skills. As you continue to build projects and explore the possibilities of TypeScript, your abilities will only grow. Embrace the challenges, learn from your mistakes, and keep creating. You’re building not just applications, but valuable skills that will serve you well in the world of software development.