TypeScript Tutorial: Building a Simple Weather App

In today’s digital age, we’re constantly bombarded with information, and the weather is no exception. Whether you’re planning your day, traveling, or simply curious, having access to accurate and up-to-date weather information is crucial. This tutorial will guide you through creating a simple, yet functional, weather application using TypeScript. We’ll cover everything from setting up your development environment to fetching data from a weather API and displaying it in a user-friendly format. This project is perfect for beginners and intermediate developers looking to expand their TypeScript skills and build something practical.

Why Build a Weather App?

Building a weather app is an excellent learning experience for several reasons:

  • Real-World Application: Weather apps are used by millions daily, making it a relevant and practical project.
  • API Integration: You’ll learn how to interact with external APIs, a fundamental skill in modern web development.
  • Data Handling: You’ll work with data formats like JSON and learn how to parse and display data effectively.
  • UI Development: You’ll get hands-on experience in creating a basic user interface with HTML, CSS, and TypeScript.
  • TypeScript Fundamentals: You’ll reinforce your understanding of TypeScript’s core concepts like types, interfaces, and classes.

By the end of this tutorial, you’ll have a fully functional weather app and a solid understanding of the concepts involved, opening doors to more complex projects.

Prerequisites

Before we begin, ensure you have the following:

  • Node.js and npm (Node Package Manager) installed: This is essential for managing project dependencies and running TypeScript code. You can download it from nodejs.org.
  • A code editor: Visual Studio Code (VS Code) is highly recommended due to its excellent TypeScript support. You can download it from code.visualstudio.com.
  • Basic knowledge of HTML, CSS, and JavaScript: While we’ll focus on TypeScript, some familiarity with these technologies will be helpful.
  • A free API key from a weather service: We’ll use the OpenWeatherMap API for fetching weather data. Sign up for a free account at openweathermap.org and obtain an API key.

Setting Up the Project

Let’s start by setting up our project environment. Open your terminal or command prompt and follow these steps:

  1. Create a Project Directory:
    mkdir weather-app
    cd weather-app
  2. Initialize npm:
    npm init -y

    This creates a `package.json` file to manage project dependencies.

  3. Install TypeScript:
    npm install typescript --save-dev

    This installs TypeScript as a development dependency.

  4. Initialize TypeScript configuration:
    npx tsc --init

    This creates a `tsconfig.json` file, which configures TypeScript compiler options.

  5. Create Source Directory:
    mkdir src

    This will hold our TypeScript source files.

Your project directory should now look like this:

weather-app/
├── node_modules/
├── package.json
├── package-lock.json
├── src/
├── tsconfig.json
└──

Writing the TypeScript Code

Now, let’s write the TypeScript code for our weather app. We’ll create the following files inside the `src` directory:

  • `index.ts`: The main entry point of our application.
  • `weatherService.ts`: Handles fetching weather data from the API.
  • `types.ts`: Defines TypeScript types and interfaces for our data.

1. Creating Types and Interfaces (types.ts)

First, let’s define the types and interfaces we’ll use to represent the weather data. Create a file named `types.ts` in the `src` directory and add the following code:

// types.ts

export interface WeatherData {
  coord: {
    lon: number;
    lat: number;
  };
  weather: {
    id: number;
    main: string;
    description: string;
    icon: string;
  }[];
  main: {
    temp: number;
    feels_like: number;
    temp_min: number;
    temp_max: number;
    pressure: number;
    humidity: number;
  };
  visibility: number;
  wind: {
    speed: number;
    deg: number;
  };
  clouds: {
    all: number;
  };
  dt: number;
  sys: {
    type: number;
    id: number;
    country: string;
    sunrise: number;
    sunset: number;
  };
  timezone: number;
  id: number;
  name: string;
  cod: number;
}

This interface, `WeatherData`, represents the structure of the JSON response we’ll receive from the OpenWeatherMap API. It’s crucial to define these types to ensure type safety and prevent errors when working with the data.

2. Fetching Weather Data (weatherService.ts)

Next, let’s create a service to fetch weather data from the OpenWeatherMap API. Create a file named `weatherService.ts` in the `src` directory and add the following code:

// weatherService.ts
import { WeatherData } from './types';

const API_KEY = 'YOUR_API_KEY'; // Replace with your API key
const API_URL = 'https://api.openweathermap.org/data/2.5/weather';

