Fullstack/REST API & HTTP

REST API & HTTP

The foundation of how clients and servers communicate. Every Next.js Route Handler, every fetch call, and every third-party API integration relies on these concepts.


HTTP Methods

Each method signals the intent of a request. The server decides what to actually do, but following conventions makes APIs predictable.

MethodIntentHas BodyIdempotent
GETRead / fetch dataNoYes
POSTCreate a new resourceYesNo
PUTReplace a resource entirelyYesYes
PATCHPartially update a resourceYesNo
DELETERemove a resourceNoYes

Idempotent means calling it multiple times produces the same result. DELETE /users/1 twice still results in the user being deleted — no extra side effects.

// Next.js Route Handler — app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";

// GET /api/users
export async function GET() {
  const users = await db.user.findMany();
  return NextResponse.json(users);
}

// POST /api/users
export async function POST(req: NextRequest) {
  const body = await req.json();
  const user = await db.user.create({ data: body });
  return NextResponse.json(user, { status: 201 });
}

// app/api/users/[id]/route.ts
// PATCH /api/users/:id
export async function PATCH(req: NextRequest, { params }: { params: { id: string } }) {
  const body = await req.json();
  const user = await db.user.update({ where: { id: params.id }, data: body });
  return NextResponse.json(user);
}

// DELETE /api/users/:id
export async function DELETE(_req: NextRequest, { params }: { params: { id: string } }) {
  await db.user.delete({ where: { id: params.id } });
  return new NextResponse(null, { status: 204 });
}

HTTP Status Codes

The three-digit code tells the client what happened — before it even reads the response body.

2xx — Success

CodeNameWhen to use
200OKSuccessful GET, PUT, PATCH
201CreatedResource successfully created (POST)
204No ContentSuccess but nothing to return (DELETE)

3xx — Redirection

CodeNameWhen to use
301Moved PermanentlyURL has changed permanently
302FoundTemporary redirect
304Not ModifiedCached version is still valid

4xx — Client Error

CodeNameWhen to use
400Bad RequestInvalid input, malformed body
401UnauthorizedNot authenticated (no/invalid token)
403ForbiddenAuthenticated but no permission
404Not FoundResource doesn't exist
409ConflictDuplicate resource (e.g. email already taken)
422Unprocessable EntityValidation failed
429Too Many RequestsRate limit hit

5xx — Server Error

CodeNameWhen to use
500Internal Server ErrorUnhandled exception on the server
502Bad GatewayUpstream server returned invalid response
503Service UnavailableServer down or overloaded

Headers

Headers carry metadata about the request or response.

Common Request Headers

HeaderPurposeExample
Content-TypeFormat of the request bodyapplication/json
AuthorizationAuth credentialsBearer <token>
AcceptExpected response formatapplication/json
X-Request-IDTrace ID for debugginguuid-v4

Common Response Headers

HeaderPurposeExample
Content-TypeFormat of the response bodyapplication/json
Cache-ControlCaching rulesno-store, max-age=3600
X-RateLimit-RemainingRequests left in window42
// Setting headers in a Next.js response
return NextResponse.json(data, {
  status: 200,
  headers: {
    "Cache-Control": "public, max-age=60, stale-while-revalidate=300",
    "X-Request-ID": crypto.randomUUID(),
  },
});

Authentication Patterns

Bearer Token (JWT)

The most common pattern for APIs. The client sends a token in the Authorization header.

// Client — fetch with auth token
const res = await fetch("/api/protected", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

// Server — validate the token
export async function GET(req: NextRequest) {
  const auth = req.headers.get("Authorization");
  if (!auth?.startsWith("Bearer ")) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  const token = auth.slice(7);
  // verify token...
}

API Key

Common for server-to-server or third-party integrations.

const res = await fetch("https://api.example.com/data", {
  headers: {
    "X-API-Key": process.env.API_KEY!,
  },
});

CORS

CORS (Cross-Origin Resource Sharing) controls which origins can call your API from a browser.

// Middleware or route handler — add CORS headers
export async function OPTIONS() {
  return new NextResponse(null, {
    status: 204,
    headers: {
      "Access-Control-Allow-Origin": "https://yourdomain.com",
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
  });
}

For Next.js apps calling their own Route Handlers, CORS is not needed — same origin. It only matters for external clients.


REST URL Design

Good URL design makes an API intuitive and predictable.

# Resources are nouns, not verbs
GET    /api/users          → list all users
POST   /api/users          → create a user
GET    /api/users/:id      → get one user
PATCH  /api/users/:id      → update a user
DELETE /api/users/:id      → delete a user

# Nested resources
GET    /api/users/:id/posts     → posts by a user
POST   /api/users/:id/posts     → create a post for a user

# Filtering / sorting via query params, not path
GET    /api/users?role=admin&sort=createdAt&order=desc
// Pagination pattern
export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url);
  const page  = Number(searchParams.get("page")  ?? 1);
  const limit = Number(searchParams.get("limit") ?? 20);

  const [items, total] = await Promise.all([
    db.user.findMany({ skip: (page - 1) * limit, take: limit }),
    db.user.count(),
  ]);

  return NextResponse.json({
    data: items,
    pagination: { page, limit, total, pages: Math.ceil(total / limit) },
  });
}

Last updated: September 2026.

Last updated · September 2026