Next.js Lesson 33 – Mini Project - API Integration | Dataplexa
LESSON 33

Mini Project – API Integration

Build a complete API-driven news aggregation system for NewsWave, connecting multiple external APIs to create dynamic content feeds.

The NewsWave team wants to expand beyond static articles. They need to pull breaking news from external sources, display weather updates for location-based articles, and integrate social media feeds. This means connecting to real APIs, handling authentication tokens, managing rate limits, and gracefully dealing with API failures. API integration separates amateur projects from professional applications. When you connect to external services like Twitter's API, news feeds, or weather data, you enter a world of unpredictable responses, changing data structures, and network timeouts. But this complexity brings power – your app becomes dynamic, fresh, and valuable to users. Unlike fetching data from your own database, external APIs have their own rules. Some require authentication headers. Others limit you to 100 requests per hour. Some return XML instead of JSON. Some go down during peak traffic. Your code needs to handle all these scenarios while keeping users happy.

Planning the API Architecture

Professional API integration starts with mapping data flows. Think of it like plumbing – you need to know where water comes from, how much pressure to expect, and what happens when pipes burst. NewsWave needs three external data sources: breaking news from NewsAPI, weather data from OpenWeatherMap, and trending topics from a placeholder social API. Each has different authentication methods, response formats, and reliability patterns. The architecture uses Next.js API routes as middleware between external APIs and your frontend. This pattern provides several benefits: it hides API keys from browsers, adds caching layers, transforms data into consistent formats, and implements retry logic for failed requests.
// app/lib/api-config.js - Central API configuration
export const API_SOURCES = {
  news: {
    baseUrl: 'https://newsapi.org/v2',
    key: process.env.NEWS_API_KEY, // Stored in environment variables
    rateLimit: 1000, // Requests per day
    timeout: 5000 // 5 second timeout
  },
  weather: {
    baseUrl: 'https://api.openweathermap.org/data/2.5',
    key: process.env.WEATHER_API_KEY,
    rateLimit: 60, // Requests per minute
    timeout: 3000
  }
};
Terminal
$ npm install axios
+ axios@1.6.0
✓ HTTP client for external API calls

What just happened?

Configuration centralizes API endpoints, keys, and limits. Axios provides better error handling than fetch(). Environment variables keep secrets secure. Try this: create a .env.local file with your API keys.

Building the News Feed API

The first integration pulls breaking news from NewsAPI. This service provides headlines, article snippets, and source information. But raw API responses contain too much data and inconsistent formatting – your API route needs to clean and standardize everything. Error handling becomes crucial with external APIs. Network requests can timeout, APIs can return error codes, or the service might be temporarily down. Professional applications never crash – they degrade gracefully with fallback content and user-friendly error messages.
// app/api/news/breaking/route.js - Breaking news endpoint
import axios from 'axios';
import { API_SOURCES } from '@/lib/api-config';

export async function GET(request) {
  try {
    const { searchParams } = new URL(request.url);
    const category = searchParams.get('category') || 'general'; // Default category
    
    const response = await axios.get(
      `${API_SOURCES.news.baseUrl}/top-headlines`,
      {
        params: {
          apiKey: API_SOURCES.news.key,
          category: category,
          country: 'us',
          pageSize: 10 // Limit results
        },
        timeout: API_SOURCES.news.timeout
      }
    );
    // Transform raw API data into NewsWave format
    const articles = response.data.articles.map(article => ({
      id: article.url, // Use URL as unique identifier
      headline: article.title,
      summary: article.description,
      source: article.source.name,
      publishedAt: article.publishedAt,
      imageUrl: article.urlToImage || '/placeholder-news.jpg'
    }));

    return Response.json({ articles, success: true });
  } catch (error) {
    console.error('News API Error:', error.message);
    
    // Return fallback data instead of crashing
    return Response.json({ 
      articles: [],
      success: false,
      error: 'Unable to fetch breaking news at this time'
    }, { status: 503 }); // Service Unavailable
  }
}
Terminal
$ curl "http://localhost:3000/api/news/breaking?category=technology"
{"articles":[{"headline":"Tech Giants Report Earnings",...}]}
✓ API route successfully transforms external data

What just happened?

