跳到主要内容

API 路由

Next.js 允许在同一项目中创建 API 接口,无需单独的后端服务。本章将介绍如何使用 Route Handlers 构建 API。

Route Handlers

在 App Router 中,使用 route.ts 文件定义 API 路由。

基本 API

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

export async function GET() {
return NextResponse.json({
message: "Hello from API",
});
}

访问 /api/hello 将返回 JSON 响应。

HTTP 方法

支持所有 HTTP 方法:

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

// GET /api/posts
export async function GET() {
const posts = await getPosts();
return NextResponse.json(posts);
}

// POST /api/posts
export async function POST(request: Request) {
const body = await request.json();
const post = await createPost(body);
return NextResponse.json(post, { status: 201 });
}

// PUT /api/posts
export async function PUT(request: Request) {
const body = await request.json();
const post = await updatePost(body);
return NextResponse.json(post);
}

// DELETE /api/posts
export async function DELETE(request: Request) {
const { searchParams } = new URL(request.url);
const id = searchParams.get("id");
await deletePost(id);
return new NextResponse(null, { status: 204 });
}

请求处理

获取请求体

export async function POST(request: Request) {
// JSON 数据
const json = await request.json();

// 表单数据
const formData = await request.formData();
const name = formData.get("name");

// 文本数据
const text = await request.text();

return NextResponse.json({ received: json });
}

获取 URL 参数

// src/app/api/posts/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const post = await getPost(params.id);
return NextResponse.json(post);
}

获取查询参数

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = searchParams.get("page") || "1";
const limit = searchParams.get("limit") || "10";

const posts = await getPosts({ page, limit });
return NextResponse.json(posts);
}

获取请求头

export async function GET(request: Request) {
const authorization = request.headers.get("authorization");
const userAgent = request.headers.get("user-agent");

return NextResponse.json({ authorization, userAgent });
}
import { cookies } from "next/headers";

export async function GET() {
const cookieStore = await cookies();
const token = cookieStore.get("token");

return NextResponse.json({ token: token?.value });
}

响应处理

JSON 响应

import { NextResponse } from "next/server";

export async function GET() {
return NextResponse.json(
{ message: "Success", data: [] },
{ status: 200 }
);
}

自定义响应头

export async function GET() {
return NextResponse.json(
{ message: "Success" },
{
headers: {
"Cache-Control": "public, max-age=3600",
"X-Custom-Header": "value",
},
}
);
}

重定向

import { redirect } from "next/navigation";

export async function GET() {
redirect("/login");
}
import { NextResponse } from "next/server";

export async function POST(request: Request) {
const body = await request.json();

const response = NextResponse.json({ success: true });

response.cookies.set("token", "your-jwt-token", {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 60 * 60 * 24 * 7, // 7 天
});

return response;
}

动态路由

动态段

// src/app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const user = await getUser(params.id);

if (!user) {
return NextResponse.json({ error: "用户不存在" }, { status: 404 });
}

return NextResponse.json(user);
}

捕获所有路由

// src/app/api/docs/[...slug]/route.ts
export async function GET(
request: Request,
{ params }: { params: { slug: string[] } }
) {
const path = params.slug.join("/");
const doc = await getDoc(path);

return NextResponse.json(doc);
}

中间件集成

在 API 路由中使用中间件验证:

// src/app/api/admin/route.ts
import { NextResponse } from "next/server";
import { cookies } from "next/headers";

async function verifyAuth() {
const cookieStore = await cookies();
const token = cookieStore.get("token");
if (!token) return null;
return verifyToken(token.value);
}

export async function GET() {
const user = await verifyAuth();

if (!user) {
return NextResponse.json({ error: "未授权" }, { status: 401 });
}

return NextResponse.json({ user });
}

实战示例

用户注册 API

// src/app/api/auth/register/route.ts
import { NextResponse } from "next/server";
import { hash } from "bcrypt";
import { z } from "zod";

const registerSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(2),
});

export async function POST(request: Request) {
try {
const body = await request.json();

// 验证输入
const { email, password, name } = registerSchema.parse(body);

// 检查用户是否存在
const existingUser = await db.user.findUnique({ where: { email } });
if (existingUser) {
return NextResponse.json(
{ error: "邮箱已被注册" },
{ status: 400 }
);
}

// 创建用户
const hashedPassword = await hash(password, 10);
const user = await db.user.create({
data: { email, password: hashedPassword, name },
});

return NextResponse.json(
{ id: user.id, email: user.email, name: user.name },
{ status: 201 }
);
} catch (error) {
return NextResponse.json(
{ error: "注册失败" },
{ status: 500 }
);
}
}

文件上传 API

// src/app/api/upload/route.ts
import { NextResponse } from "next/server";
import { writeFile } from "fs/promises";
import path from "path";

export async function POST(request: Request) {
try {
const formData = await request.formData();
const file = formData.get("file") as File;

if (!file) {
return NextResponse.json({ error: "未选择文件" }, { status: 400 });
}

const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);

const filename = `${Date.now()}-${file.name}`;
const filepath = path.join(process.cwd(), "public/uploads", filename);

await writeFile(filepath, buffer);

return NextResponse.json({
url: `/uploads/${filename}`,
filename,
});
} catch (error) {
return NextResponse.json({ error: "上传失败" }, { status: 500 });
}
}

分页 API

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

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get("page") || "1");
const limit = parseInt(searchParams.get("limit") || "10");
const skip = (page - 1) * limit;

const [posts, total] = await Promise.all([
db.post.findMany({
skip,
take: limit,
orderBy: { createdAt: "desc" },
}),
db.post.count(),
]);

return NextResponse.json({
data: posts,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
});
}

小结

本章我们学习了:

  1. Route Handlers 的基本用法
  2. 各种 HTTP 方法的处理
  3. 请求参数和请求体的获取
  4. 响应的构建和 Cookie 操作
  5. 动态路由 API
  6. 实战示例

练习

  1. 创建一个简单的 CRUD API(增删改查)
  2. 实现一个带验证的用户登录 API
  3. 创建一个分页查询 API
  4. 实现一个文件上传 API