Build a Weather App with AI: Step-by-Step Project Guide

Your previous projects lived inside the browser. This one reaches out to the real world. You'll build a weather app that talks to an external API, fetches live data, and displays it beautifully — all with AI writing the code while you learn what every piece does.

TL;DR

You'll build a weather app that fetches live data from the OpenWeatherMap API. Type a city, get current temperature, conditions, humidity, and wind speed. This is your first project that makes real API calls — the skill that separates static pages from real web apps. Stack: HTML + CSS + vanilla JavaScript + fetch API. Cost: free. Time: ~1.5 hours.

Why Build This?

Your to-do app taught you DOM manipulation, event handling, and localStorage. Everything happened inside the browser. But real web apps don't just store data locally — they talk to servers on the internet to get and send information.

A weather app is the perfect introduction to this because:

  • You'll learn the fetch API — the built-in JavaScript tool for making HTTP requests. Every web app uses this (or something like it) to get data from servers.
  • You'll work with a real API — OpenWeatherMap gives you live weather data, and the free tier is generous enough for learning.
  • You'll handle async code — your app needs to wait for data to come back from the internet. That means async/await, which shows up in almost every modern JavaScript project.
  • You'll deal with real-world problems — network errors, API keys, rate limits, and data that doesn't look the way you expected. These are the exact things that break AI-generated code in the real world.
  • You'll understand JSON — the format servers use to send structured data. When your AI generates code that parses API responses, you'll know what it's doing.

This is the project that takes you from "I can build things that look nice" to "I can build things that actually do something." It's the gateway to every app that relies on external data — from social media dashboards to e-commerce sites to the SaaS product you'll eventually want to ship.

What You'll Need

  • An AI coding tool — Cursor, Windsurf, Claude Code, or even ChatGPT. Any tool that can generate code from a prompt works.
  • A code editor — VS Code or Cursor (which is VS Code with AI built in).
  • A free OpenWeatherMap API key — we'll walk through getting this in Step 3. It takes 2 minutes to sign up and the free tier gives you 1,000 calls per day.
  • A web browser — Chrome or Firefox with DevTools. You'll use the console to debug API responses.
  • Basic HTML/CSS/JS knowledge — if you've built the portfolio or to-do app projects, you're ready.

No framework needed. This project uses plain HTML, CSS, and JavaScript. No React, no Node.js, no build tools. Just files in a folder that you open in a browser.

Step 1: Tell Your AI What to Build

Here's the exact prompt you'll give your AI. This is specific enough to get good output on the first try, but leaves room for the AI to make design choices:

AI Prompt

"Build a weather app in a single index.html file (inline CSS and JS). Requirements: (1) A search input where the user types a city name and presses Enter or clicks a Search button. (2) Display the current weather: city name, temperature in Fahrenheit, weather condition (like 'Cloudy' or 'Clear'), an icon from the API, humidity percentage, and wind speed in mph. (3) Use the OpenWeatherMap Current Weather API (api.openweathermap.org/data/2.5/weather). (4) Use async/await with fetch. (5) Show a loading state while fetching. (6) Show a clear error message if the city isn't found or the network fails. (7) Dark theme, centered card design, clean modern UI. (8) Put the API key in a variable at the top of the script so it's easy to find and replace."

Let's break down why each part of this prompt matters:

  • "Single index.html file" — keeps it simple. No build step, no multiple files to manage. You can open it directly in a browser.
  • "Inline CSS and JS" — everything in one place for learning. You can split it into separate files later.
  • "async/await with fetch" — tells the AI to use modern syntax instead of older callback-based patterns. This is the standard way to make API calls in 2026.
  • "Show a loading state" — AI often skips this. Without it, users click search and nothing happens for 1-2 seconds while the API responds.
  • "Clear error message" — another thing AI often forgets. Without it, your app silently breaks when the city doesn't exist.
  • "API key in a variable" — prevents the AI from burying the key inside a URL string where you can't find it.

Step 2: What AI Generated

Here's the complete code your AI will produce (cleaned up and annotated). This is a working weather app — once you add your API key, it fetches real weather data.

The Complete HTML/CSS/JS