Your API route acts as a translator between NewsAPI and your frontend. It handles authentication, transforms data structure, and provides fallbacks. Status 503 tells clients the service is temporarily unavailable. Try this: test with an invalid API key to see error handling.

Adding Weather Integration

Weather data enhances location-based articles with current conditions and forecasts. OpenWeatherMap provides detailed weather information, but the API returns complex nested objects with temperature in Kelvin, wind speed in meters per second, and weather conditions as cryptic codes. Your integration needs to convert units, simplify the data structure, and provide meaningful weather descriptions. Users want "Sunny, 72°F" not "Clear sky, 295.15K with wind speed 3.2 m/s from 180 degrees."
// app/api/weather/current/route.js - Current weather by city
import axios from 'axios';
import { API_SOURCES } from '@/lib/api-config';

// Helper function to convert Kelvin to Fahrenheit
function kelvinToFahrenheit(kelvin) {
  return Math.round((kelvin - 273.15) * 9/5 + 32);
}

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const city = searchParams.get('city');
  
  if (!city) {
    return Response.json({ error: 'City parameter required' }, { status: 400 });
  }

  try {
    const response = await axios.get(
      `${API_SOURCES.weather.baseUrl}/weather`,
      {
        params: {
          q: city,
          appid: API_SOURCES.weather.key
        },
        timeout: API_SOURCES.weather.timeout
      }
    );

    const data = response.data;
    
    // Transform complex weather data into simple format
    const weather = {
      city: data.name,
      temperature: kelvinToFahrenheit(data.main.temp),
      condition: data.weather[0].main, // Clear, Clouds, Rain, etc.
      description: data.weather[0].description,
      humidity: data.main.humidity,
      windSpeed: Math.round(data.wind.speed * 2.237), // Convert m/s to mph
      icon: `https://openweathermap.org/img/w/${data.weather[0].icon}.png`
    };

    return Response.json({ weather, success: true });
    
  } catch (error) {
    return Response.json({ 
      error: 'Weather data unavailable',
      success: false 
    }, { status: 503 });
  }
}
Terminal
$ curl "http://localhost:3000/api/weather/current?city=San Francisco"
{"weather":{"city":"San Francisco","temperature":68,"condition":"Clear"}}
✓ Weather API returns user-friendly format

What just happened?

Unit conversion makes data user-friendly. Parameter validation prevents invalid requests. The icon URL provides ready-to-use weather graphics. Error 400 means bad request (missing city), while 503 means external service failed. Try this: test with "InvalidCityName" to see error handling.

Frontend Integration Component

The frontend component orchestrates multiple API calls, manages loading states, and handles errors gracefully. Users should see content appearing progressively – news articles first, weather data second, with loading spinners for each section. React's useEffect hook triggers API calls when components mount. But multiple simultaneous requests need careful coordination. Some might succeed while others fail. The component should display successful data while showing error messages for failed requests.
// app/components/NewsAggregator.js - Main integration component
'use client';
import { useState, useEffect } from 'react';

