DG
DevGuide
Run Code
Home/Next.js/API Routes
N Next.js3 / 3

API Routes

📌 WHAT

Next.js allows you to create backend API endpoints as files in the app/api/ directory. These run on the server and never expose code to the browser.

💡 WHY

Build your entire full-stack app in one project. API routes replace the need for a separate Express/Node.js server for simple backends.

⏰ WHEN / WHERE

Form submissions, database operations, authentication, webhooks, or any server-side logic that needs to be called from the client.

🔧 HOW

Create app/api/[endpoint]/route.ts and export named functions GET, POST, PUT, DELETE corresponding to HTTP methods.

Code Examples

#1API routes — full CRUD example

API routes — full CRUD example
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY! // service key for server-side
);
// GET /api/users
// GET /api/users?role=admin (with query params)
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const role = searchParams.get("role"); // get query param
let query = supabase.from("users").select("*");
if (role) query = query.eq("role", role);
const { data, error } = await query;
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json(data); // { status: 200 } by default
}
// POST /api/users — create new user
export async function POST(request: NextRequest) {
const body = await request.json(); // parse request body
// Validation
if (!body.name || !body.email) {
return NextResponse.json(
{ error: "name and email are required" },
{ status: 400 }
);
}
const { data, error } = await supabase
.from("users")
.insert({ name: body.name, email: body.email })
.select()
.single();
if (error) {
if (error.code === "23505") { // unique constraint violation
return NextResponse.json({ error: "Email already exists" }, { status: 409 });
}
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json(data, { status: 201 });
}
// ------------------------------------------------
// Dynamic route: app/api/users/[id]/route.ts
// ------------------------------------------------
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const { data, error } = await supabase
.from("users")
.select("*")
.eq("id", params.id)
.single();
if (!data) return NextResponse.json({ error: "Not found" }, { status: 404 });
return NextResponse.json(data);
}
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const body = await request.json();
const { data, error } = await supabase
.from("users")
.update(body)
.eq("id", params.id)
.select()
.single();
return NextResponse.json(data);
}
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
await supabase.from("users").delete().eq("id", params.id);
return NextResponse.json({ success: true });
}

Practice This Concept

Open the playground, paste any example, and experiment. The best way to learn is by breaking things.

Open in Playground