TypeScript: Building a Simple Weather Application

In today’s interconnected world, weather information is readily accessible and essential for daily planning. From checking if you need an umbrella to understanding the impact of climate change, the ability to quickly and accurately access weather data is invaluable. This tutorial will guide you through building a simple, yet functional, weather application using TypeScript. We’ll explore how to fetch data from a weather API, display it in a user-friendly format, and incorporate error handling to make your application robust. This project is ideal for developers looking to solidify their TypeScript skills while creating a practical and engaging application.

Setting Up Your Development Environment

Before diving into the code, let’s ensure you have the necessary tools installed. You’ll need:

  • Node.js and npm (Node Package Manager): These are essential for managing project dependencies and running your TypeScript code. You can download them from the official Node.js website (https://nodejs.org/).
  • TypeScript Compiler: Install it globally using npm: npm install -g typescript
  • A Code Editor: Visual Studio Code (VS Code) is highly recommended due to its excellent TypeScript support, but you can use any editor you prefer.

Once you have these installed, create a new project directory and initialize a new npm project:

mkdir weather-app
cd weather-app
npm init -y

This will create a package.json file in your project directory. Next, let’s set up TypeScript in our project:

npm install --save-dev typescript

This command installs TypeScript as a development dependency. Now, create a tsconfig.json file in your project root. This file tells the TypeScript compiler how to compile your code. You can generate a basic tsconfig.json file using the following command:

npx tsc --init

This command creates a tsconfig.json file with default configurations. Open tsconfig.json and make sure the following options are set (or uncommented and set):

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}
  • target: Specifies the JavaScript version to compile to. ES2015 is a good starting point.
  • module: Specifies the module system. commonjs is suitable for Node.js environments.
  • outDir: Specifies the output directory for the compiled JavaScript files. We’ll use ./dist.
  • rootDir: Specifies the root directory of your TypeScript files. We’ll use ./src.
  • strict: Enables strict type-checking options. It’s highly recommended to enable this for better code quality.
  • esModuleInterop: Enables interoperability between CommonJS and ES modules.
  • skipLibCheck: Skips type checking of declaration files.
  • forceConsistentCasingInFileNames: Enforces consistent casing in filenames.
  • include: Specifies which files to include in the compilation.

Finally, create a src directory and a file named index.ts inside it. This is where we’ll write our TypeScript code.

Fetching Weather Data from an API

To get weather data, we’ll use a weather API. There are many free and paid options available. For this tutorial, we’ll use the OpenWeatherMap API, which provides a free tier. You’ll need to sign up for a free API key at https://openweathermap.org/.

Once you have your API key, let’s create a function to fetch weather data. Inside src/index.ts, add the following code:

// Import the 'node-fetch' library
import fetch from 'node-fetch';

// Replace with your OpenWeatherMap API key
const apiKey = 'YOUR_API_KEY';

// Define an interface for the weather data
interface WeatherData {
  main: {
    temp: number;
    humidity: number;
  };
  weather: {
    description: string;
    icon: string;
  }[];
  name: string;
}

