Next.js

Lesson 10

Route handlers as API boundaries

Use route handlers for HTTP APIs and keep them separate from page UI.

Good Code

app/api/reviews/[id]/route.ts
import { NextResponse } from "next/server";
import { getReview } from "@/lib/reviews";

export async function GET(
  _request: Request,
  context: RouteContext<"/api/reviews/[id]">,
) {
  const { id } = await context.params;
  const review = await getReview(id);

  // The route handler validates lookup results and returns explicit HTTP status.
  if (!review) {
    return NextResponse.json(
      { error: "Review not found." },
      { status: 404 },
    );
  }

  return NextResponse.json({ review });
}

Bad Code

app/reviews/[id]/page.tsx
import { getReview } from "@/lib/reviews";

// Mixing GET and page UI in one file blurs the App Router boundary.
export async function GET() {
  const review = await getReview("current");
  return Response.json(review);
}

export default async function ReviewPage() {
  const review = await getReview("current");

  return <h1>{review.title}</h1>;
}

Review Notes

What to review

Good Code

The good version keeps the HTTP API in a route.ts file and returns explicit JSON with a 404 when the record is missing.

Bad Code

The bad version mixes a route handler and page UI at the same segment, which conflicts with App Router route resolution and blurs the API boundary.

Takeaways

  • Route handlers should validate requests and return explicit HTTP responses.