<!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>
  <style>
    /* ===== BASE STYLES ===== */
    * { margin: 0; padding: 0; box-sizing: border-box; }

    body {
      font-family: 'Segoe UI', system-ui, sans-serif;
      background: #0A0E1A;
      color: #E2E8F0;
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    /* ===== CARD CONTAINER ===== */
    .weather-app {
      background: #1A1F2E;
      border-radius: 16px;
      padding: 2rem;
      width: 100%;
      max-width: 420px;
      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
    }

    .weather-app h1 {
      text-align: center;
      font-size: 1.5rem;
      margin-bottom: 1.5rem;
      color: #60A5FA;
    }

    /* ===== SEARCH FORM ===== */
    .search-form {
      display: flex;
      gap: 0.5rem;
      margin-bottom: 1.5rem;
    }

    .search-form input {
      flex: 1;
      padding: 0.75rem 1rem;
      border: 2px solid #2D3748;
      border-radius: 8px;
      background: #0A0E1A;
      color: #E2E8F0;
      font-size: 1rem;
      outline: none;
      transition: border-color 0.2s;
    }

    .search-form input:focus {
      border-color: #60A5FA;
    }

    .search-form button {
      padding: 0.75rem 1.25rem;
      background: #3B82F6;
      color: white;
      border: none;
      border-radius: 8px;
      font-size: 1rem;
      cursor: pointer;
      transition: background 0.2s;
    }

    .search-form button:hover {
      background: #2563EB;
    }

    /* ===== WEATHER DISPLAY ===== */
    .weather-display {
      text-align: center;
      display: none;  /* Hidden until data loads */
    }

    .weather-display.active {
      display: block;
    }

    .city-name {
      font-size: 1.75rem;
      font-weight: 700;
      margin-bottom: 0.5rem;
    }

    .weather-icon {
      width: 100px;
      height: 100px;
    }

    .temperature {
      font-size: 3.5rem;
      font-weight: 800;
      color: #60A5FA;
    }

    .condition {
      font-size: 1.1rem;
      color: #94A3B8;
      text-transform: capitalize;
      margin-bottom: 1.5rem;
    }

    .details {
      display: flex;
      justify-content: space-around;
      padding-top: 1.5rem;
      border-top: 1px solid #2D3748;
    }

    .detail-item {
      text-align: center;
    }

    .detail-label {
      font-size: 0.8rem;
      color: #64748B;
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }

    .detail-value {
      font-size: 1.25rem;
      font-weight: 600;
      margin-top: 0.25rem;
    }

    /* ===== LOADING STATE ===== */
    .loading {
      text-align: center;
      padding: 2rem;
      color: #64748B;
      display: none;
    }

    .loading.active {
      display: block;
    }

    /* ===== ERROR STATE ===== */
    .error {
      text-align: center;
      padding: 1.5rem;
      color: #F87171;
      background: rgba(248, 113, 113, 0.1);
      border-radius: 8px;
      display: none;
    }

    .error.active {
      display: block;
    }
  </style>
</head>
<body>

  <div class="weather-app">
    <h1>🌤 Weather</h1>

    <form class="search-form" id="search-form">
      <input
        type="text"
        id="city-input"
        placeholder="Enter city name..."
        autocomplete="off"
        required
      >
      <button type="submit">Search</button>
    </form>

    <div class="loading" id="loading">
      Fetching weather data...
    </div>

    <div class="error" id="error"></div>

    <div class="weather-display" id="weather-display">
      <div class="city-name" id="city-name"></div>
      <img class="weather-icon" id="weather-icon" alt="Weather icon">
      <div class="temperature" id="temperature"></div>
      <div class="condition" id="condition"></div>
      <div class="details">
        <div class="detail-item">
          <div class="detail-label">Humidity</div>
          <div class="detail-value" id="humidity"></div>
        </div>
        <div class="detail-item">
          <div class="detail-label">Wind</div>
          <div class="detail-value" id="wind"></div>
        </div>
        <div class="detail-item">
          <div class="detail-label">Feels Like</div>
          <div class="detail-value" id="feels-like"></div>
        </div>
      </div>
    </div>
  </div>

  <script>
    // ========================================
    // YOUR API KEY — Replace this with yours
    // ========================================
    const API_KEY = 'YOUR_API_KEY_HERE'
    const BASE_URL = 'https://api.openweathermap.org/data/2.5/weather'

    // ===== DOM REFERENCES =====
    const form = document.getElementById('search-form')
    const input = document.getElementById('city-input')
    const weatherDisplay = document.getElementById('weather-display')
    const loadingEl = document.getElementById('loading')
    const errorEl = document.getElementById('error')

    // ===== FETCH WEATHER DATA =====
    async function getWeather(city) {
      // Build the URL with query parameters
      const url = `${BASE_URL}?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=imperial`

      // Show loading, hide everything else
      showLoading()

      try {
        // Make the API request
        const response = await fetch(url)

        // Check if the response was successful
        if (!response.ok) {
          if (response.status === 404) {
            throw new Error(`City "${city}" not found. Check the spelling and try again.`)
          }
          if (response.status === 401) {
            throw new Error('Invalid API key. Check your OpenWeatherMap API key.')
          }
          throw new Error('Something went wrong. Please try again.')
        }

        // Parse the JSON response
        const data = await response.json()

        // Display the weather data
        displayWeather(data)

      } catch (error) {
        showError(error.message)
      }
    }

    // ===== DISPLAY WEATHER =====
    function displayWeather(data) {
      // Hide loading and error
      loadingEl.classList.remove('active')
      errorEl.classList.remove('active')

      // Populate the weather display
      document.getElementById('city-name').textContent =
        `${data.name}, ${data.sys.country}`

      document.getElementById('temperature').textContent =
        `${Math.round(data.main.temp)}°F`

      document.getElementById('condition').textContent =
        data.weather[0].description

      document.getElementById('weather-icon').src =
        `https://openweathermap.org/img/wn/${data.weather[0].icon}@2x.png`

      document.getElementById('humidity').textContent =
        `${data.main.humidity}%`

      document.getElementById('wind').textContent =
        `${Math.round(data.wind.speed)} mph`

      document.getElementById('feels-like').textContent =
        `${Math.round(data.main.feels_like)}°F`

      // Show the weather display
      weatherDisplay.classList.add('active')
    }

    // ===== UI STATE HELPERS =====
    function showLoading() {
      loadingEl.classList.add('active')
      weatherDisplay.classList.remove('active')
      errorEl.classList.remove('active')
    }

    function showError(message) {
      loadingEl.classList.remove('active')
      weatherDisplay.classList.remove('active')
      errorEl.textContent = message
      errorEl.classList.add('active')
    }

    // ===== EVENT HANDLER =====
    form.addEventListener('submit', (e) => {
      e.preventDefault()
      const city = input.value.trim()
      if (city) {
        getWeather(city)
      }
    })
  </script>

</body>
</html>

That's the full app. Copy this code, replace YOUR_API_KEY_HERE with your actual OpenWeatherMap API key (we'll get that in the next step), and open the file in a browser. You'll have a working weather app.