// Function to fetch weather data
async function getWeatherData(city: string): Promise {
  try {
    const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`;
    const response = await fetch(apiUrl);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data: WeatherData = await response.json() as WeatherData;
    return data;
  } catch (error: any) {
    console.error('Error fetching weather data:', error.message);
    return null;
  }
}

Let’s break down this code:

  • Import fetch: We import the fetch function from the node-fetch library to make HTTP requests. First install the library using npm: npm install node-fetch.
  • API Key: Replace 'YOUR_API_KEY' with your actual API key.
  • WeatherData Interface: This interface defines the structure of the data we expect to receive from the API. This is a crucial part of TypeScript, allowing us to define the shape of our data and catch type errors early.
  • getWeatherData Function:
    • It takes a city string as input.
    • It constructs the API URL using the city name and API key.
    • It uses fetch to make a GET request to the API.
    • It checks if the response is successful (status code 200-299). If not, it throws an error.
    • It parses the response as JSON and casts it to the WeatherData interface.
    • It handles potential errors using a try...catch block, logging any errors to the console and returning null.

Displaying Weather Data

Now that we can fetch weather data, let’s create a function to display it. We’ll keep it simple and log the data to the console for now. Later, we’ll explore how to display it in a web application.

Add the following code to src/index.ts, below the getWeatherData function:


function displayWeatherData(data: WeatherData | null) {
  if (data) {
    console.log(`Weather in ${data.name}:`);
    console.log(`Temperature: ${data.main.temp}°C`);
    console.log(`Humidity: ${data.main.humidity}%`);
    console.log(`Description: ${data.weather[0].description}`);
  } else {
    console.log('Could not retrieve weather data.');
  }
}

This function takes the weather data (or null if an error occurred) and logs the relevant information to the console. It checks if the data is not null before accessing its properties to avoid runtime errors.

Putting It All Together: The Main Function

Let’s create a main function to orchestrate the process. This function will call getWeatherData, and then displayWeatherData.

Add the following to src/index.ts:


async function main() {
  const city = 'London'; // You can change this to any city
  const weatherData = await getWeatherData(city);
  displayWeatherData(weatherData);
}

main();

Here’s what this code does:

  • main Function:
    • It defines a city variable (e.g., “London”).
    • It calls getWeatherData with the city name and awaits the result.
    • It then calls displayWeatherData with the weather data.
  • Calling main(): The last line, main();, calls the main function to start the process.

Running Your Application

Now it’s time to run your application. Open your terminal and navigate to your project directory. Then, compile your TypeScript code using the TypeScript compiler:

tsc

This command will compile your src/index.ts file and create a dist/index.js file. Then, run the compiled JavaScript file using Node.js:

node dist/index.js

You should see the weather data for the specified city printed in your console. If you encounter errors, double-check your API key, ensure you have the necessary dependencies installed, and review the console output for error messages.

Adding Error Handling

Robust error handling is crucial for any application. We’ve already included basic error handling in the getWeatherData function. Let’s expand on this to handle common issues.

1. API Key Errors: Ensure your API key is valid and that you’ve enabled the necessary API features in your OpenWeatherMap account.

2. Network Errors: The fetch function can throw errors if there are network issues. While we have a try/catch block, you might want to provide more specific error messages to the user.

3. Invalid City Names: The API might return an error if the city name is not found. You can handle this by checking the API response for error codes or messages and displaying an appropriate message to the user.

Here’s an example of how you can improve the error handling in your getWeatherData function:


async function getWeatherData(city: string): Promise {
  try {
    const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`;
    const response = await fetch(apiUrl);

    if (!response.ok) {
      // Check for specific error codes from the API
      if (response.status === 404) {
        console.error('City not found.');
      } else {
        console.error(`HTTP error! status: ${response.status}`);
      }
      return null;
    }

    const data: WeatherData = await response.json() as WeatherData;
    return data;
  } catch (error: any) {
    if (error instanceof TypeError) {
        console.error('Network error. Check your internet connection.');
    } else {
        console.error('Error fetching weather data:', error.message);
    }
    return null;
  }
}

In this enhanced version:

  • We check for specific HTTP status codes (e.g., 404 for “Not Found”) and provide more informative error messages.
  • We check for TypeError, which can be thrown by the fetch function if there is a network error.

Making a Simple Web Application (Optional)

While the console output is useful for testing, you’ll likely want to display the weather data in a web application. Here’s a basic outline of how you can do this using HTML, CSS, and JavaScript. This is a simplified example and does not cover advanced concepts like framework integrations.

1. Create an HTML File (index.html):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Weather App</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Weather App</h1>
        <div id="weather-info">
            <p id="city"></p>
            <p id="temperature"></p>
            <p id="humidity"></p>
            <p id="description"></p>
            <p id="error-message" class="error"></p>
        </div>
        <input type="text" id="city-input" placeholder="Enter city name">
        <button id="search-button">Search</button>
    </div>
    <script src="dist/index.js"></script>
</body>
</html>

2. Create a CSS File (style.css):

.container {
    width: 80%;
    margin: 20px auto;
    text-align: center;
    font-family: sans-serif;
}

#weather-info {
    margin-top: 20px;
}

.error {
    color: red;
}

3. Modify index.ts to Update the DOM:


// ... (your existing code)

function displayWeatherData(data: WeatherData | null) {
    const cityElement = document.getElementById('city') as HTMLParagraphElement;
    const temperatureElement = document.getElementById('temperature') as HTMLParagraphElement;
    const humidityElement = document.getElementById('humidity') as HTMLParagraphElement;
    const descriptionElement = document.getElementById('description') as HTMLParagraphElement;
    const errorMessageElement = document.getElementById('error-message') as HTMLParagraphElement;

    if (data) {
        cityElement.textContent = `Weather in ${data.name}:`;
        temperatureElement.textContent = `Temperature: ${data.main.temp}°C`;
        humidityElement.textContent = `Humidity: ${data.main.humidity}%`;
        descriptionElement.textContent = `Description: ${data.weather[0].description}`;
        errorMessageElement.textContent = ''; // Clear any previous error messages
    } else {
        cityElement.textContent = '';
        temperatureElement.textContent = '';
        humidityElement.textContent = '';
        descriptionElement.textContent = '';
        errorMessageElement.textContent = 'Could not retrieve weather data.';
    }
}

