学习目标
- 1理解API路由的概念和使用场景
- 2学会创建RESTful API端点
- 3掌握请求处理(查询参数、请求体、请求头)
- 4了解数据验证的重要性
回顾与问题引入
上一章我们学会了用Next.js创建页面。现在博客有了"门面"——首页、文章详情页、关于页面。但是有一个问题:页面上的数据是硬编码的。
你可能会想:我怎么让页面从数据库获取数据?怎么让用户提交表单?怎么实现"删除文章"这种操作?
答案是:API路由。
类比理解:
想象你的博客是一家餐厅:
- 页面(上一章学的)= 餐厅的菜单和装修
- API路由(本章学的)= 厨房,处理"做菜"的逻辑
客人(前端)通过菜单(页面)下单,厨房(API)处理订单,然后把做好的菜(数据)送回去。
API路由解决的问题:
- 前端需要获取数据(GET请求)
- 用户需要提交表单(POST请求)
- 用户需要修改数据(PUT请求)
- 用户需要删除数据(DELETE请求)
什么是API路由?
Next.js允许你在同一个项目中创建API端点,不需要单独搭建后端服务器。
传统方式 vs Next.js方式:
| 方面 | 传统方式 | Next.js方式 | |------|---------|------------| | 项目结构 | 前端项目 + 后端项目 | 一个项目 | | 部署 | 分别部署 | 统一部署 | | 类型共享 | 需要额外配置 | 天然共享 | | 开发效率 | 需要切换上下文 | 在一个地方完成 |
API路由的文件位置:
app/
├── api/ # API路由目录
│ ├── users/
│ │ └── route.ts # /api/users
│ └── posts/
│ ├── route.ts # /api/posts
│ └── [id]/
│ └── route.ts # /api/posts/:id
关键规则:
- API路由放在
app/api/目录下 - 文件名必须是
route.ts - 导出命名函数:GET、POST、PUT、DELETE
创建第一个API端点
动态API路由:处理单个资源
有时候你需要处理单个资源——比如获取ID为1的文章、更新ID为2的文章。这需要用到动态路由参数。
类比理解:
动态API路由就像快递柜:
/api/posts→ 快递柜的总入口(查看所有快递)/api/posts/123→ 123号柜子(查看特定快递)
参数获取方式:
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const id = params.id; // 获取URL中的id参数
}
注意:第二个参数是一个对象,包含params属性。
动态API路由实战
请求处理:获取客户端发送的数据
API需要接收客户端发送的数据。根据请求类型,获取数据的方式不同:
三种常见数据来源:
| 数据来源 | 获取方式 | 使用场景 |
|----------|---------|---------|
| 查询参数 | new URL(request.url).searchParams | 搜索、分页、过滤 |
| 请求体 | request.json() | 表单提交、创建/更新数据 |
| 请求头 | request.headers.get("Authorization") | 认证、内容类型 |
类比理解:
- 查询参数 = 快递单上的备注("请放门口")
- 请求体 = 快递里面的东西(实际的包裹)
- 请求头 = 快递单上的发件人信息(谁寄的)
处理不同类型的数据
数据验证:永远不要相信客户端
这是一个重要的安全原则:永远不要相信客户端发送的数据。
用户可能发送:
- 空的标题
- 超长的内容
- 恶意的HTML代码
- 不合法的邮箱格式
类比理解:
数据验证就像机场安检:
- 所有行李(数据)都要过安检(验证)
- 不合格的行李会被拦截(返回400错误)
- 合格的行李才能进入候机厅(进入业务逻辑)
推荐使用Zod库: Zod是TypeScript优先的数据验证库,语法简洁,类型推断强大。
使用Zod进行数据验证
常见错误
常见错误
错误1:忘记导出命名函数
// 错误!函数名必须是 GET、POST、PUT、DELETE
export default function handler() {} // 不工作
// 正确
export async function GET() {}
错误2:文件名不是route.ts
// 错误!app/api/hello.ts 不会创建API路由
// 正确:app/api/hello/route.ts
错误3:忘记await request.json()
// 错误!request.json() 返回 Promise
const body = request.json(); // body 是 Promise,不是对象
// 正确
const body = await request.json(); // body 是解析后的对象
错误4:在GET请求中读取请求体
// 错误!GET请求通常没有请求体
export async function GET(request: Request) {
const body = await request.json(); // 可能报错
}
// 正确:GET请求使用查询参数
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = searchParams.get("page");
}
错误5:没有处理错误情况
// 危险!没有try-catch
export async function POST(request: Request) {
const body = await request.json(); // 如果请求体不是JSON会崩溃
}
// 安全
export async function POST(request: Request) {
try {
const body = await request.json();
// ...
} catch (error) {
return NextResponse.json({ error: "请求格式错误" }, { status: 400 });
}
}
动手实践
现在轮到你了!让我们动手练习:
练习1:创建用户API 创建一个完整的用户CRUD API:
- GET /api/users - 获取所有用户
- POST /api/users - 创建用户
- GET /api/users/[id] - 获取单个用户
- PUT /api/users/[id] - 更新用户
- DELETE /api/users/[id] - 删除用户
练习2:添加搜索功能 创建一个搜索API:
- GET /api/posts/search?q=keyword
- 根据关键词搜索文章标题和内容
- 返回匹配的文章列表
练习3:数据验证 使用Zod验证用户输入:
- 验证邮箱格式
- 验证密码长度(至少8位)
- 验证用户名(只能包含字母和数字)
提示:
- 使用NextResponse.json()返回JSON响应
- 使用{ status: 400 }返回错误状态码
- 使用try-catch处理可能的错误
本章总结
恭喜你完成了Next.js API路由的学习!让我们回顾一下:
本章学到的:
- API路由让前后端写在一个项目里,共享TypeScript类型
- 文件放在app/api/目录下,文件名必须是route.ts
- 导出GET、POST、PUT、DELETE函数处理不同请求
- 使用Zod进行数据验证,永远不要相信客户端
前后对比:
| 方面 | 之前(无API) | 现在(有API) | |------|-------------|-------------| | 数据来源 | 硬编码在页面中 | 从API获取 | | 用户交互 | 只能展示 | 可以提交表单、修改数据 | | 项目结构 | 只有前端 | 前后端一体化 | | 类型安全 | 页面内部 | 前后端共享类型 |
下一章预告:
有了页面和API,我们的博客还需要可复用的React组件。下一章我们将学习如何用TypeScript构建React组件——按钮、卡片、表单,让你的博客更专业。
交互式练习
创建文章API
创建一个GET /api/posts端点,返回文章列表
章节测验
Next.js API路由文件放在哪个目录?