# Session 11: Connect to the Real World (External APIs) ## Overview Welcome to Session 11! In this session, we'll learn how to integrate external APIs into our applications. APIs (Application Programming Interfaces) allow our applications to communicate with other services and retrieve or send data, enabling us to build feature-rich applications that leverage existing services rather than building everything from scratch. ## Learning Objectives - Understand what APIs are and how they work - Learn about RESTful API principles - Make API requests using different HTTP methods - Handle API responses and errors - Implement authentication for secure API access - Integrate external APIs into our applications ## Topics Covered ### 1. Understanding APIs An API (Application Programming Interface) is a set of rules that allows one software application to interact with another. It defines the methods and data formats that applications can use to request and exchange data. #### Types of APIs: - **REST APIs**: Use standard HTTP methods and are the most common - **GraphQL APIs**: Allow clients to request exactly what they need - **SOAP APIs**: Use XML for message format and are typically more rigid - **WebSocket APIs**: Enable bidirectional communication for real-time applications In this session, we'll focus primarily on REST APIs, which are the most widely used. ### 2. REST API Principles REST (Representational State Transfer) is an architectural style for designing networked applications. RESTful APIs follow these key principles: - **Client-Server Architecture**: Separation of concerns between client and server - **Statelessness**: Each request contains all the information needed to complete it - **Cacheable**: Responses must be explicitly labeled as cacheable or non-cacheable - **Uniform Interface**: Resources are identified in requests, manipulated through representations, and include self-descriptive messages - **Layered System**: Architecture composed of hierarchical layers #### HTTP Methods (CRUD Operations): - **GET**: Retrieve data (Read) - **POST**: Create new data (Create) - **PUT/PATCH**: Update existing data (Update) - **DELETE**: Remove data (Delete) #### HTTP Status Codes: - **2xx (Success)**: 200 OK, 201 Created, 204 No Content - **3xx (Redirection)**: 301 Moved Permanently, 302 Found - **4xx (Client Error)**: 400 Bad Request, 401 Unauthorized, 404 Not Found - **5xx (Server Error)**: 500 Internal Server Error, 503 Service Unavailable ### 3. Making API Requests #### Using the `fetch` API (JavaScript): ```javascript // Basic GET request fetch('https://api.example.com/data') .then(response => { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } return response.json(); }) .then(data => { console.log('Data received:', data); }) .catch(error => { console.error('Error fetching data:', error); }); // POST request with headers and body fetch('https://api.example.com/items', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer your-token-here' }, body: JSON.stringify({ name: 'New Item', description: 'This is a new item' }) }) .then(response => response.json()) .then(data => console.log('Item created:', data)) .catch(error => console.error('Error creating item:', error)); ``` #### Using Axios (JavaScript): ```javascript import axios from 'axios'; // GET request axios.get('https://api.example.com/data') .then(response => { console.log('Data received:', response.data); }) .catch(error => { console.error('Error fetching data:', error); }); // POST request axios.post('https://api.example.com/items', { name: 'New Item', description: 'This is a new item' }, { headers: { 'Authorization': 'Bearer your-token-here' } }) .then(response => { console.log('Item created:', response.data); }) .catch(error => { console.error('Error creating item:', error); }); ``` #### Using the `requests` Library (Python): ```python import requests # GET request response = requests.get('https://api.example.com/data') if response.status_code == 200: data = response.json() print('Data received:', data) else: print(f'Error: {response.status_code}') # POST request headers = { 'Authorization': 'Bearer your-token-here' } data = { 'name': 'New Item', 'description': 'This is a new item' } response = requests.post('https://api.example.com/items', json=data, headers=headers) if response.status_code == 201: # Created new_item = response.json() print('Item created:', new_item) else: print(f'Error: {response.status_code}') ``` ### 4. API Authentication Most APIs require authentication to ensure that only authorized users can access them. Common authentication methods include: #### API Keys: ```javascript fetch('https://api.example.com/data?api_key=your-api-key-here') .then(response => response.json()) .then(data => console.log(data)); // Or in headers fetch('https://api.example.com/data', { headers: { 'X-API-Key': 'your-api-key-here' } }) .then(response => response.json()) .then(data => console.log(data)); ``` #### OAuth 2.0: ```javascript // After obtaining an access token through the OAuth flow fetch('https://api.example.com/protected-resource', { headers: { 'Authorization': 'Bearer your-access-token' } }) .then(response => response.json()) .then(data => console.log(data)); ``` #### Basic Authentication: ```javascript const credentials = btoa('username:password'); // Base64 encoding fetch('https://api.example.com/data', { headers: { 'Authorization': `Basic ${credentials}` } }) .then(response => response.json()) .then(data => console.log(data)); ``` ### 5. Handling API Responses and Errors Proper error handling is crucial when working with external APIs: ```javascript async function fetchData(url) { try { const response = await fetch(url); // Check if the request was successful if (!response.ok) { // Get more error info from the response const errorData = await response.json().catch(() => null); throw new Error(`API error: ${response.status} ${response.statusText} ${errorData ? JSON.stringify(errorData) : ''}`); } // Parse and return the data const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); throw error; // Re-throw to let the caller handle it } } // With rate limiting and retry logic async function fetchWithRetry(url, maxRetries = 3, delay = 1000) { let retries = 0; while (retries < maxRetries) { try { return await fetchData(url); } catch (error) { // Retry on rate limiting (429) or server errors (5xx) if ((error.message.includes('429') || error.message.includes('5')) && retries < maxRetries - 1) { retries++; console.log(`Retrying (${retries}/${maxRetries}) in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); delay *= 2; // Exponential backoff } else { throw error; } } } } ``` ### 6. Popular External APIs Let's explore some popular APIs that you can integrate into your applications: #### Weather APIs: - **OpenWeatherMap**: Weather data, forecasts, historical data ```javascript const API_KEY = 'your-openweathermap-api-key'; const city = 'London'; fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`) .then(response => response.json()) .then(data => { console.log(`Current temperature in ${city}: ${data.main.temp}°C`); console.log(`Weather conditions: ${data.weather[0].description}`); }); ``` #### Payment APIs: - **Stripe**: Online payment processing ```javascript // Using Stripe.js const stripe = Stripe('your-publishable-key'); stripe.createPaymentMethod({ type: 'card', card: elements.getElement('card'), billing_details: { name: 'Customer Name', }, }) .then(result => { if (result.error) { console.error(result.error.message); } else { // Send payment method ID to your server // server creates payment intent and confirms payment } }); ``` #### Maps and Location APIs: - **Google Maps API**: Maps, geocoding, directions ```javascript // Initialize map const map = new google.maps.Map(document.getElementById('map'), { center: { lat: -34.397, lng: 150.644 }, zoom: 8 }); // Add a marker new google.maps.Marker({ position: { lat: -34.397, lng: 150.644 }, map: map, title: 'Hello World!' }); ``` #### Social Media APIs: - **Twitter API**: Tweet data, user information, post tweets - **Facebook Graph API**: User data, posts, pages #### File Storage APIs: - **Amazon S3**: Cloud storage for files - **Google Drive API**: File storage and manipulation ### 7. Building a Weather Dashboard with OpenWeatherMap API Let's build a simple Next.js component that fetches and displays weather data: ```jsx 'use client'; import { useState, useEffect } from 'react'; import axios from 'axios'; export default function WeatherDashboard() { const [city, setCity] = useState(''); const [weather, setWeather] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const API_KEY = process.env.NEXT_PUBLIC_OPENWEATHERMAP_API_KEY; const fetchWeather = async (e) => { e.preventDefault(); if (!city.trim()) { setError('Please enter a city name'); return; } setLoading(true); setError(''); setWeather(null); try { const response = await axios.get( `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric` ); setWeather(response.data); } catch (err) { if (err.response && err.response.status === 404) { setError('City not found. Please check the spelling and try again.'); } else { setError('Failed to fetch weather data. Please try again later.'); } console.error('Error fetching weather:', err); } finally { setLoading(false); } }; return (

