---
title: How to Build a Weather API with Nitro and Vercel
description: Provide real-time weather data to apps and websites with a single Nitro route, Vercel cache storage, and Observability.
url: /kb/guide/weather-api-with-nitro
canonical_url: "https://vercel.com/kb/guide/weather-api-with-nitro"
last_updated: 2026-07-16
authors: Pooya Pasa, Ismael Rumzan
related:
  - /docs/observability
  - /docs/functions
  - /docs/fluid-compute
  - /docs/data-cache
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

To provide real-time weather data, you need to handle API integration, caching for performance, and scaling for traffic spikes. With Vercel functions and Nitro, you can build a production-ready weather API with Vercel cache and observability 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 Nitro and caching concepts
  

## Build the Weather API

### 1\. Create your Nitro project

Start with the template [Deploy Nitro to Vercel](https://vercel.com/templates/backend/nitro-starter). Once your the project is deployed on Vercel, clone the repository locally.

### 2\. Add the Weather route

Create `server/routes/api/weather/[city].ts` and paste the following code: `import { getRouterParam, getQuery, createError } from 'h3'; import { $fetch } from 'ofetch'; export default defineEventHandler(async (event) => { const cityParam = getRouterParam(event, 'city'); if (!cityParam) { throw createError({ statusCode: 400, statusMessage: 'city is required' }); } const city = decodeURIComponent(cityParam); const { units } = getQuery(event) as { units?: 'metric' | 'imperial' }; const normalizedUnits = units === 'imperial' ? 'imperial' : 'metric'; // 1) Geocode city -> lat/lon const geo = await $fetch<{ results?: Array<{ name: string; country: string; latitude: number; longitude: number; }>; }>('https://geocoding-api.open-meteo.com/v1/search', { params: { name: city, count: 1, language: 'en', format: 'json' }, }); if (!geo?.results?.length) { throw createError({ statusCode: 404, statusMessage: 'city not found' }); } const { name, country, latitude, longitude } = geo.results[0]; // 2) Fetch current weather const forecastParams: Record<string, any> = { latitude, longitude, current: [ 'temperature_2m', 'relative_humidity_2m', 'apparent_temperature', 'wind_speed_10m', ].join(','), timezone: 'auto', }; if (units === 'imperial') { forecastParams.temperature_unit = 'fahrenheit'; forecastParams.wind_speed_unit = 'mph'; } const forecast = await $fetch<{ current: { time: string; temperature_2m: number; relative_humidity_2m: number; apparent_temperature: number; wind_speed_10m: number; }; }>('https://api.open-meteo.com/v1/forecast', { params: forecastParams }); const weatherData = { city: name, country, latitude, longitude, units: normalizedUnits, current: forecast.current, }; return weatherData; });` ### 3\. Test the route locally Install the dependencies and launch the application in dev mode: `pnpm install pnpm dev` Use `curl` to test the API route: `# 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\. Configure Observability Observability helps you monitor your API's performance, error rates, and cache hit ratios in production. To use [Observability](https://vercel.com/docs/observability), update the `nitro.config.ts`:

`import { defineNitroConfig } from 'nitro/config'; export default defineNitroConfig({ srcDir: 'server', // Enables routing hints for Vercel Function insights compatibilityDate: '2025-07-15', // or 'latest' });`

### 5\. Deploy to Vercel

- Push the changes to your remote repository
  
- Vercel will create a new preview deployment for your to test
  
- Merge to main branch to deploy to Production
  

> When deployed to Vercel, Nitro server routes become [Vercel Functions](https://vercel.com/docs/functions) and use [Fluid compute](https://vercel.com/docs/fluid-compute) by default

## Using observability

Now that you deployed your Nitro app to Vercel, let's check that Observability is working.

1. Make a `curl` request to your production URL: `https://my-nitro-app.vercel.app/api/weather/london.`
   
2. Go to the Vercel functions section of the Observability tab for your project by selecting your team and project [here](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fobservability%2Fvercel-functions&title=Go+to+Observability+for+Vercel+Functions).
   
3. Under the list of routes, you should see `/api/weather/[city]` with detailed metrics such as number of invocations, active CPU time and error rate.     ## Cache your responses You can cache your API responses by using [Vercel runtime cache](https://vercel.com/docs/data-cache) with Nitro.

First, update the `nitro.config.ts` to use the Vercel cache driver:

`import { defineNitroConfig } from 'nitro/config'; export default defineNitroConfig({ srcDir: 'server', // Enables routing hints for Vercel Function insights compatibilityDate: '2025-07-15', // or 'latest' storage: { '/cache/nitro': { // It's important to use this specific path for saving to Vercel runtime cache. Other paths will use the browser cache. driver: 'vercel-runtime-cache', }, }, });`

Then, update the `routes/api/weather/[city].ts` with the cache options by using the `defineCachedEventHandler` helper. In this example, we are caching the response for 1 hr: `import { getRouterParam, getQuery, createError } from 'h3'; import { $fetch } from 'ofetch'; export default defineCachedEventHandler( async (event) => { const cityParam = getRouterParam(event, 'city'); if (!cityParam) { throw createError({ statusCode: 400, statusMessage: 'city is required' }); } const city = decodeURIComponent(cityParam); const { units } = getQuery(event) as { units?: 'metric' | 'imperial' }; const normalizedUnits = units === 'imperial' ? 'imperial' : 'metric'; // 1) Geocode city -> lat/lon const geo = await $fetch<{ results?: Array<{ name: string; country: string; latitude: number; longitude: number; }>; }>('https://geocoding-api.open-meteo.com/v1/search', { params: { name: city, count: 1, language: 'en', format: 'json' }, }); if (!geo?.results?.length) { throw createError({ statusCode: 404, statusMessage: 'city not found' }); } const { name, country, latitude, longitude } = geo.results[0]; // 2) Fetch current weather const forecastParams: Record<string, any> = { latitude, longitude, current: [ 'temperature_2m', 'relative_humidity_2m', 'apparent_temperature', 'wind_speed_10m', ].join(','), timezone: 'auto', }; if (units === 'imperial') { forecastParams.temperature_unit = 'fahrenheit'; forecastParams.wind_speed_unit = 'mph'; } const forecast = await $fetch<{ current: { time: string; temperature_2m: number; relative_humidity_2m: number; apparent_temperature: number; wind_speed_10m: number; }; }>('https://api.open-meteo.com/v1/forecast', { params: forecastParams }); const weatherData = { city: name, country, latitude, longitude, units: normalizedUnits, current: forecast.current, }; return weatherData; }, { maxAge: 3600, // Cache for 1 hour }, );` Check the runtime cache metric in the Observability tab - Push the changes to your remote repository and merge to the `main` branch to deploy to Production.    - Make a few `curl` requests to your production URL, `https://my-nitro-app.vercel.app/api/weather/london`, with a few seconds interval between each request.    - Go to the Vercel \*\*Runtime cache \*\*section of the Observability tab for your project by selecting your team and project [here](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fobservability%2Fruntime-cache&title=Go+to+Observability+for+Runtime+Cache).
  
- Make sure the time interval is set on **Last hour**. You should see a Cache Read for each request. The Hit Rate will summarize the number of Hits and Miss to the cache.
  

## Summary

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

You learned to:

- Structure a dynamic API route and integrate external APIs
  
- Use Vercel runtime cache to cache your responses
  
- Monitor the route performance and cache usage using Vercel Observability
  

## Next steps

### Hands-on challenges

Extend your knowledge by:

- Adding precipitation and cloud cover to the response
  
- Implementing rate limiting to protect the endpoint
  
- Building a front-end widget to display the weather data
  

### Explore references

- [Explore the Nitro docs](https://nitro.build/guide)
  
- [Nitro Cache](https://nitro.build/guide/cache)
  
- [Vercel Functions](https://vercel.com/docs/functions)
  
- [Vercel Observability](https://vercel.com/docs/observability)