Step 3: Getting Your API Key

The weather app needs to identify itself to OpenWeatherMap's server. That's what an API key does — it's like a membership card that says "I'm allowed to request data." Here's how to get yours:

  1. Go to openweathermap.org/api and click "Sign Up" (or "Sign In" if you already have an account).
  2. Create a free account. You just need an email. No credit card.
  3. Once logged in, go to "API keys" in your profile. You'll see a default key already generated for you.
  4. Copy that key. It looks like a long random string: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
  5. Paste it into your code, replacing YOUR_API_KEY_HERE.

⚠️ New API keys take up to 2 hours to activate. If you just created your account and the app shows a 401 error ("Invalid API key"), wait a couple hours and try again. This trips up almost everyone — it's not your code, it's OpenWeatherMap's activation delay.

⚠️ Don't share your API key publicly. If you push this code to GitHub, anyone can see your key and use your quota. For a learning project, this isn't a disaster (the free tier is limited anyway). For production apps, you'd use environment variables on a backend server. We'll cover that in the "What AI Gets Wrong" section below.

Step 4: Understanding the Code

Let's walk through every important piece. You don't need to memorize this — you need to understand it well enough to debug problems and tell your AI what to fix.

The API URL Structure

const url = `${BASE_URL}?q=${encodeURIComponent(city)}&appid=${API_KEY}&units=imperial`

