Skip to Content
Lesson 5

Server-Side API Consumption

Workplace Context

Your front-end team needs data from an external API, but there’s a problem. The API requires a secret key, which cannot be exposed in the browser. Furthermore, the API’s data structure is overly complex, and it sometimes returns data you don’t want to show to users. Your task is to build a “proxy” or “facade” endpoint on your Express server. This endpoint will securely handle the API key, fetch the data, clean it up, and then forward a simplified version to your front-end application.


Learning Objectives

By the end of this lesson, you will be able to:

  • Explain the strategic advantages of server-side API consumption (e.g., security, data transformation, caching).
  • Integrate fetch within an Express route to create a proxy for an external API.
  • Implement robust error handling that inspects the external API’s response and translates it into a meaningful status code for your own server’s response.
  • Transform and sanitize data from an external API before sending it to the client.

Why Fetch on the Server?

While fetch can be used in the browser, performing the request on the server offers several powerful advantages that are critical for professional applications:

  1. Security: The most important reason. You can securely store and use API keys, tokens, and other credentials. If you made the request from the browser, these secrets would be visible to anyone, creating a massive security risk.
  2. CORS Prevention: Some APIs are not configured to allow cross-origin requests from browsers (CORS). By making the request from your server (which doesn’t have CORS restrictions), you can bypass this issue entirely.
  3. Data Transformation and Sanitization: You have full control to reshape the data before it reaches the client. You can remove unnecessary fields to reduce payload size, combine data from multiple sources, or remove sensitive information.
  4. Caching: You can implement a caching layer on your server. If you frequently request the same data, you can store it temporarily and serve it from your cache instead of hitting the external API every time, improving performance and respecting rate limits.

Building a Data Transformation Proxy

Let’s build a route that acts as a proxy to fetch user data from JSONPlaceholder. However, we won’t just pass the data through. We will transform it, sending back only the id, name, username, and email for each user.

This pattern is often called a Facade, because it provides a simpler, custom interface to a more complex underlying system.

