Consuming Express

Consuming the Express API from Chapter 3

The Bookshelf API of The Bookshelf API already validates input, enforces roles and versions its routes. Next.js 10,514 does not replace it; it becomes its best client. Give the app one module that knows the base URL and the error contract, and no page writes a raw fetch again.

One module for the whole API (app/lib/bookshelf.js)JavaScript
import "server-only";
const API = process.env.BOOKSHELF_API ?? "http://localhost:4310/api/v1";
export async function bookshelf(path, init) {
  const headers = { accept: "application/json", ...init?.headers };
  const res = await fetch(`${API}${path}`, { ...init, headers });
  if (res.status === 404) return null;              // an absence, not an error
  if (!res.ok) throw new Error(`Bookshelf ${path} -> ${res.status}`);
  return (await res.json()).data;                   // unwrap { data, page, links }
}
export const getBook = (id) => bookshelf(`/books/${id}`);
export const listBooks = (limit = 20) => bookshelf(`/books?limit=${limit}`);

Four decisions are worth copying. import "server-only" (Server-Only Code) fails the build if a Client Component ever imports this file, keeping BOOKSHELF_API and any token out of the browser bundle — note the missing NEXT_PUBLIC_ prefix, without which the variable is an empty string on the client anyway. Unwrapping body.data puts the API's envelope in one place. A 404 becomes null rather than a throw, so a page can call notFound() and get the framework's 404 page instead of its error boundary. Every other non-2xx answer throws into error.js.

One rule saves hours: never fetch your own app's Route Handlers from a Server Component. await fetch("http://localhost:3000/api/books") makes the server call itself over HTTP, paying for a socket and a second render to get data it could have loaded directly. Import the function instead. The Express 24,430 API is different — a separate process, so HTTP is the only way in.

Three failures that come with an external API
Symptom Cause
ECONNREFUSED in next build API down while pages prerender
Data frozen at build time uncached fetch in a static route
Token undefined in the browser server variable read on the client