async function main() {
    const cityInput = document.getElementById('city-input') as HTMLInputElement;
    const searchButton = document.getElementById('search-button') as HTMLButtonElement;

    searchButton.addEventListener('click', async () => {
        const city = cityInput.value;
        const weatherData = await getWeatherData(city);
        displayWeatherData(weatherData);
    });
}

main();

In this example:

  • We added HTML elements to display the weather information.
  • We used CSS for basic styling.
  • We modified the displayWeatherData function to update the DOM elements.
  • We added an input field and a button to allow the user to enter a city name.
  • We added an event listener to the button to fetch and display the weather data when the button is clicked.

To run this web application, you’ll need to serve the files using a web server. You can use a simple server like the http-server package (install it globally with npm install -g http-server) and then run http-server in your project directory. Then open your browser and go to the address provided by the server (usually http://localhost:8080).

Common Mistakes and How to Fix Them

Let’s address some common pitfalls and how to avoid them:

  • Incorrect API Key: Double-check your API key for typos and ensure it’s activated in your OpenWeatherMap account.
  • CORS Errors: If you’re running your web application locally and encounter CORS (Cross-Origin Resource Sharing) errors, you might need to configure your web server to allow requests from your origin. For development, you can use a browser extension to disable CORS or use a proxy server. In a production environment, you need to configure the server to allow requests from your domain.
  • Typos in City Names: The API is sensitive to city names. Implement input validation to handle incorrect city names.
  • Incorrect Data Types: Make sure your interface matches the data structure of the API response. TypeScript will help you catch these errors early.
  • Asynchronous Operations: Ensure you’re using async/await correctly when working with asynchronous operations (like fetching data from an API). Avoid blocking the main thread.

Summary / Key Takeaways

This tutorial provided a comprehensive guide to building a simple weather application with TypeScript. You learned how to set up a TypeScript project, fetch data from an API, handle errors, and display the information. We covered essential concepts like interfaces, asynchronous functions, and error handling. By implementing these techniques, you can create robust and user-friendly applications.

FAQ

1. Can I use a different weather API?

Yes, you can use any weather API that provides a public API. You’ll need to adjust the API URL, the data structure (and therefore your TypeScript interfaces), and potentially the authentication method (e.g., API key) to match the API you choose.

2. How can I improve the user interface?

You can enhance the UI by using HTML, CSS, and potentially a JavaScript framework like React, Angular, or Vue.js. Consider adding features like:

  • More detailed weather information (e.g., wind speed, pressure).
  • Icons to represent the weather conditions.
  • A search bar with autocomplete.
  • A map to display the weather for different locations.

3. How do I handle different units (Celsius vs. Fahrenheit)?

The OpenWeatherMap API provides a units parameter. You can add a setting in your application to allow the user to select the preferred unit (Celsius or Fahrenheit) and pass the appropriate value (metric for Celsius, imperial for Fahrenheit) to the API in the URL. You’ll also need to convert the temperature values accordingly in your display function.

4. How can I deploy this application?

You can deploy your web application using various platforms, such as:

  • Netlify or Vercel: These platforms are great for static sites and offer free tiers.
  • GitHub Pages: A simple and free option for hosting static websites.
  • A cloud provider (e.g., AWS, Google Cloud, Azure): Offers more control and scalability but requires more setup.

5. How can I make my application more accessible?

To improve accessibility, consider the following:

  • Use semantic HTML (e.g., <header>, <nav>, <main>, <article>, <aside>, <footer>).
  • Provide alt text for images.
  • Ensure sufficient color contrast.
  • Use ARIA attributes to enhance the accessibility of dynamic content.
  • Make your application keyboard-navigable.

Building a weather application is an excellent way to practice your TypeScript skills and learn about working with APIs. By following these steps, you can create a functional and informative application that provides real-time weather data. Remember that the journey of a thousand lines of code begins with a single one, so keep experimenting, learning, and refining your projects.