server.js
const express = require('express'); const app = express(); const port = 3000; app.get('/api/users', async (req, res) => { try { const apiResponse = await fetch('https://jsonplaceholder.typicode.com/users'); // It's crucial to check if the request was successful if (!apiResponse.ok) { // If not, we create an error to be caught by the catch block throw new Error(`HTTP error! status: ${apiResponse.status}`); } const users = await apiResponse.json(); // Transform the data const transformedUsers = users.map(user => ({ id: user.id, name: user.name, username: user.username, email: user.email, })); res.json(transformedUsers); } catch (error) { console.error('Error fetching or transforming users:', error); // Send a generic server error response res.status(500).json({ message: 'Failed to fetch data from the external API.' }); } }); app.listen(port, () => { console.log(`Server is running at http://localhost:${port}`); });

Advanced Error Handling: Matching Status Codes

A simple 500 error is okay, but we can do better. A professional server should provide meaningful feedback. If our server requests a resource that doesn’t exist on the external API (a 404), our server shouldn’t just crash with a generic 500 error. It should also respond with a 404 Not Found, telling our own client what happened.

This involves inspecting the status of the fetch response.

server.js
const express = require('express'); const app = express(); const port = 3000; app.get('/api/posts/:id', async (req, res) => { const { id } = req.params; const url = `https://jsonplaceholder.typicode.com/posts/${id}`; try { const apiResponse = await fetch(url); // If the response status is 404, we send our own 404 response. if (apiResponse.status === 404) { return res.status(404).json({ message: `Post with id ${id} not found.` }); } // Handle other non-successful responses that aren't 404 if (!apiResponse.ok) { // This will catch 500s from the external API, etc. throw new Error(`External API returned status ${apiResponse.status}`); } const post = await apiResponse.json(); res.json(post); } catch (error) { console.error('API request failed:', error.message); // We can use a 502 Bad Gateway status to indicate that our server // received an invalid response from the upstream server. res.status(502).json({ message: 'Failed to get a valid response from the API.' }); } }); app.listen(port, () => { console.log(`Server is running at http://localhost:${port}`); });

Activities

Activity 1: Build a Data Transformer

  1. Create an Express route GET /api/comments.
  2. Fetch the list of all comments from https://jsonplaceholder.typicode.com/comments.
  3. Instead of sending the full objects, transform the data into an array of strings, where each string is in the format "<email> commented: <name>".
  4. Implement try...catch for error handling.

Activity 2: Robust Error Proxy

  1. Create another route: GET /api/users/:id.
  2. Fetch a single user from https://jsonplaceholder.typicode.com/users/:id.
  3. If the external API responds with a 404, your server should respond with a 404 and a JSON object { "error": "User not found" }.
  4. If the external API responds with any other error (e.g., 500), your server should respond with a 502 Bad Gateway status and a message.
  5. If the request is successful, send the user data back to the client.

Activity 3: AI-Assisted Error Handling Generation

In professional backend development, generating comprehensive error handling for API consumption is a strong use case for AI. Error handling is tedious and repetitive, but critical for production applications. AI tools excel at scaffolding robust error handling patterns, freeing developers to focus on business logic and edge cases specific to their domain.

If you haven't already done so, you should install an AI assistant.

We recommend Claude for VS Code , but you can use any AI assistant you prefer.

Scenario: You are building a weather proxy service that fetches data from an external weather API. The service needs comprehensive error handling to provide meaningful feedback to your frontend application.

Requirements for /api/weather/:city endpoint:

  1. Network Errors: Handle cases where the external API is completely unreachable (e.g., DNS failure, timeout). Return 503 Service Unavailable with message "Weather service temporarily unavailable".
  2. 404 Not Found: If the city doesn’t exist in the weather API, return 404 with { "error": "City not found", "city": "<requested_city>" }.
  3. 401 Unauthorized: If the API key is invalid or missing, return 500 with { "error": "Weather service configuration error" } (don’t expose that it’s an auth issue to clients).
  4. 429 Rate Limited: If the external API rate limits you, return 503 with { "error": "Weather service busy, please try again later" }.
  5. 5xx Server Errors: For any 500-level error from the external API, return 502 Bad Gateway with { "error": "Weather service returned an error" }.
  6. Invalid JSON: If the response body cannot be parsed as JSON, return 502 with { "error": "Invalid response from weather service" }.
  7. Success: Transform the response to return only { city, temperature, conditions, humidity }.

Part 1: Manual Implementation (15 min)

First, implement error handling for scenarios 1, 2, and 7 yourself. Start with this skeleton:

app.get('/api/weather/:city', async (req, res) => { const { city } = req.params; const apiKey = process.env.WEATHER_API_KEY; const url = `https://api.example-weather.com/v1/current?city=${city}&key=${apiKey}`; try { const apiResponse = await fetch(url); // Your error handling here... const data = await apiResponse.json(); // Transform and return... } catch (error) { // Handle network errors... } });

Part 2: AI-Assisted Generation (10 min)

Now, use an AI assistant to generate comprehensive error handling for ALL seven scenarios.

Prompt the AI with:

“Generate an Express route handler for GET /api/weather/:city that fetches from an external weather API. Implement comprehensive error handling for: network failures (503), city not found (404), invalid API key (return 500, don’t expose auth details), rate limiting (503), upstream 5xx errors (502), invalid JSON responses (502), and successful responses (transform to city, temperature, conditions, humidity only).”

Part 3: Compare and Evaluate (10 min)

Compare your manual implementation with the AI-generated code:

  • Did the AI handle edge cases you missed?
  • Did the AI use any patterns you weren’t aware of (e.g., AbortController for timeouts)?
  • Is the AI’s error message formatting consistent and professional?
  • Are there any security issues (e.g., leaking internal details in error messages)?

Reflection Questions:

  1. What percentage of this error handling code is “boilerplate” vs. business-specific logic?
  2. How would you test all these error scenarios? Did the AI provide any hints for testing?
  3. In a production codebase, how might you refactor this error handling into reusable middleware?
Important

Comprehensive error handling is essential for production APIs but tedious to write from scratch. AI tools are excellent at generating this boilerplate, allowing you to focus on business logic and edge cases unique to your application. However, always review AI-generated error handling for security issues! Never expose internal details like API key errors to clients.


Knowledge Check

What is a primary security reason for using fetch on an Express server instead of in the browser?

  • Select an answer to view feedback.

In a server-side fetch call, what is the purpose of checking response.ok?

  • Select an answer to view feedback.

If an external API returns a 500 error, what is the most appropriate HTTP status for your server to send back to the client in a proxy scenario?

  • Select an answer to view feedback.

Summary

In this lesson, you graduated from simply fetching data to strategically using your Express server as a secure and powerful API client. You learned the critical advantages of server-side requests, such as protecting API keys and transforming data. You can now build robust proxy and facade routes that handle external API errors gracefully, translating them into meaningful responses for your own clients. This is a key pattern used in professional microservice and monolithic architectures.


References