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.tsimport { 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 paramlet 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 userexport async function POST(request: NextRequest) {const body = await request.json(); // parse request body// Validationif (!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 violationreturn 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