Data Fetching with Route Handlers
Data Fetching with Route Handlers
Section titled “Data Fetching with Route Handlers”Introduction
Section titled “Introduction”While Server Components can fetch data directly, Route Handlers (route.ts) serve as API endpoints for Client Components, mobile apps, and third-party services. They provide a standard REST interface for data access.
Why do we need this?
Section titled “Why do we need this?”Server Components fetch data directly at render time. But Client Components, mobile apps, and external services need API endpoints. Route Handlers fill this gap — they’re the bridge between client-side code and server-side data.
When to Use Route Handlers vs Direct Fetching
Section titled “When to Use Route Handlers vs Direct Fetching”| Scenario | Approach | Why |
|---|---|---|
| Server Component needs data | Direct fetch in component | Simplest, cached automatically |
| Client Component needs data | Route Handler + fetch | Client can’t fetch DB directly |
| Mobile app needs data | Route Handler | External access via REST |
| Webhook endpoints | Route Handler | POST endpoint for external services |
| Form submission (simple) | Server Action | Less boilerplate |
| Form submission (complex) | Route Handler | More control, custom logic |
Basic Route Handler
Section titled “Basic Route Handler”import { NextResponse } from 'next/server'
export async function GET() { const products = await db.product.findMany() return NextResponse.json(products)}
export async function POST(request: Request) { const body = await request.json() const product = await db.product.create({ data: body }) return NextResponse.json(product, { status: 201 })}Consuming from Client Component
Section titled “Consuming from Client Component”'use client'import { useEffect, useState } from 'react'
export function ProductList() { const [products, setProducts] = useState([])
useEffect(() => { fetch('/api/products') .then(res => res.json()) .then(setProducts) }, [])
return <div>{products.length} products</div>}Dynamic Route Handlers
Section titled “Dynamic Route Handlers”import { NextResponse } from 'next/server'
export async function GET( request: Request, { params }: { params: { id: string } }) { const product = await db.product.findUnique({ where: { id: params.id } })
if (!product) { return NextResponse.json( { error: 'Not found' }, { status: 404 } ) }
return NextResponse.json(product)}Caching Route Handlers
Section titled “Caching Route Handlers”// GET requests are cached by default// Opt out with:export const dynamic = 'force-dynamic'
// Or configure per fetch:export async function GET() { const data = await fetch('https://api.example.com/data', { cache: 'no-store' // Never cache this Route Handler }) return NextResponse.json(await data.json())}Common Mistakes
Section titled “Common Mistakes”- Not parsing request body — For POST/PUT, always
await request.json() - Missing error handling — Wrap in try/catch and return proper status codes
- Exposing sensitive data — Route Handlers return data to clients; filter responses
- Not handling OPTIONS — CORS preflight fails without an OPTIONS handler
Best Practices
Section titled “Best Practices”- Use Route Handlers for external APIs — Mobile apps, webhooks, third-party services
- Prefer Server Actions for form mutations — Less boilerplate, automatic revalidation
- Prefetch Route Handlers — Use
<Link prefetch={true}>to preload API data - Validate input — Use Zod or similar for request validation
- Return proper status codes — 200, 201, 400, 404, 500
Summary
Section titled “Summary”Route Handlers provide API endpoints for client-side and external data access. Use them when direct Server Component fetching isn’t possible (Client Components, mobile apps, webhooks). For form mutations, prefer Server Actions.