Weather Dashboard

setCity(e.target.value)} placeholder="Enter city name" className="flex-1 px-4 py-2 border border-gray-300 rounded-l focus:outline-none focus:ring-2 focus:ring-blue-500" />
{error && (
{error}
)} {weather && (

{weather.name}, {weather.sys.country}

{new Date().toLocaleDateString()}

{weather.weather[0].description}

{Math.round(weather.main.temp)}°C

{weather.weather[0].description}

Feels Like

{Math.round(weather.main.feels_like)}°C

Humidity

{weather.main.humidity}%

Wind Speed

{weather.wind.speed} m/s

Pressure

{weather.main.pressure} hPa

)}
); } ``` ### 8. Building a RESTful API Client When working with multiple APIs, it's a good practice to create a unified API client: ```javascript // src/utils/apiClient.js import axios from 'axios'; // Create API client instances for different services const createApiClient = (baseURL, options = {}) => { const client = axios.create({ baseURL, timeout: options.timeout || 10000, headers: { 'Content-Type': 'application/json', ...options.headers } }); // Request interceptor client.interceptors.request.use( config => { // Add auth token if needed if (options.authTokenProvider) { const token = options.authTokenProvider(); if (token) { config.headers.Authorization = `Bearer ${token}`; } } return config; }, error => Promise.reject(error) ); // Response interceptor client.interceptors.response.use( response => response, async error => { // Handle token refresh if 401 error and refreshTokenProvider exists if (error.response?.status === 401 && options.refreshTokenProvider) { try { const newToken = await options.refreshTokenProvider(); if (newToken) { error.config.headers.Authorization = `Bearer ${newToken}`; return axios(error.config); } } catch (refreshError) { return Promise.reject(refreshError); } } return Promise.reject(error); } ); return client; }; // Create API clients for different services export const weatherApi = createApiClient('https://api.openweathermap.org/data/2.5', { // Add API key to every request timeout: 5000, paramsSerializer: params => { return Object.entries({ ...params, appid: process.env.NEXT_PUBLIC_OPENWEATHERMAP_API_KEY }) .map(([key, value]) => `${key}=${value}`) .join('&'); } }); export const backendApi = createApiClient(process.env.NEXT_PUBLIC_API_URL, { authTokenProvider: () => { // Get token from local storage or state management return localStorage.getItem('auth_token'); }, refreshTokenProvider: async () => { // Implement token refresh logic here const refreshToken = localStorage.getItem('refresh_token'); if (refreshToken) { try { const response = await axios.post('/auth/refresh', { refreshToken }); const newToken = response.data.access_token; localStorage.setItem('auth_token', newToken); return newToken; } catch (error) { // Handle refresh error (e.g., redirect to login) localStorage.removeItem('auth_token'); localStorage.removeItem('refresh_token'); window.location.href = '/login'; return null; } } return null; } }); ``` ## Practice Exercises 1. Create a weather dashboard using the OpenWeatherMap API 2. Build an application that integrates with a public API of your choice (e.g., movie database, news API, cryptocurrency data) 3. Implement error handling and loading states for API requests 4. Create a unified API client for multiple services 5. Add authentication to your API requests 6. Implement rate limiting and retry logic for API calls ## Additional Resources - [Public APIs Directory](https://github.com/public-apis/public-apis) - Extensive list of free public APIs - [MDN Fetch API Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) - [Axios Documentation](https://axios-http.com/docs/intro) - [Python Requests Library](https://docs.python-requests.org/) - [REST API Design Best Practices](https://restfulapi.net/) - [OAuth 2.0 Simplified](https://www.oauth.com/) ## Next Steps Now that you've learned how to integrate external APIs into your application, let's move on to [Session 12: Make it Pretty](Session-12-UI-UX-Design.md) where you'll learn about UI/UX design principles and how to create beautiful user interfaces using shadcn/ui.