export async function getWeatherData(city: string): Promise<WeatherData | null> {
  try {
    const response = await fetch(`${API_URL}?q=${city}&appid=${API_KEY}&units=metric`);

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

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

In this code:

  • We import the `WeatherData` interface from `types.ts`.
  • We define `API_KEY` and `API_URL` constants. Remember to replace `YOUR_API_KEY` with your actual API key.
  • The `getWeatherData` function takes a `city` as input.
  • It uses the `fetch` API to make a request to the OpenWeatherMap API, including the city and API key in the URL. The `units=metric` parameter ensures the temperature is in Celsius.
  • 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 returns the weather data or `null` if an error occurs.

3. Main Application Logic (index.ts)

Now, let’s create the main file, `index.ts`, which will handle the user interface and call our weather service. Create a file named `index.ts` in the `src` directory and add the following code:

// index.ts
import { getWeatherData } from './weatherService';
import { WeatherData } from './types';

const searchForm = document.getElementById('search-form') as HTMLFormElement;
const cityInput = document.getElementById('city-input') as HTMLInputElement;
const weatherContainer = document.getElementById('weather-container') as HTMLDivElement;

async function displayWeather(city: string) {
  const weatherData = await getWeatherData(city);

  if (weatherData) {
    renderWeather(weatherData);
  } else {
    weatherContainer.innerHTML = '<p>Could not fetch weather data.</p>';
  }
}

function renderWeather(data: WeatherData) {
  weatherContainer.innerHTML = `
    <h2>Weather in ${data.name}, ${data.sys.country}</h2>
    <p>Temperature: ${data.main.temp} °C</p>
    <p>Condition: ${data.weather[0].description}</p>
    <p>Humidity: ${data.main.humidity}%</p>
    <p>Wind Speed: ${data.wind.speed} m/s</p>
  `;
}

searchForm.addEventListener('submit', (event) => {
  event.preventDefault();
  const city = cityInput.value;
  if (city) {
    displayWeather(city);
  }
});

In this code:

  • We import `getWeatherData` from `weatherService.ts` and `WeatherData` from `types.ts`.
  • We get references to HTML elements using `document.getElementById`. The `as HTMLFormElement`, `as HTMLInputElement`, and `as HTMLDivElement` are type assertions, telling TypeScript the expected types of these elements.
  • The `displayWeather` function takes a `city` as input, calls `getWeatherData` to fetch the data, and then calls `renderWeather` to display the results. If an error occurs, it displays an error message.
  • The `renderWeather` function takes `WeatherData` as input and constructs the HTML to display the weather information. It uses template literals for easy string concatenation.
  • An event listener is added to the `searchForm`. When the form is submitted, it prevents the default form submission behavior (which would refresh the page), gets the city from the input field, and calls `displayWeather`.

Creating the HTML and CSS

Now, let’s create the HTML and CSS for our weather app. Create an `index.html` file in the root of your project directory 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>Weather App</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Weather App</h1>
        <form id="search-form">
            <input type="text" id="city-input" placeholder="Enter city name">
            <button type="submit">Search</button>
        </form>
        <div id="weather-container"></div>
    </div>
    <script src="bundle.js"></script>
</body>
</html>

This HTML provides the basic structure of the app, including a title, a search form with an input field and a button, and a container to display the weather information. It also links to a `style.css` file for styling and includes the compiled JavaScript file (`bundle.js`).

Next, create a `style.css` file in the root of your project directory and add some basic styling:

/* style.css */
body {
    font-family: sans-serif;
    background-color: #f0f0f0;
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
}

.container {
    background-color: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    width: 80%; /* Adjust as needed */
    max-width: 600px;
}

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

form {
    display: flex;
    margin-bottom: 20px;
}

input[type="text"] {
    flex-grow: 1;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
    margin-right: 10px;
}

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

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

#weather-container {
    margin-top: 20px;
    text-align: center;
}

This CSS provides basic styling for the app, making it more visually appealing.

Compiling and Running the App

Now, let’s compile our TypeScript code and run the app. Open your terminal in the project directory and run the following commands:

  1. Compile the TypeScript code:
    npx tsc

    This command uses the TypeScript compiler (`tsc`) to transpile the TypeScript code (`.ts` files) into JavaScript code (`.js` files). The compiled JavaScript will be placed in the same directory as the source TypeScript files (e.g., `src/index.js`, `src/weatherService.js`, etc.). However, we need to bundle it into a single file for the browser.

  2. Bundle the JavaScript files (using a bundler): We’ll use a simple bundler like Parcel or Webpack to bundle our JavaScript files into a single `bundle.js` file. First, install Parcel:
    npm install -D parcel

    Then, run Parcel to bundle your code:

    npx parcel src/index.html

    This will create a `dist` folder containing `index.html` and `bundle.js`.

  3. Open the app in your browser: Open the `index.html` file (from the `dist` folder if using Parcel) in your web browser. You can do this by double-clicking the file or by using a local web server (like `http-server` if you have it installed globally) and navigating to the appropriate URL (e.g., `http://localhost:8080`).

You should now see the weather app in your browser. Enter a city name in the input field and click the search button. The app will fetch the weather data from the OpenWeatherMap API and display it on the page.

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them:

  • API Key Issues:
    • Incorrect API Key: Double-check that you’ve entered your API key correctly in `weatherService.ts`.
    • API Key Restrictions: Make sure your API key is not restricted by IP address or other limitations in your OpenWeatherMap account.
    • API Key Usage Limits: Ensure you haven’t exceeded the free tier’s usage limits. Check your OpenWeatherMap dashboard.
  • CORS Errors:
    • Problem: You might encounter CORS (Cross-Origin Resource Sharing) errors if the browser blocks the request to the OpenWeatherMap API. This usually happens if you’re opening the `index.html` file directly from your file system (using `file://`) instead of serving it through a web server.
    • Solution: Use a local web server (e.g., `npx http-server` or `python -m http.server`) to serve the HTML file. This allows the browser to make cross-origin requests. If you’re still facing CORS issues, you might need to configure CORS on your local server or use a proxy.
  • Type Errors:
    • Problem: TypeScript might report type errors.
    • Solution: Carefully check your code for type mismatches, missing properties, or incorrect data types. Use the error messages provided by the TypeScript compiler to guide you. Double-check your `WeatherData` interface definition.
  • Incorrect API URL:
    • Problem: The API URL might be incorrect.
    • Solution: Double-check that the API URL in `weatherService.ts` is correct and that you’re including the necessary parameters (e.g., `q=${city}`, `appid=${API_KEY}`, `units=metric`).
  • Network Errors:
    • Problem: The browser might not be able to connect to the OpenWeatherMap API due to network issues.
    • Solution: Check your internet connection. Ensure that the OpenWeatherMap API is accessible and not experiencing any outages. Use your browser’s developer tools (Network tab) to inspect the API requests and responses.

Key Takeaways and Best Practices

  • Type Safety: TypeScript’s type system significantly improves code quality and reduces errors. Defining interfaces and types for your data is essential.
  • API Integration: Interacting with APIs is a crucial skill. Learn how to make HTTP requests, handle responses, and parse data.
  • Modularity: Breaking down your code into smaller, reusable functions and files (e.g., using a service for API calls) makes your code more organized and maintainable.
  • Error Handling: Implement robust error handling to gracefully handle potential issues like network errors or API failures.
  • User Experience: Provide informative feedback to the user, such as loading indicators or error messages, to enhance the user experience.
  • Code Formatting: Use consistent code formatting and style guidelines to improve readability. Consider using a code formatter like Prettier.

FAQ

  1. Can I use a different weather API? Yes, you can. Simply replace the API URL and adjust the data parsing logic in `weatherService.ts` to match the structure of the new API’s response.
  2. How can I add more features to the app? You could add features such as displaying the weather forecast for multiple days, adding a location search with autocomplete, displaying weather icons, and adding a background image based on the current weather conditions.
  3. How can I deploy this app? You can deploy the app to various platforms, such as Netlify, Vercel, or GitHub Pages. You’ll need to build your project (using `npx tsc` and a bundler like Parcel or Webpack) and then deploy the contents of the `dist` folder.
  4. How can I improve the UI/UX? Consider using a CSS framework (e.g., Bootstrap, Tailwind CSS) or a UI library (e.g., React, Vue.js, Angular) to create a more polished and interactive user interface. Focus on making the design responsive and user-friendly.
  5. Where can I learn more about TypeScript? The official TypeScript documentation is an excellent resource: typescriptlang.org/docs/. You can also find numerous tutorials, courses, and books online.

This weather app is a starting point. There are many ways to extend it and add more features. You can incorporate more detailed weather information, implement a location search feature, or even build a mobile-friendly version. The key is to keep practicing and experimenting with different features and technologies. The skills you gain from this project will be valuable in your journey as a TypeScript developer.