// This builds a URL like:
// https://api.openweathermap.org/data/2.5/weather?q=Seattle&appid=abc123&units=imperial

This URL has four parts:

  • BASE_URL — the server address (where to send the request)
  • ?q=Seattle — the query parameter (what city you want)
  • &appid=abc123 — your API key (proving you're authorized)
  • &units=imperial — tells the API to return Fahrenheit and mph (use metric for Celsius and km/h)

encodeURIComponent(city) handles city names with spaces or special characters. "New York" becomes "New%20York" so the URL doesn't break. Your AI usually includes this, but sometimes it doesn't — and the app works fine until someone searches for "San Francisco" and gets an error.

async/await — Waiting for the Internet

async function getWeather(city) {
  const response = await fetch(url)
  const data = await response.json()
}

This is the pattern you'll see in every app that talks to a server:

  1. async — marks the function as asynchronous (it does something that takes time)
  2. await fetch(url) — sends the request and waits for the response to come back. Without await, JavaScript would try to use the response before it arrives — and crash.
  3. await response.json() — the response comes as raw text. .json() converts it into a JavaScript object you can work with. This also takes time (it's reading the response body), so it needs await too.

If you've never seen async/await, check out What Is Async/Await? for the full explanation. The short version: it lets your code say "do this thing, wait for it to finish, then continue" — without freezing the entire page.

The API Response (What the Server Sends Back)

When you call the OpenWeatherMap API for Seattle, you get back a JSON object that looks like this:

{
  "name": "Seattle",
  "main": {
    "temp": 52.3,
    "feels_like": 49.1,
    "humidity": 78
  },
  "weather": [
    {
      "main": "Clouds",
      "description": "overcast clouds",
      "icon": "04d"
    }
  ],
  "wind": {
    "speed": 8.5
  },
  "sys": {
    "country": "US"
  }
}

Notice the nesting. Temperature isn't at data.temp — it's at data.main.temp. The weather description isn't at data.description — it's at data.weather[0].description (the [0] means "the first item in the array").

This is one of the most common sources of bugs in API projects. Your AI usually gets the paths right because it knows the OpenWeatherMap API format, but if something shows "undefined" on screen, this is the first thing to check.

💡 Pro debugging tip: Add console.log(data) right after const data = await response.json(). Open your browser's DevTools (F12 → Console tab) and you'll see the entire response object. Click the arrows to expand it and see every field. This is how you figure out the exact path to any piece of data.

Error Handling — The try/catch Block

try {
  const response = await fetch(url)

  if (!response.ok) {
    if (response.status === 404) {
      throw new Error(`City "${city}" not found.`)
    }
    if (response.status === 401) {
      throw new Error('Invalid API key.')
    }
    throw new Error('Something went wrong.')
  }

  const data = await response.json()
  displayWeather(data)

} catch (error) {
  showError(error.message)
}

Here's what's happening:

  • try { ... } — "Try running this code. If anything goes wrong, jump to the catch block instead of crashing."
  • response.ok — a boolean that's true for successful responses (status 200-299) and false for errors (400, 401, 404, 500, etc.).
  • throw new Error() — manually creates an error with a custom message. This triggers the catch block.
  • catch (error) — catches any error that happened in the try block — whether it's a network failure, an API error, or something you threw manually. The error.message is the text you passed to new Error().

Without try/catch, your app crashes silently when the network is down or the city doesn't exist. The user sees nothing. With it, you show a helpful error message. This is the pattern your AI should use every time it makes an API call — but often doesn't.

UI State Management (Loading, Error, Display)

function showLoading() {
  loadingEl.classList.add('active')
  weatherDisplay.classList.remove('active')
  errorEl.classList.remove('active')
}

function showError(message) {
  loadingEl.classList.remove('active')
  weatherDisplay.classList.remove('active')
  errorEl.textContent = message
  errorEl.classList.add('active')
}

The app has three mutually exclusive states: loading, error, or showing weather data. These functions toggle CSS classes to show one and hide the others. The CSS uses display: none by default and display: block when the .active class is added. Simple, effective, and the same basic pattern used in every frontend framework — just without the framework.

Step 5: Common Issues and Fixes

Issue: "Invalid API key" (401 Error)

Cause: Your API key is new and hasn't been activated yet, or you pasted it incorrectly.

Fix: Wait 2 hours after creating the key. Double-check that you copied the entire key with no extra spaces. The key should be a string of letters and numbers — no quotes around it in the URL, but quotes around it in the JavaScript variable.

// ✅ Correct
const API_KEY = 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'

// ❌ Wrong — extra space at the end
const API_KEY = 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 '

// ❌ Wrong — still has placeholder
const API_KEY = 'YOUR_API_KEY_HERE'

Issue: Temperature Shows "undefined"

Cause: Your code is accessing the wrong property path in the API response.

Fix: The temperature lives at data.main.temp, not data.temp or data.temperature. Add console.log(data) to see the actual response structure. Tell your AI: "The temperature data is at data.main.temp — update the display function."

Issue: "Failed to fetch" (Network Error)

Cause: No internet connection, the API server is down, or there's a CORS issue.

Fix: Check your internet connection first. Then open DevTools (F12 → Network tab) and look at the failed request. If you see a CORS error, it usually means you're calling the API from a file:// URL instead of a local server.

// ❌ If your browser's address bar shows:
// file:///C:/Users/you/weather-app/index.html
// Some browsers block API calls from file:// URLs

// ✅ Run a local server instead:
// Open terminal in your project folder and run:
npx serve .
// Then open http://localhost:3000

Issue: Weather Icon Doesn't Load

Cause: The icon URL is wrong or the icon code from the API wasn't used correctly.

Fix: OpenWeatherMap icons follow this pattern: https://openweathermap.org/img/wn/{icon_code}@2x.png. The icon code comes from data.weather[0].icon (like "04d" for overcast clouds). Make sure your code uses this path and not a different icon service.

Issue: Search Works Once, Then Breaks

Cause: The form is submitting and reloading the page.

Fix: Make sure you have e.preventDefault() in your form submit handler. This stops the browser from actually submitting the form (which would reload the page and lose your JavaScript state).

form.addEventListener('submit', (e) => {
  e.preventDefault()  // <-- This line is critical
  const city = input.value.trim()
  if (city) getWeather(city)
})

What AI Gets Wrong

AI coding tools will get you 90% of the way there with a weather app — but that last 10% is where real bugs live. Here's what your AI will likely miss or do poorly:

1. Hardcoded API Keys

Every AI tool will put the API key directly in the JavaScript code. For a learning project, this is fine. For anything you deploy publicly, it's a problem — anyone who views your page source can steal your key.

The real fix: Use a backend proxy. Your JavaScript calls your own server, and your server calls OpenWeatherMap with the key stored as an environment variable. Your AI can set this up, but it won't do it unless you ask.

Prompt to Fix This

"Create a simple Express.js backend proxy for my weather app. The backend should have one endpoint — GET /api/weather?city=Seattle — that calls the OpenWeatherMap API using an API key from process.env.WEATHER_API_KEY, then returns the data to the frontend. The frontend should call my backend instead of OpenWeatherMap directly."

2. No Error Handling

If you don't explicitly ask for error handling, many AI tools will generate a bare fetch call with no try/catch. The app works perfectly when you search for "Seattle" — and silently breaks when you search for "asdfgh" or lose internet.

Always tell your AI: "Add error handling for network failures, invalid city names, and API errors. Show user-friendly error messages."

3. No Loading State

API calls take 200ms to 2 seconds depending on the server and your connection. Without a loading indicator, the user clicks "Search" and nothing happens — they think the app is broken and click again (sending a duplicate request).

Always tell your AI: "Show a loading indicator while fetching data. Disable the search button during the request to prevent duplicate submissions."

4. No Input Validation

AI-generated code often doesn't check if the input is empty or contains only whitespace. Submitting an empty search sends a useless API request and returns confusing errors.

// ❌ What AI often generates
form.addEventListener('submit', (e) => {
  e.preventDefault()
  getWeather(input.value)  // Empty string? No problem! (narrator: it was a problem)
})

// ✅ What it should generate
form.addEventListener('submit', (e) => {
  e.preventDefault()
  const city = input.value.trim()
  if (!city) return  // Don't search for nothing
  getWeather(city)
})

5. Hardcoded Units

AI usually picks either Fahrenheit or Celsius and hardcodes it. Half your users will want the other one. A quick fix is adding a toggle button, but the AI won't include this unless you ask.

Level Up: Add These Features

Once your basic weather app is working, use these prompts to extend it. Each one teaches you a new concept:

1. Five-Day Forecast

AI Prompt

"Add a 5-day forecast below the current weather. Use the OpenWeatherMap forecast endpoint (api.openweathermap.org/data/2.5/forecast). Show the day name, high/low temperature, and weather icon for each day. Display them in a horizontal scrollable row."

What you'll learn: Working with arrays of data, looping through API results, and rendering multiple items from a single response. The forecast API returns data in 3-hour intervals — you'll need to filter it to get one entry per day.

2. Geolocation (Auto-Detect City)

AI Prompt

"Add a 'Use My Location' button that detects the user's city using the browser's Geolocation API (navigator.geolocation). Pass the latitude and longitude to the OpenWeatherMap API instead of a city name. Handle the case where the user denies location permission."

What you'll learn: Browser APIs beyond the DOM, user permissions, and using latitude/longitude coordinates instead of city names. The OpenWeatherMap API accepts lat and lon parameters.

3. Fahrenheit/Celsius Toggle

AI Prompt

"Add a toggle button to switch between Fahrenheit and Celsius. When toggled, re-fetch the weather data with units=imperial or units=metric. Remember the user's preference in localStorage."

What you'll learn: Storing user preferences, re-fetching data with different parameters, and the localStorage pattern you learned in the to-do app.

4. Recent Searches History

AI Prompt

"Add a recent searches feature. Save the last 5 cities searched to localStorage. Display them as clickable chips below the search bar. Clicking a chip searches for that city again. Add a 'Clear History' button."

What you'll learn: Combining localStorage with dynamic UI — similar to the to-do app but applied in a new context. You'll also practice array manipulation (limiting to 5 items, preventing duplicates).

5. Weather-Based Background

AI Prompt

"Change the app's background gradient based on the current weather condition. Sunny = warm yellow/orange gradient, cloudy = gray gradient, rainy = dark blue gradient, snowy = light blue/white gradient. Use the weather condition code from the API to determine which gradient to show. Animate the transition when it changes."

What you'll learn: Conditional styling based on data, CSS gradients, and CSS transitions — turning raw data into visual design decisions.

What to Learn Next

This weather app introduced you to some of the most important concepts in web development. Here's where to go deeper:

Frequently Asked Questions

Is the OpenWeatherMap API free?

Yes. OpenWeatherMap offers a free tier that gives you 1,000 API calls per day and 60 calls per minute. That's more than enough for a personal weather app. You just need to sign up for a free account at openweathermap.org and generate an API key. The key can take up to 2 hours to activate after creation.

Why is my weather app showing "undefined" instead of temperature?

This usually means the API response structure doesn't match what your code expects. The OpenWeatherMap API nests temperature inside response.main.temp. If your code tries to access response.temp or response.temperature directly, you'll get undefined. Add console.log(data) after parsing the response to see its actual structure, then update your code to match.

What is the fetch API and why does the weather app use it?

The fetch API is a built-in JavaScript function that makes HTTP requests — it's how your browser asks another server for data. Your weather app uses fetch to send a request to the OpenWeatherMap server, which responds with weather data in JSON format. It's asynchronous (uses async/await), meaning the page doesn't freeze while waiting for the response. Learn more in What Is the Fetch API?

Can I build a weather app without a backend server?

Yes, for a personal project. The OpenWeatherMap API supports direct browser requests (CORS-enabled), so you can call it from plain HTML/JavaScript without a backend. However, this means your API key is visible in the source code. For a production app, you'd want a backend proxy to keep your key secret using environment variables. For learning and personal use, direct browser calls work fine.

What's the difference between this weather app and the to-do app project?

The to-do app taught you DOM manipulation, event handling, and localStorage — everything stays in the browser. The weather app introduces external API calls: your code talks to a server on the internet, waits for a response, and displays that live data. This is a fundamental shift — your app now depends on the outside world, which means new challenges like network errors, API keys, rate limits, and asynchronous code.