export default function NewsAggregator() {
  const [newsData, setNewsData] = useState({ articles: [], loading: true, error: null });
  const [weatherData, setWeatherData] = useState({ weather: null, loading: true, error: null });
  
  // Fetch breaking news on component mount
  useEffect(() => {
    fetchBreakingNews();
    fetchWeatherData('New York'); // Default city
  }, []);
  const fetchBreakingNews = async () => {
    try {
      const response = await fetch('/api/news/breaking?category=general');
      const data = await response.json();
      
      if (data.success) {
        setNewsData({ articles: data.articles, loading: false, error: null });
      } else {
        setNewsData({ articles: [], loading: false, error: data.error });
      }
    } catch (error) {
      setNewsData({ 
        articles: [], 
        loading: false, 
        error: 'Unable to connect to news service' 
      });
    }
  };

  const fetchWeatherData = async (city) => {
    try {
      const response = await fetch(`/api/weather/current?city=${city}`);
      const data = await response.json();
      if (data.success) {
        setWeatherData({ weather: data.weather, loading: false, error: null });
      } else {
        setWeatherData({ weather: null, loading: false, error: data.error });
      }
    } catch (error) {
      setWeatherData({ 
        weather: null, 
        loading: false, 
        error: 'Weather service unavailable' 
      });
    }
  };
localhost:3000 — NewsWave

What just happened?

Separate state objects handle loading and errors independently. News can load successfully while weather fails. The UI shows content as it becomes available. Progressive loading creates a better user experience than waiting for all APIs. Try this: simulate network delays with setTimeout().

Caching and Performance

External APIs are slow and expensive. Every request costs money, bandwidth, and time. Professional applications implement multiple caching layers: browser cache for images, memory cache for frequent requests, and database cache for expensive operations. Next.js provides built-in caching through the fetch API and revalidation strategies. But external API integration often needs custom caching logic. News updates every few minutes, weather changes hourly, and user preferences remain static for days.
// app/lib/cache.js - Simple in-memory cache
class ApiCache {
  constructor() {
    this.cache = new Map(); // Store cached responses
    this.timestamps = new Map(); // Track when data was cached
  }
  
  get(key, maxAge = 300000) { // Default 5 minutes
    const cached = this.cache.get(key);
    const timestamp = this.timestamps.get(key);
    
    if (!cached || !timestamp) return null;
    
    // Check if cache is expired
    const age = Date.now() - timestamp;
    if (age > maxAge) {
      this.cache.delete(key);
      this.timestamps.delete(key);
      return null;
    }
    
    return cached;
  }
  set(key, data) {
    this.cache.set(key, data);
    this.timestamps.set(key, Date.now());
  }
  
  clear() {
    this.cache.clear();
    this.timestamps.clear();
  }
}

export const apiCache = new ApiCache();
// Updated news API route with caching
import { apiCache } from '@/lib/cache';

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const category = searchParams.get('category') || 'general';
  const cacheKey = `news-${category}`;
  
  // Try to get from cache first
  const cached = apiCache.get(cacheKey, 300000); // 5 minutes
  if (cached) {
    return Response.json(cached);
  }
Terminal
$ curl "http://localhost:3000/api/news/breaking?category=tech"
Response time: 1.2s (external API call)
$ curl "http://localhost:3000/api/news/breaking?category=tech"
Response time: 0.02s (cached response)
✓ Cache reduces response time by 98%

What just happened?

The cache stores API responses in memory with timestamps. Expired entries get deleted automatically. Cache keys include parameters so different categories cache separately. Map data structure provides O(1) lookup performance. Try this: implement Redis for production caching across server restarts.

Error Handling and Resilience

External APIs fail in creative ways. Servers return 500 errors during maintenance, rate limits block your requests after heavy usage, authentication tokens expire at midnight, and network timeouts happen during peak traffic. Professional applications implement circuit breaker patterns – when an API fails repeatedly, stop calling it temporarily and serve cached data instead. This prevents cascading failures where your entire application breaks because one external service is down.
// app/lib/circuit-breaker.js - Prevents cascading failures
class CircuitBreaker {
  constructor(failureThreshold = 5, timeout = 60000) {
    this.failureThreshold = failureThreshold;
    this.timeout = timeout;
    this.failureCount = 0;
    this.lastFailureTime = null;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
  }
  
  async execute(operation) {
    if (this.state === 'OPEN') {
      // Check if timeout period has passed
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN - service unavailable');
      }
    }
    
    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }
  
  onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
    }
  }
}

export const newsApiBreaker = new CircuitBreaker(3, 30000); // 3 failures, 30s timeout
localhost:3000 — NewsWave Admin

What just happened?

Circuit breakers track failure counts and automatically stop calling failing services. CLOSED means healthy, OPEN means too many failures, HALF_OPEN means testing recovery. This prevents wasting time on broken APIs and protects your application from cascading failures. Try this: implement exponential backoff for retry logic.

API integration transforms static websites into dynamic applications. But with great power comes great responsibility – handle errors gracefully, cache expensive requests, and never let external failures break your user experience. Your NewsWave integration now pulls live data from multiple sources, caches responses for performance, and handles failures elegantly. The architecture scales to dozens of APIs while maintaining fast response times and reliable service.

Quiz

1. Why should NewsWave use Next.js API routes instead of calling external APIs directly from React components?


2. What should happen when NewsWave's weather API fails but news data loads successfully?


3. What is the main benefit of implementing circuit breaker patterns in API integration?


Up Next: Next.js Case Study

Analyze a complete production Next.js application, examining architecture decisions, performance optimizations, and real-world implementation patterns used by successful companies.