API Routes

Learn how to create and secure API endpoints in your Next.js application using App Router.

Route Handlers

Create API endpoints using Next.js App Router's Route Handlers. These are located in the app/api directory.

// app/api/hello/route.ts
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({ message: "Hello World" });
}
export async function POST(request: Request) {
const data = await request.json();
return NextResponse.json({ received: data });
}

Dynamic Routes

Create dynamic API endpoints using folder and file naming conventions:

// app/api/users/[id]/route.ts
import { NextResponse } from "next/server";
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const { id } = params;
try {
const user = await db.user.findUnique({
where: { id },
});
if (!user) {
return NextResponse.json(
{ error: "User not found" },
{ status: 404 }
);
}
return NextResponse.json(user);
} catch (error) {
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}

Request Validation

Validate incoming requests using Zod schemas:

// app/api/posts/route.ts
import { z } from "zod";
import { NextResponse } from "next/server";
const postSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().min(1),
published: z.boolean().default(false),
});
export async function POST(request: Request) {
try {
const json = await request.json();
const body = postSchema.parse(json);
const post = await db.post.create({
data: body,
});
return NextResponse.json(post, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid request data", issues: error.issues },
{ status: 400 }
);
}
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}

Authentication & Authorization

Protect your API routes with authentication middleware:

// middleware/withAuth.ts
import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export async function withAuth(req: NextRequest) {
const res = NextResponse.next();
const supabase = createMiddlewareClient({ req, res });
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
);
}
// Add user to request for downstream use
const requestHeaders = new Headers(req.headers);
requestHeaders.set("x-user-id", session.user.id);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
// Usage in API route
export async function GET(request: Request) {
const userId = request.headers.get("x-user-id");
// Handle authenticated request
}

Error Handling

Implement consistent error handling across your API routes:

// lib/api/error.ts
export class APIError extends Error {
constructor(
public message: string,
public status: number,
public code?: string
) {
super(message);
}
}
// Usage in API route
export async function GET() {
try {
const data = await someOperation();
if (!data) {
throw new APIError("Resource not found", 404, "NOT_FOUND");
}
return NextResponse.json(data);
} catch (error) {
if (error instanceof APIError) {
return NextResponse.json(
{
error: error.message,
code: error.code
},
{ status: error.status }
);
}
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}

Best Practices

  • Always validate incoming request data
  • Implement proper error handling and status codes
  • Use TypeScript for better type safety
  • Add rate limiting for public endpoints
  • Keep routes modular and focused
  • Use middleware for cross-cutting concerns
  • Document your API endpoints

Next Steps