A

API Routes

Type-safe API route handlers in Next.js App Router with request/response typing.

Code

typescript
1// app/api/users/route.ts
2import { NextRequest, NextResponse } from "next/server";
3import { z } from "zod";
4
5// Types
6interface User {
7 id: number;
8 name: string;
9 email: string;
10 createdAt: Date;
11}
12
13// Validation schemas
14const CreateUserSchema = z.object({
15 name: z.string().min(2).max(50),
16 email: z.string().email(),
17});
18
19const QuerySchema = z.object({
20 page: z.coerce.number().min(1).default(1),
21 limit: z.coerce.number().min(1).max(100).default(10),
22 search: z.string().optional(),
23});
24
25// Response helpers
26function jsonResponse<T>(data: T, status = 200) {
27 return NextResponse.json(data, { status });
28}
29
30function errorResponse(message: string, status = 400) {
31 return NextResponse.json({ error: message }, { status });
32}
33
34// GET /api/users - List users
35export async function GET(request: NextRequest) {
36 try {
37 const { searchParams } = new URL(request.url);
38 const query = QuerySchema.parse(Object.fromEntries(searchParams));
39
40 // Simulate database query
41 const users: User[] = [
42 { id: 1, name: "Alice", email: "alice@example.com", createdAt: new Date() },
43 { id: 2, name: "Bob", email: "bob@example.com", createdAt: new Date() },
44 ];
45
46 return jsonResponse({
47 data: users,
48 pagination: {
49 page: query.page,
50 limit: query.limit,
51 total: users.length,
52 },
53 });
54 } catch (error) {
55 if (error instanceof z.ZodError) {
56 return errorResponse("Invalid query parameters", 400);
57 }
58 return errorResponse("Internal server error", 500);
59 }
60}
61
62// POST /api/users - Create user
63export async function POST(request: NextRequest) {
64 try {
65 const body = await request.json();
66 const data = CreateUserSchema.parse(body);
67
68 // Simulate creating user
69 const newUser: User = {
70 id: Date.now(),
71 ...data,
72 createdAt: new Date(),
73 };
74
75 return jsonResponse(newUser, 201);
76 } catch (error) {
77 if (error instanceof z.ZodError) {
78 return jsonResponse(
79 { error: "Validation failed", details: error.errors },
80 400
81 );
82 }
83 return errorResponse("Internal server error", 500);
84 }
85}
86
87// app/api/users/[id]/route.ts - Dynamic route
88interface RouteContext {
89 params: Promise<{ id: string }>;
90}
91
92export async function GET(request: NextRequest, context: RouteContext) {
93 const { id } = await context.params;
94 const userId = parseInt(id, 10);
95
96 if (isNaN(userId)) {
97 return errorResponse("Invalid user ID", 400);
98 }
99
100 // Simulate fetching user
101 const user: User | null = {
102 id: userId,
103 name: "Alice",
104 email: "alice@example.com",
105 createdAt: new Date(),
106 };
107
108 if (!user) {
109 return errorResponse("User not found", 404);
110 }
111
112 return jsonResponse(user);
113}
114
115export async function PATCH(request: NextRequest, context: RouteContext) {
116 const { id } = await context.params;
117 const userId = parseInt(id, 10);
118 const body = await request.json();
119
120 const UpdateSchema = CreateUserSchema.partial();
121 const data = UpdateSchema.parse(body);
122
123 // Simulate updating user
124 const updatedUser: User = {
125 id: userId,
126 name: data.name || "Alice",
127 email: data.email || "alice@example.com",
128 createdAt: new Date(),
129 };
130
131 return jsonResponse(updatedUser);
132}
133
134export async function DELETE(request: NextRequest, context: RouteContext) {
135 const { id } = await context.params;
136 // Simulate deleting user
137 return new NextResponse(null, { status: 204 });
138}

Run this example locally

$ npx create-next-app@latest --typescript