---
title: How to Build a Weather API with Express and Vercel
description: Provide real-time weather data to apps and websites with a single Express route.
url: /kb/guide/weather-api-with-express
canonical_url: "https://vercel.com/kb/guide/weather-api-with-express"
published: 2025-11-03
last_updated: 2026-06-16
authors: Jeff See, Ismael Rumzan
related:
  - /docs/functions
  - /docs/fluid-compute
  - /docs/frameworks/backend/express
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

To provide real-time weather data, you need to handle API integration, error handling and automatic scaling. With Vercel functions and Express, you can build a production-ready weather API in minutes.

In this tutorial, you will build and deploy a weather API route using the Open-Meteo API that:

1. Accepts a city name and geocodes it to coordinates
   
2. Fetches current weather data from the Open-Meteo API
   
3. Returns temperature, humidity, wind speed, and other weather metrics with optional metric/imperial unit conversion
   

## Prerequisites

- Node.js and pnpm installed locally
  
- A Vercel account
  
- Basic understanding of Express and async/await in TypeScript
  

## Build the Weather API

### 1\. Create your Express project

Start with the [Express starter](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fvercel%2Ftree%2Fmain%2Fexamples%2Fexpress&template=express) template. Once your the project is deployed on Vercel, clone the repository locally.

You can also create the project locally using the Vercel CLI with the following command

```bash
vc init express
```

### 2\. Add the Weather endpoint

Add the weather endpoint after your existing routes:

```typescript
app.get('/api/weather/:city', async (req, res) => {
  try {
    const city = req.params.city
    const units = req.query.units as string | undefined
    
    // Normalize units parameter
    const normalizedUnits = units === 'imperial' ? 'imperial' : 'metric'
    
    // Step 1: Geocode city to get coordinates
    const geoParams = new URLSearchParams({
      name: city,
      count: '1',
      language: 'en',
      format: 'json'
    })
    
    const geoResponse = await fetch(`https://geocoding-api.open-meteo.com/v1/search?${geoParams}`)
    
    if (!geoResponse.ok) {
      return res.status(geoResponse.status).json({ 
        error: 'Failed to fetch geocoding data' 
      })
    }
    
    const geoData = await geoResponse.json()
    
    if (!geoData.results || geoData.results.length === 0) {
      return res.status(404).json({ error: `City '${city}' not found` })
    }
    
    const location = geoData.results[0]
    const { name, country, latitude, longitude } = location
    
    // Step 2: Fetch current weather data
    const weatherParams: Record<string, string> = {
      latitude: latitude.toString(),
      longitude: longitude.toString(),
      current: 'temperature_2m,relative_humidity_2m,apparent_temperature,wind_speed_10m',
      timezone: 'auto'
    }
    
    // Add unit parameters for imperial if needed
    if (units === 'imperial') {
      weatherParams.temperature_unit = 'fahrenheit'
      weatherParams.wind_speed_unit = 'mph'
    }
    
    const weatherUrlParams = new URLSearchParams(weatherParams)
    const weatherResponse = await fetch(`https://api.open-meteo.com/v1/forecast?${weatherUrlParams}`)
    
    if (!weatherResponse.ok) {
      return res.status(weatherResponse.status).json({ 
        error: 'Failed to fetch weather data' 
      })
    }
    
    const weatherData = await weatherResponse.json()
    
    // Return structured weather data
    res.json({
      city: name,
      country,
      latitude,
      longitude,
      units: normalizedUnits,
      current: weatherData.current
    })
    
  } catch (error) {
    console.error('Weather API error:', error)
    res.status(500).json({ 
      error: 'Failed to fetch weather data',
      message: error instanceof Error ? error.message : 'Unknown error'
    })
  }
})
```

### 3\. Test the route locally

Install the dependencies and launch the application in dev mode:

```bash
vercel dev
```

Use `curl` to test the API route:

```bash
# Test with default metric units
curl http://localhost:3000/api/weather/london

# Test with imperial units
curl "http://localhost:3000/api/weather/san%20francisco?units=imperial"
```

### 4\. Deploy to Vercel

- Push the changes to your remote repository or run the `vercel` cli command
  
- Vercel will create a new preview deployment for your to test
  
- Merge to main branch to deploy to Production
  

> When you deploy an Express app to Vercel, the application becomes a single [Vercel Function](https://vercel.com/docs/functions) and uses [Fluid compute](https://vercel.com/docs/fluid-compute) by default.

## Summary

In this tutorial, you’ve built a real-time weather API using Express on Vercel.

You learned to:

- Structure a dynamic API route and integrate external APIs
  
- Deploy the app to Vercel as a function for automatic scaling
  

For a production application, make sure that you use a weather API that will not be rate limited based on the amount of traffic that you are expecting.

## Next steps

### Explore references

- [Explore the Express on Vercel docs](https://vercel.com/docs/frameworks/backend/express)
  
- [Express documentation](https://expressjs.com/)
  
- [Vercel Functions](https://vercel.com/docs/functions)