Stop 10 · Full-Stack FrameworksTopics · 3/7

API Routes & Server Actions

Part of the Full Stack Developer Roadmap

In short

Next.js lets you write backend code right next to your pages. Route handlers give you real HTTP endpoints, and server actions let a form call a server function directly. Each fits a different job.

Next.js's built-in ways to write backend logic inside the same project as your frontend — API routes for traditional endpoints, Server Actions for form-submission-style mutations callable directly from components.

The blob character posts a letter in a red postbox while pressing a wall intercom button.

Think of it like this

Postbox vs the society intercom

A postbox on the street takes letters from anyone, from anywhere. The intercom in your building connects only your flat to the society office, with one button press. Route handlers are the postbox: any client can call them. Server actions are the intercom, made for your own app's forms.

Watch it happen

Two ways to run server code

Route handler

  • app/api/notes/route.ts exports GET and POST
  • Any client: mobile app, webhook, curl
  • You handle JSON, status codes, caching

Server action

  • An async function marked 'use server'
  • Called from your forms or client code
  • Refreshes the page data after a write

Use server actions for your own app's writes. Use route handlers when something outside your React UI must call you.

The big ideas

What you need to know

Route handlers are endpoints

A route.ts file exports functions named after HTTP methods. Each receives a Request and returns a Response.

Server actions are functions

Mark an async function with 'use server'. Next.js turns calls to it into a POST request behind the scenes.

Both are public

A server action still has an endpoint under the hood. Check the session and validate input inside it, every time.

Refresh after writes

Call revalidatePath or revalidateTag in an action so server components show the new data.

See it in code

Walk through the code

ts
1// app/api/notes/route.ts: a real endpoint for any client2export async function GET() {3  const notes = await db.note.findMany();4  return Response.json(notes);5}6// app/notes/actions.ts: a server action for your own forms7"use server";8import { revalidatePath } from "next/cache";9export async function createNote(formData: FormData) {10  const session = await auth();11  if (!session) throw new Error("Not signed in");12  const text = String(formData.get("text") ?? "").trim();13  if (!text) return;14  await db.note.create({ data: { text, userId: session.user.id } });15  revalidatePath("/notes");16}

1/4GET /api/notes returns JSON. A mobile app or curl can call it like any API.

Use the action as <form action={createNote}>. auth() stands for your auth library's session helper, such as the one Auth.js gives you.

Save yourself the pain

Common mistakes

Oops

Creating an API route just so your own form can fetch it from a client component.

Do this instead

For your own UI, a server action is less code: no fetch, no JSON parsing, and the page refreshes itself.

Oops

Skipping auth checks in a server action because 'only my form calls it'.

Do this instead

Anyone can call it with a crafted request. Check the session and validate input in every action.

Your mission

One endpoint, one action

~30 min

Quick check

Your React Native app also needs to read the notes. What should you build?

Go deeper

  1. 1Build a simple API route and call it from a client component.
  2. 2Build a form that submits directly to a Server Action instead of a separate API call.
  3. 3Compare when you'd reach for one vs the other.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge