Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on building a Weather App with External API Integration! ๐ In this guide, weโll explore how to create a fully functional weather application that fetches real-time data from external APIs.
Youโll discover how TypeScript makes API integration safer and more predictable. Whether youโre building weather widgets ๐ฆ๏ธ, dashboard components ๐, or full-featured weather applications ๐, understanding how to work with external APIs in TypeScript is essential for modern web development.
By the end of this tutorial, youโll have built your own weather app and mastered the art of type-safe API integration! Letโs dive in! ๐โโ๏ธ
๐ Understanding Weather App API Integration
๐ค What is API Integration?
API integration is like having a magic portal ๐ช to weather data from around the world. Think of it as a restaurant menu ๐ where you order specific weather information and the API kitchen prepares and serves it to you in a format your app can understand.
In TypeScript terms, API integration involves:
- โจ Making HTTP requests to external services
- ๐ Handling responses with proper types
- ๐ก๏ธ Managing errors gracefully
- ๐ Transforming data for your appโs needs
๐ก Why Use TypeScript for API Integration?
Hereโs why developers love TypeScript for API work:
- Type Safety ๐: Know exactly what data youโre getting
- Better IDE Support ๐ป: Autocomplete API responses
- Error Prevention ๐: Catch API contract changes early
- Refactoring Confidence ๐ง: Update API handling safely
Real-world example: Imagine your weather app expecting temperature in Celsius but the API suddenly returns Fahrenheit. With TypeScript, youโll catch this mismatch immediately! ๐ก๏ธ
๐ง Basic Syntax and Usage
๐ Setting Up API Types
Letโs start with defining our weather data types:
// ๐ Hello, Weather API!
interface WeatherData {
temperature: number; // ๐ก๏ธ Current temperature
humidity: number; // ๐ง Humidity percentage
description: string; // โ๏ธ Weather description
windSpeed: number; // ๐จ Wind speed
location: {
city: string; // ๐๏ธ City name
country: string; // ๐ Country code
};
}
// ๐จ API Response type
interface WeatherAPIResponse {
main: {
temp: number;
humidity: number;
};
weather: Array<{
description: string;
}>;
wind: {
speed: number;
};
name: string;
sys: {
country: string;
};
}
๐ก Explanation: Notice how we define two interfaces - one for the raw API response and one for our cleaned-up app data!
๐ฏ Basic API Call Pattern
Hereโs how to fetch weather data safely:
// ๐๏ธ Weather service class
class WeatherService {
private apiKey: string = "your-api-key"; // ๐ Keep it secret!
private baseUrl: string = "https://api.openweathermap.org/data/2.5";
// ๐ค๏ธ Fetch weather for a city
async getWeatherByCity(city: string): Promise<WeatherData> {
try {
const url = `${this.baseUrl}/weather?q=${city}&appid=${this.apiKey}&units=metric`;
// ๐ Make the API call
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Weather API error! status: ${response.status}`);
}
// ๐ฆ Parse the response
const data: WeatherAPIResponse = await response.json();
// ๐จ Transform to our format
return this.transformWeatherData(data);
} catch (error) {
console.error("โ Weather fetch failed:", error);
throw error;
}
}
// ๐ Transform API data to our format
private transformWeatherData(apiData: WeatherAPIResponse): WeatherData {
return {
temperature: Math.round(apiData.main.temp),
humidity: apiData.main.humidity,
description: apiData.weather[0]?.description || "Unknown",
windSpeed: apiData.wind.speed,
location: {
city: apiData.name,
country: apiData.sys.country
}
};
}
}
๐ก Practical Examples
๐ Example 1: Complete Weather Dashboard
Letโs build a real weather dashboard:
// ๐ Weather dashboard component
interface WeatherState {
data: WeatherData | null;
loading: boolean;
error: string | null;
emoji: string;
}
class WeatherDashboard {
private weatherService: WeatherService;
private state: WeatherState = {
data: null,
loading: false,
error: null,
emoji: "๐"
};
constructor() {
this.weatherService = new WeatherService();
}
// ๐ Search for weather
async searchWeather(city: string): Promise<void> {
this.setState({ loading: true, error: null });
try {
const weatherData = await this.weatherService.getWeatherByCity(city);
// ๐จ Pick emoji based on weather
const emoji = this.getWeatherEmoji(weatherData.description);
this.setState({
data: weatherData,
loading: false,
emoji
});
console.log(`${emoji} Weather in ${city}: ${weatherData.temperature}ยฐC`);
} catch (error) {
this.setState({
loading: false,
error: "Failed to fetch weather data ๐ข"
});
}
}
// ๐ญ Get emoji for weather
private getWeatherEmoji(description: string): string {
const weatherEmojis: Record<string, string> = {
"clear": "โ๏ธ",
"clouds": "โ๏ธ",
"rain": "๐ง๏ธ",
"snow": "โ๏ธ",
"thunderstorm": "โ๏ธ",
"mist": "๐ซ๏ธ"
};
const key = Object.keys(weatherEmojis).find(k =>
description.toLowerCase().includes(k)
);
return weatherEmojis[key || ""] || "๐";
}
// ๐ Display weather stats
displayWeatherStats(): void {
if (!this.state.data) return;
const { data, emoji } = this.state;
console.log(`
${emoji} Weather Report for ${data.location.city}, ${data.location.country}
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ก๏ธ Temperature: ${data.temperature}ยฐC
๐ง Humidity: ${data.humidity}%
๐จ Wind Speed: ${data.windSpeed} m/s
โ๏ธ Conditions: ${data.description}
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
`);
}
// ๐ Update state
private setState(newState: Partial<WeatherState>): void {
this.state = { ...this.state, ...newState };
}
}
// ๐ฎ Let's use it!
const dashboard = new WeatherDashboard();
await dashboard.searchWeather("London");
dashboard.displayWeatherStats();
๐ฏ Try it yourself: Add a temperature unit converter (Celsius/Fahrenheit) feature!
๐ฎ Example 2: Weather Forecast with Multiple Cities
Letโs make it more advanced:
// ๐ Multi-city weather tracker
interface CityWeather extends WeatherData {
lastUpdated: Date;
isFavorite: boolean;
}
class WeatherTracker {
private cities: Map<string, CityWeather> = new Map();
private weatherService: WeatherService;
private updateInterval: number = 600000; // ๐ 10 minutes
constructor() {
this.weatherService = new WeatherService();
}
// โ Add city to tracker
async addCity(cityName: string, isFavorite: boolean = false): Promise<void> {
try {
const weatherData = await this.weatherService.getWeatherByCity(cityName);
const cityWeather: CityWeather = {
...weatherData,
lastUpdated: new Date(),
isFavorite
};
this.cities.set(cityName.toLowerCase(), cityWeather);
console.log(`โ
Added ${cityName} to tracker!`);
// ๐ Set up auto-update for favorites
if (isFavorite) {
this.setupAutoUpdate(cityName);
}
} catch (error) {
console.error(`โ Failed to add ${cityName}`);
}
}
// ๐ Get favorite cities weather
getFavoriteWeather(): CityWeather[] {
return Array.from(this.cities.values())
.filter(city => city.isFavorite)
.sort((a, b) => b.temperature - a.temperature); // ๐ฅ Hottest first!
}
// ๐ฅถ Find coldest city
findColdestCity(): CityWeather | undefined {
const cities = Array.from(this.cities.values());
if (cities.length === 0) return undefined;
return cities.reduce((coldest, city) =>
city.temperature < coldest.temperature ? city : coldest
);
}
// ๐ Weather comparison
compareWeather(city1: string, city2: string): void {
const weather1 = this.cities.get(city1.toLowerCase());
const weather2 = this.cities.get(city2.toLowerCase());
if (!weather1 || !weather2) {
console.log("โ ๏ธ One or both cities not found!");
return;
}
const tempDiff = Math.abs(weather1.temperature - weather2.temperature);
const warmer = weather1.temperature > weather2.temperature ? city1 : city2;
console.log(`
๐ก๏ธ Temperature Comparison:
${city1}: ${weather1.temperature}ยฐC
${city2}: ${weather2.temperature}ยฐC
๐ ${warmer} is warmer by ${tempDiff}ยฐC!
`);
}
// ๐ Auto-update weather
private setupAutoUpdate(cityName: string): void {
setInterval(async () => {
console.log(`๐ Updating weather for ${cityName}...`);
await this.addCity(cityName, true);
}, this.updateInterval);
}
}
// ๐ Track multiple cities!
const tracker = new WeatherTracker();
await tracker.addCity("Tokyo", true);
await tracker.addCity("New York", true);
await tracker.addCity("London");
tracker.compareWeather("Tokyo", "New York");
๐ Advanced Concepts
๐งโโ๏ธ Advanced Error Handling with Custom Types
When youโre ready to level up, implement robust error handling:
// ๐ฏ Custom error types
type WeatherErrorType =
| "NETWORK_ERROR"
| "INVALID_API_KEY"
| "CITY_NOT_FOUND"
| "RATE_LIMIT_EXCEEDED";
interface WeatherError {
type: WeatherErrorType;
message: string;
retry: boolean;
emoji: string;
}
// ๐ก๏ธ Advanced weather service with error handling
class RobustWeatherService {
private retryCount: number = 0;
private maxRetries: number = 3;
// ๐ Fetch with retry logic
async fetchWeatherWithRetry(city: string): Promise<WeatherData | WeatherError> {
try {
const result = await this.getWeatherByCity(city);
this.retryCount = 0; // ๐ฏ Reset on success
return result;
} catch (error) {
const weatherError = this.handleWeatherError(error);
if (weatherError.retry && this.retryCount < this.maxRetries) {
this.retryCount++;
console.log(`๐ Retry attempt ${this.retryCount}...`);
// โณ Wait before retry
await this.delay(1000 * this.retryCount);
return this.fetchWeatherWithRetry(city);
}
return weatherError;
}
}
// ๐จ Handle different error types
private handleWeatherError(error: any): WeatherError {
if (error.message.includes("404")) {
return {
type: "CITY_NOT_FOUND",
message: "City not found! Check the spelling ๐๏ธ",
retry: false,
emoji: "๐"
};
}
if (error.message.includes("401")) {
return {
type: "INVALID_API_KEY",
message: "Invalid API key! Check your credentials ๐",
retry: false,
emoji: "๐ซ"
};
}
if (error.message.includes("429")) {
return {
type: "RATE_LIMIT_EXCEEDED",
message: "Too many requests! Take a break โ",
retry: true,
emoji: "โฑ๏ธ"
};
}
return {
type: "NETWORK_ERROR",
message: "Network issue! Check your connection ๐",
retry: true,
emoji: "๐ก"
};
}
// โฐ Delay helper
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
๐๏ธ Type-Safe API Response Caching
For the brave developers, implement smart caching:
// ๐ Cache with TypeScript generics
interface CacheEntry<T> {
data: T;
timestamp: number;
ttl: number; // ๐ Time to live in ms
}
class WeatherCache<T> {
private cache: Map<string, CacheEntry<T>> = new Map();
// ๐พ Store in cache
set(key: string, data: T, ttl: number = 300000): void {
this.cache.set(key, {
data,
timestamp: Date.now(),
ttl
});
console.log(`๐พ Cached: ${key}`);
}
// ๐ฆ Get from cache
get(key: string): T | null {
const entry = this.cache.get(key);
if (!entry) return null;
// ๐ Check if expired
if (Date.now() - entry.timestamp > entry.ttl) {
this.cache.delete(key);
console.log(`๐๏ธ Cache expired: ${key}`);
return null;
}
console.log(`โจ Cache hit: ${key}`);
return entry.data;
}
// ๐งน Clean expired entries
cleanup(): void {
const now = Date.now();
let cleaned = 0;
this.cache.forEach((entry, key) => {
if (now - entry.timestamp > entry.ttl) {
this.cache.delete(key);
cleaned++;
}
});
if (cleaned > 0) {
console.log(`๐งน Cleaned ${cleaned} expired entries`);
}
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Not Handling API Keys Securely
// โ Wrong way - exposing API key!
const apiKey = "sk_12345_secret_key_visible"; // ๐ฐ Never do this!
const url = `https://api.weather.com?key=${apiKey}`;
// โ
Correct way - use environment variables!
const apiKey = process.env.WEATHER_API_KEY; // ๐ก๏ธ Keep it safe!
if (!apiKey) {
throw new Error("โ ๏ธ WEATHER_API_KEY not found in environment!");
}
๐คฏ Pitfall 2: Not Validating API Responses
// โ Dangerous - trusting API blindly!
async function getWeather(city: string) {
const response = await fetch(`/weather/${city}`);
const data = await response.json();
return data.temperature; // ๐ฅ What if temperature doesn't exist?
}
// โ
Safe - validate everything!
async function getWeather(city: string): Promise<number | null> {
const response = await fetch(`/weather/${city}`);
if (!response.ok) {
console.error("โ ๏ธ API request failed!");
return null;
}
const data = await response.json();
// ๐ก๏ธ Validate the response structure
if (typeof data?.temperature !== 'number') {
console.error("โ ๏ธ Invalid temperature data!");
return null;
}
return data.temperature; // โ
Safe now!
}
๐ ๏ธ Best Practices
- ๐ฏ Type Everything: Define interfaces for all API responses
- ๐ Environment Variables: Never hardcode API keys
- ๐ก๏ธ Error Boundaries: Always handle API failures gracefully
- ๐จ Transform Data: Convert API responses to your appโs format
- โจ Cache Wisely: Reduce API calls with smart caching
- ๐ Validate Responses: Never trust external data blindly
- ๐ Rate Limiting: Respect API limits and implement throttling
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Weather Comparison App
Create a weather comparison app with these features:
๐ Requirements:
- โ Compare weather between 3 cities
- ๐ท๏ธ Add weather alerts (rain, snow, extreme temps)
- ๐ค Save favorite locations
- ๐ Show 5-day forecast
- ๐จ Weather-based theme colors!
๐ Bonus Points:
- Add weather history tracking
- Implement weather notifications
- Create a weather mood detector
๐ก Solution
๐ Click to see solution
// ๐ฏ Complete weather comparison app!
interface WeatherAlert {
type: "rain" | "snow" | "heat" | "cold";
severity: "low" | "medium" | "high";
message: string;
emoji: string;
}
interface ExtendedWeatherData extends WeatherData {
forecast: Array<{
date: Date;
temperature: number;
description: string;
}>;
alerts: WeatherAlert[];
}
class WeatherComparisonApp {
private weatherService: WeatherService;
private favorites: Set<string> = new Set();
private cache: WeatherCache<ExtendedWeatherData>;
constructor() {
this.weatherService = new WeatherService();
this.cache = new WeatherCache();
}
// ๐ Compare multiple cities
async compareCities(cities: string[]): Promise<void> {
console.log("๐ Weather Comparison Dashboard");
console.log("โ".repeat(50));
const weatherPromises = cities.map(city => this.getExtendedWeather(city));
const results = await Promise.all(weatherPromises);
// ๐ Sort by temperature
const sortedResults = results
.filter((r): r is ExtendedWeatherData => r !== null)
.sort((a, b) => b.temperature - a.temperature);
sortedResults.forEach((weather, index) => {
const medal = index === 0 ? "๐ฅ" : index === 1 ? "๐ฅ" : "๐ฅ";
console.log(`
${medal} ${weather.location.city}, ${weather.location.country}
๐ก๏ธ ${weather.temperature}ยฐC | ${weather.description}
๐ง ${weather.humidity}% | ๐จ ${weather.windSpeed} m/s
${this.getAlertSummary(weather.alerts)}
`);
});
this.showTemperatureChart(sortedResults);
}
// ๐ Get extended weather with forecast
private async getExtendedWeather(city: string): Promise<ExtendedWeatherData | null> {
// ๐พ Check cache first
const cached = this.cache.get(city);
if (cached) return cached;
try {
const basicWeather = await this.weatherService.getWeatherByCity(city);
// ๐ฏ Generate alerts
const alerts = this.generateWeatherAlerts(basicWeather);
// ๐
Mock 5-day forecast
const forecast = this.generateForecast(basicWeather);
const extendedData: ExtendedWeatherData = {
...basicWeather,
forecast,
alerts
};
// ๐พ Cache for 5 minutes
this.cache.set(city, extendedData, 300000);
return extendedData;
} catch (error) {
console.error(`โ Failed to get weather for ${city}`);
return null;
}
}
// ๐จ Generate weather alerts
private generateWeatherAlerts(weather: WeatherData): WeatherAlert[] {
const alerts: WeatherAlert[] = [];
// ๐ฅต Heat alert
if (weather.temperature > 35) {
alerts.push({
type: "heat",
severity: "high",
message: "Extreme heat warning!",
emoji: "๐ฅ"
});
}
// ๐ฅถ Cold alert
if (weather.temperature < 0) {
alerts.push({
type: "cold",
severity: weather.temperature < -10 ? "high" : "medium",
message: "Freezing temperatures!",
emoji: "โ๏ธ"
});
}
// ๐ง๏ธ Rain alert
if (weather.description.includes("rain")) {
alerts.push({
type: "rain",
severity: "medium",
message: "Don't forget your umbrella!",
emoji: "โ"
});
}
return alerts;
}
// ๐ Show temperature chart
private showTemperatureChart(cities: ExtendedWeatherData[]): void {
console.log("\n๐ Temperature Chart:");
cities.forEach(city => {
const bars = "โ".repeat(Math.max(1, Math.floor(city.temperature / 2)));
const color = city.temperature > 25 ? "๐ฅ" :
city.temperature > 15 ? "๐จ" : "๐ฆ";
console.log(`${city.location.city.padEnd(15)} ${color} ${bars} ${city.temperature}ยฐC`);
});
}
// ๐
Generate mock forecast
private generateForecast(baseWeather: WeatherData): ExtendedWeatherData["forecast"] {
return Array.from({ length: 5 }, (_, i) => ({
date: new Date(Date.now() + (i + 1) * 24 * 60 * 60 * 1000),
temperature: baseWeather.temperature + Math.floor(Math.random() * 10 - 5),
description: ["sunny", "cloudy", "rainy", "partly cloudy"][Math.floor(Math.random() * 4)]
}));
}
// ๐จ Get alert summary
private getAlertSummary(alerts: WeatherAlert[]): string {
if (alerts.length === 0) return " โ
No weather alerts";
return alerts
.map(alert => ` ${alert.emoji} ${alert.message}`)
.join("\n");
}
// โญ Add to favorites
addFavorite(city: string): void {
this.favorites.add(city);
console.log(`โญ Added ${city} to favorites!`);
}
// ๐จ Get theme color based on weather
getWeatherTheme(temperature: number): string {
if (temperature > 30) return "๐ฅ Hot Orange";
if (temperature > 20) return "โ๏ธ Sunny Yellow";
if (temperature > 10) return "๐ค๏ธ Pleasant Blue";
if (temperature > 0) return "โ๏ธ Cool Cyan";
return "๐ง Icy White";
}
}
// ๐ฎ Test it out!
const app = new WeatherComparisonApp();
await app.compareCities(["London", "Tokyo", "New York"]);
app.addFavorite("Tokyo");
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create weather apps with real API integration ๐ช
- โ Handle API responses with proper TypeScript types ๐ก๏ธ
- โ Implement error handling for robust applications ๐ฏ
- โ Build caching systems for better performance ๐
- โ Transform API data into user-friendly formats! ๐
Remember: External APIs are powerful tools, and TypeScript makes them safer and easier to work with! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve built a complete weather application with external API integration!
Hereโs what to do next:
- ๐ป Get a real weather API key and test the app
- ๐๏ธ Add more features like weather maps or historical data
- ๐ Move on to our next tutorial: E-commerce Backend Development
- ๐ Share your weather app with the world!
Remember: Every weather app started with a single API call. Keep building, keep learning, and most importantly, have fun! ๐
Happy coding! ๐๐โจ