Next.js API routes — build your first endpoint step by step

Learn how to create API routes in Next.js 15. Handle GET and POST requests, validate input, return JSON, and connect to a database. Beginner-friendly with full code examples.

What are API routes?

API routes let you build a backend inside your Next.js project. Instead of setting up a separate server, you create files in app/api/ and Next.js turns them into endpoints. Your frontend can call them, and so can external services (like Stripe webhooks).

Think of it this way: app/api/hello/route.ts becomes https://yourapp.com/api/hello. That's it.


Step 1: Create a GET endpoint

Create the file app/api/hello/route.ts. Add this code:

app/api/hello/route.ts
// app/api/hello/route.ts
import { NextResponse } from "next/server";

export async function GET() {
  return NextResponse.json({
    message: "Hello from the API!",
    time: new Date().toISOString(),
  });
}

Open http://localhost:3000/api/hello in your browser. You'll see the JSON response. That's your first API endpoint.


Step 2: Handle POST requests

Most forms send POST requests. Here's how to read the body and validate the input:

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

export async function POST(req: NextRequest) {
  const body = await req.json();

  // Validate input
  if (!body.email || !body.message) {
    return NextResponse.json(
      { error: "Email and message are required" },
      { status: 400 }
    );
  }

  // Do something with the data
  // e.g. save to database, send email, etc.
  console.log("New contact:", body.email, body.message);

  return NextResponse.json({ success: true });
}

Key takeaways: use await req.json() to parse the body, always validate before doing anything, and return proper status codes (400 for bad input, 200 for success).


Step 3: Call it from the frontend

In your React component, use fetch to call the endpoint:

React component
// In any client component
const handleSubmit = async () => {
  const res = await fetch("/api/contact", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      email: "user@example.com",
      message: "Hello!",
    }),
  });

  const data = await res.json();

  if (res.ok) {
    alert("Sent!");
  } else {
    alert(data.error);
  }
};

Notice: no absolute URL needed. /api/contact works because your frontend and API live in the same project.


Step 4: Add authentication

To protect an endpoint (only logged-in users), check the session:

app/api/profile/route.ts
// app/api/profile/route.ts
import { createClient } from "@/libs/supabase/server";
import { NextResponse } from "next/server";

export async function GET() {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) {
    return NextResponse.json(
      { error: "Not logged in" },
      { status: 401 }
    );
  }

  return NextResponse.json({ email: user.email });
}

Important in Next.js 15: createClient() is async on the server — always use await. Learn more in our Supabase Auth guide.


Common patterns

  • GET — read data (list items, get profile)
  • POST — create data (submit form, start checkout)
  • PUT / PATCH — update data (edit profile)
  • DELETE — remove data (delete account)

Export the function name that matches the HTTP method: export async function GET(), export async function POST(), etc. You can have multiple methods in the same route.ts file.


20+ API routes ready to use

Delfy comes with API routes for Stripe checkout, webhooks, user profiles, community invites, and more — all with proper validation and error handling.

See what's included
Next.js API routes — build your first endpoint step by step | Delfy.dev Blog