---
title: "Wire Next.js to FastAPI"
description: "Replace mock inventory with an async Server Component that calls FastAPI through the BACKEND_URL service binding at request time."
canonical_url: "https://vercel.com/academy/python-on-vercel/wire-nextjs-to-fastapi"
md_url: "https://vercel.com/academy/python-on-vercel/wire-nextjs-to-fastapi.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-09-29T18:31:24.632Z"
content_type: "lesson"
course: "python-on-vercel"
course_title: "Python on Vercel"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Wire Next.js to FastAPI

# Wire Next.js to FastAPI

The backend can change its inventory, but the storefront still shows the same furniture. Let's replace that local array with a request to FastAPI.

## Outcome

Render the storefront from FastAPI data using the frontend's generated service binding.

## Hands-on exercise 2.2

### Read the binding

Open `starter/frontend/app/page.tsx`. Add the `connection` import at the top, keep the `Item` type, remove the `mockItems` array, and add this function above `Home`:

```tsx
import { connection } from "next/server";

async function getItems(): Promise<Item[]> {
  await connection();
  const backendUrl = process.env.BACKEND_URL;
  if (!backendUrl) {
    throw new Error("BACKEND_URL is missing. Run vercel dev from the folder containing vercel.json.");
  }
  const res = await fetch(new URL("api/items", backendUrl), { cache: "no-store" });
  if (!res.ok) throw new Error("Failed to fetch items from Hazel Home API");
  return res.json();
}
```

`BACKEND_URL` comes from the frontend binding in `vercel.json`. It is already an absolute URL base. `new URL` resolves the API path against it, and the fetch runs on the server. The address stays out of browser code, so it needs no `NEXT_PUBLIC_` prefix.

Bindings are available at runtime. Calling [`await connection()`](https://nextjs.org/docs/app/api-reference/functions/connection) waits for an incoming request before reading `BACKEND_URL`. This keeps the missing-binding check out of build-time prerendering. The `no-store` option keeps inventory responses out of the fetch cache. Checking `res.ok` catches a failed backend response before the page tries to render it as inventory.

### Connect the page

Make `Home` asynchronous, call `getItems()`, and change the product loop from `mockItems.map` to `items.map`:

```tsx
export default async function Home() {
  const items = await getItems();

  // Keep the existing card markup and render items.map(...) here.
}
```

The full page is in the Solution below. The visible storefront stays the same; its inventory now comes from Python.

### Run through Services

Keep `vercel dev -L` running from `starter/`, or restart it there:

```bash
vercel dev -L
```

Open the local storefront URL. Running `npm run dev` inside `frontend/` alone does not create the binding, so the connected page requires the Services development server.

The binding resolves to the backend in the same deployment: a preview frontend calls its preview backend, and production calls its production backend. We don't need to construct a public deployment hostname or set `BACKEND_URL` manually.

## Try It

1. Open the storefront and confirm that eight items appear.
2. Open `/api/items` on the same local origin and compare the data.
3. Change `Fernwood Sectional` to `Fernwood Sectional, back in stock` in `backend/main.py`.
4. Wait for the backend to reload, then refresh the storefront. The new name should appear, confirming that the page reads the backend response.
5. Restore the original name before committing.

## Commit

From `starter/`, save the connected page:

```bash
git add frontend/app/page.tsx
git commit -m "feat(storefront): fetch inventory through backend binding"
```

## Troubleshooting

**`BACKEND_URL is missing`:** Start `vercel dev -L` from `starter/`, and confirm the binding is declared under the `frontend` service with `env` set to `BACKEND_URL`. Restart the CLI after configuration changes.

**The build reports a missing binding:** Keep `await connection()` before reading `BACKEND_URL`. The binding is only injected at runtime, and the later `no-store` fetch cannot prevent an earlier missing-binding error.

**The backend returns 404:** The internal request also needs the application's `/api/items` route. Check the URL passed to `fetch` and the route in `backend/main.py`.

**The storefront still shows old data:** Remove `mockItems`, render the fetched `items`, and keep `{ cache: "no-store" }`. Check the backend terminal for a reload error.

## Done-When

- [ ] `frontend/app/page.tsx` has no `mockItems` array
- [ ] `getItems()` awaits `connection()` before reading and checking `BACKEND_URL`
- [ ] The fetch resolves `api/items` against the binding URL and uses `no-store`
- [ ] `Home` is async and renders the returned inventory
- [ ] A backend item edit appears in the storefront after refresh

## Solution

Replace `starter/frontend/app/page.tsx` with this Server Component. It fetches inventory through the service binding on each request:

```tsx
import { connection } from "next/server";

type Item = {
  id: number;
  name: string;
  category: string;
  price: number;
  in_stock: boolean;
};

async function getItems(): Promise<Item[]> {
  await connection();
  const backendUrl = process.env.BACKEND_URL;
  if (!backendUrl) {
    throw new Error("BACKEND_URL is missing. Run vercel dev from the folder containing vercel.json.");
  }
  const res = await fetch(new URL("api/items", backendUrl), { cache: "no-store" });
  if (!res.ok) throw new Error("Failed to fetch items from Hazel Home API");
  return res.json();
}

export default async function Home() {
  const items = await getItems();

  return (
    <>
      <h2 className="text-2xl font-semibold mb-8">All Furniture</h2>
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
        {items.map((item) => (
          <div
            key={item.id}
            className="bg-white border border-stone-200 rounded-lg p-6"
          >
            <p className="text-xs uppercase tracking-widest text-stone-400 mb-1">
              {item.category}
            </p>
            <h3 className="text-lg font-medium mb-3">{item.name}</h3>
            <div className="flex items-center justify-between">
              <span className="text-lg font-semibold">
                ${item.price.toLocaleString()}
              </span>
              <span
                className={`text-xs px-2 py-1 rounded-full ${
                  item.in_stock
                    ? "bg-emerald-50 text-emerald-700"
                    : "bg-stone-100 text-stone-400"
                }`}
              >
                {item.in_stock ? "In stock" : "Out of stock"}
              </span>
            </div>
          </div>
        ))}
      </div>
    </>
  );
}
```


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
