Skip to Content
ExamplesNext.js API Routes

Next.js API Routes

A bookmarks app built with Next.js App Router that uses route handlers to expose a REST API (GET, POST, DELETE) and a client component that consumes it with fetch. No server actions — just plain HTTP requests between client and server.

How it works

The page is a server component that renders a client BookmarkManager. On mount the client fetches GET /api/bookmarks to load the list. When the user submits the form, it POSTs JSON to the same endpoint. Clicking the delete button sends DELETE /api/bookmarks/[id]. Each route handler reads or writes a JSON file on disk and returns a JSON response.

nextjs-api-routes/ ├── data/bookmarks.json ← JSON "database" (seeded with two entries) ├── app/layout.tsx ← root layout (minimal) ├── app/page.tsx ← server component: renders the client manager ├── app/api/bookmarks/route.ts ← GET all + POST new bookmark ├── app/api/bookmarks/[id]/route.ts ← DELETE a bookmark by id └── app/bookmark-manager.tsx ← client component: fetch, create, delete

data/bookmarks.json

Seed file with two entries so the list isn’t empty on first load. The route handlers read and write this array.

[ { "id": 1, "title": "Next.js Docs", "url": "https://nextjs.org/docs" }, { "id": 2, "title": "MDN Web Docs", "url": "https://developer.mozilla.org" } ]

app/layout.tsx

Minimal root layout — just the HTML shell and the system font stack.

import type { ReactNode } from 'react' export const metadata = { title: 'Next.js API Routes' } export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <body style={{ fontFamily: 'system-ui, sans-serif', maxWidth: 520, margin: '2rem auto' }}> {children} </body> </html> ) }

app/api/bookmarks/route.ts

This is the main route handler. Exporting named GET and POST functions tells Next.js to handle those HTTP methods at /api/bookmarks. GET reads the JSON file and returns the array. POST parses the request body with req.json(), validates it, appends a new entry, writes the file, and returns the created bookmark with a 201 status.

app/api/bookmarks/route.ts

app/api/bookmarks/[id]/route.ts

Dynamic route segment — Next.js matches /api/bookmarks/42 and passes { id: '42' } through params. The params property is a Promise in Next.js 16, so it needs to be awaited. The handler imports readBookmarks and writeBookmarks from the parent route file so the helpers aren’t duplicated.

app/api/bookmarks/[id]/route.ts

app/page.tsx

Server component that just renders the heading and the client-side BookmarkManager. The data fetching happens in the client via fetch calls to the API routes — this is the key difference from the server-action pattern.

import { BookmarkManager } from './bookmark-manager' export default function HomePage() { return ( <main> <h1>Bookmarks</h1> <BookmarkManager /> </main> ) }

app/bookmark-manager.tsx

'use client' makes this a client component so it can use hooks and event handlers. On mount, useEffect calls GET /api/bookmarks and populates state. The form submit handler calls POST /api/bookmarks with a JSON body and appends the returned bookmark to the list. The delete handler calls DELETE /api/bookmarks/[id] and removes it from state. Every API call uses the standard fetch API — no special libraries needed.

app/bookmark-manager.tsx

Run it

npx create-next-app@latest nextjs-api-routes --ts --app --no-tailwind --no-eslint --no-src-dir cd nextjs-api-routes mkdir data # create the files above npm run dev

Open http://localhost:3000 , add a bookmark, and watch it appear in the list. Click delete to remove it. Check data/bookmarks.json to see changes persisted to disk.

Last updated on