API Routes & Server Actions

Next.js provides two patterns for server-side logic: Route Handlers for traditional API endpoints and Server Actions for form handling and data mutations.

Route Handlers

Define HTTP endpoints in the app/api/ directory using the Web Request and Response APIs.

// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const users = await db.query("SELECT * FROM users");
  return NextResponse.json(users);
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  const user = await db.insert("users", body);
  return NextResponse.json(user, { status: 201 });
}

Supported HTTP Methods

MethodUse Case
GETFetch data
POSTCreate a resource
PUTReplace a resource
PATCHPartially update a resource
DELETERemove a resource

Dynamic Route Handlers

// app/api/users/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const user = await db.findById("users", id);
  if (!user) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }
  return NextResponse.json(user);
}

Server Actions

Server Actions run on the server and can be called directly from Client Components. Mark a function with "use server" to create an action.

// app/actions/create-post.ts
"use server";

import { revalidatePath } from "next/cache";

export async function createPost(formData: FormData) {
  const title = formData.get("title") as string;
  const content = formData.get("content") as string;

  await db.insert("posts", { title, content });
  revalidatePath("/posts");
}

Using Actions in Forms

// app/posts/new/page.tsx
import { createPost } from "@/app/actions/create-post";

export default function NewPostPage() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="Post title" required />
      <textarea name="content" placeholder="Write your post..." required />
      <button type="submit">Publish</button>
    </form>
  );
}

Calling Actions from Client Components

"use client";

import { useTransition } from "react";
import { createPost } from "@/app/actions/create-post";

export function PublishButton() {
  const [isPending, startTransition] = useTransition();

  const handleClick = () => {
    const formData = new FormData();
    formData.set("title", "My Post");
    formData.set("content", "Hello world");
    startTransition(() => createPost(formData));
  };

  return (
    <button onClick={handleClick} disabled={isPending}>
      {isPending ? "Publishing..." : "Publish"}
    </button>
  );
}

When to Use Each Pattern

ScenarioUse
Third-party API proxyRoute Handler
Webhook receiverRoute Handler
Form submissionServer Action
Data mutation from UIServer Action
Public REST APIRoute Handler
Revalidating cached dataServer Action

Server Actions are the preferred pattern for mutations in Next.js. Use Route Handlers when you need a traditional HTTP endpoint (webhooks, external consumers, etc.).