Next.js 系列教程(二):API 路由与数据处理
目录
- API 路由简介
- 创建基本 API 路由
- 处理不同 HTTP 方法
- 动态 API 路由
- 请求和响应处理
- 中间件使用
- 数据库集成
- 错误处理和验证
1. API 路由简介
Next.js 不仅是一个前端框架,它还提供了强大的后端能力。通过 API 路由功能,你可以在同一个项目中创建后端 API 接口,无需单独维护后端服务。
API 路由的优势:
- 统一开发体验: 前后端代码在同一项目中管理
- 简化部署: 前后端一起部署,减少运维复杂度
- 快速原型开发: 快速构建全栈应用
- 无缝集成: 前端可以直接调用同源 API
2. 创建基本 API 路由
在 Next.js 中,API 路由文件放置在 src/app/api/ 目录下。每个路由文件都会自动成为对应的 API 端点。
创建第一个 API 路由
创建 src/app/api/hello/route.ts:
1 2 3 4 5 6
| import { NextResponse } from 'next/server';
export async function GET() { return NextResponse.json({ message: 'Hello, Next.js!' }); }
|
访问 http://localhost:3000/api/hello 就可以看到返回的 JSON 数据。
返回不同类型的响应
1 2 3 4 5 6 7 8 9 10 11 12
| import { NextResponse } from 'next/server';
export async function GET() { return new NextResponse('Hello, World!', { status: 200, headers: { 'Content-Type': 'text/plain', }, }); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { NextResponse } from 'next/server';
export async function GET() { const html = ` <!DOCTYPE html> <html> <head> <title>Next.js API</title> </head> <body> <h1>Hello from Next.js API Route!</h1> </body> </html> `; return new NextResponse(html, { status: 200, headers: { 'Content-Type': 'text/html', }, }); }
|
3. 处理不同 HTTP 方法
API 路由可以处理各种 HTTP 方法,如 GET、POST、PUT、DELETE 等。
多方法处理
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
| import { NextResponse } from 'next/server';
export async function GET() { const users = [ { id: 1, name: '张三', email: 'zhangsan@example.com' }, { id: 2, name: '李四', email: 'lisi@example.com' }, ]; return NextResponse.json(users); }
export async function POST(request: Request) { const userData = await request.json(); const newUser = { id: Date.now(), ...userData }; return NextResponse.json(newUser, { status: 201 }); }
export async function PUT(request: Request) { const userData = await request.json(); return NextResponse.json({ message: '用户更新成功', user: userData }); }
export async function DELETE() { return NextResponse.json({ message: '用户删除成功' }); }
|
测试 API 路由
可以使用 curl 命令测试 API:
1 2 3 4 5 6 7
| curl http://localhost:3000/api/users
curl -X POST http://localhost:3000/api/users \ -H "Content-Type: application/json" \ -d '{"name":"王五","email":"wangwu@example.com"}'
|
4. 动态 API 路由
Next.js 支持动态 API 路由,类似于页面路由中的动态路由。
基本动态路由
创建 src/app/api/users/[id]/route.ts:
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
| import { NextResponse } from 'next/server';
const users = [ { id: '1', name: '张三', email: 'zhangsan@example.com' }, { id: '2', name: '李四', email: 'lisi@example.com' }, ];
export async function GET( request: Request, { params }: { params: { id: string } } ) { const { id } = params; const user = users.find(u => u.id === id); if (!user) { return NextResponse.json( { error: '用户未找到' }, { status: 404 } ); } return NextResponse.json(user); }
export async function PUT( request: Request, { params }: { params: { id: string } } ) { const { id } = params; const userData = await request.json(); const userIndex = users.findIndex(u => u.id === id); if (userIndex === -1) { return NextResponse.json( { error: '用户未找到' }, { status: 404 } ); } users[userIndex] = { ...users[userIndex], ...userData }; return NextResponse.json(users[userIndex]); }
export async function DELETE( request: Request, { params }: { params: { id: string } } ) { const { id } = params; const userIndex = users.findIndex(u => u.id === id); if (userIndex === -1) { return NextResponse.json( { error: '用户未找到' }, { status: 404 } ); } users.splice(userIndex, 1); return NextResponse.json({ message: '用户删除成功' }); }
|
复杂动态路由
创建 src/app/api/posts/[userId]/[postId]/route.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { NextResponse } from 'next/server';
export async function GET( request: Request, { params }: { params: { userId: string; postId: string } } ) { const { userId, postId } = params; const post = { id: postId, userId: userId, title: '文章标题', content: '文章内容...', createdAt: new Date().toISOString() }; return NextResponse.json(post); }
|
5. 请求和响应处理
解析请求数据
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
| import { NextResponse } from 'next/server';
export async function POST(request: Request) { const jsonData = await request.json(); console.log('JSON 数据:', jsonData); const formData = await request.formData(); const formObject: Record<string, string> = {}; formData.forEach((value, key) => { formObject[key] = value.toString(); }); console.log('表单数据:', formObject); const { searchParams } = new URL(request.url); const name = searchParams.get('name'); console.log('查询参数 name:', name); return NextResponse.json({ message: '数据接收成功', jsonData, formObject, queryParams: { name } }); }
|
设置响应头和 Cookie
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
| import { NextResponse } from 'next/server';
export async function POST(request: Request) { const { username, password } = await request.json(); if (username === 'admin' && password === 'password') { const response = NextResponse.json({ message: '登录成功', user: { id: 1, username: 'admin' } }); response.cookies.set('auth-token', 'your-jwt-token', { httpOnly: true, secure: process.env.NODE_ENV === 'production', maxAge: 60 * 60 * 24, path: '/', }); return response; } return NextResponse.json( { error: '用户名或密码错误' }, { status: 401 } ); }
|
流式响应
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
| export async function GET() { const stream = new ReadableStream({ async start(controller) { const encoder = new TextEncoder(); controller.enqueue(encoder.encode('第一块数据\n')); await new Promise(resolve => setTimeout(resolve, 1000)); controller.enqueue(encoder.encode('第二块数据\n')); await new Promise(resolve => setTimeout(resolve, 1000)); controller.enqueue(encoder.encode('第三块数据\n')); controller.close(); }, }); return new Response(stream, { headers: { 'Content-Type': 'text/plain', 'Transfer-Encoding': 'chunked', }, }); }
|
6. 中间件使用
Next.js 中间件可以对请求进行拦截和处理。
创建中间件
创建 src/middleware.ts:
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
| import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) { const path = request.nextUrl.pathname; const token = request.cookies.get('auth-token'); const protectedPaths = ['/api/admin', '/dashboard']; const isProtectedPath = protectedPaths.some(p => path.startsWith(p)); if (isProtectedPath && !token) { return NextResponse.redirect(new URL('/login', request.url)); } const response = NextResponse.next(); response.headers.set('X-Custom-Header', 'Next.js Middleware'); return response; }
export const config = { matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)', ], };
|
高级中间件示例
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
| import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) { const { pathname } = request.nextUrl; console.log(`[${new Date().toISOString()}] ${request.method} ${pathname}`); const ip = request.ip ?? '127.0.0.1'; const rateLimitKey = `rate-limit:${ip}`; if (request.method === 'OPTIONS') { const response = new NextResponse(null, { status: 204 }); response.headers.set('Access-Control-Allow-Origin', '*'); response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); return response; } const response = NextResponse.next(); response.headers.set('Access-Control-Allow-Origin', '*'); return response; }
|
7. 数据库集成
Next.js 可以与各种数据库集成,以下是几个常见数据库的示例。
MongoDB 集成
首先安装依赖:
创建数据库连接模块:
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
| import { MongoClient } from 'mongodb';
const uri = process.env.MONGODB_URI!; const options = {};
let client: MongoClient; let clientPromise: Promise<MongoClient>;
if (!process.env.MONGODB_URI) { throw new Error('请设置 MONGODB_URI 环境变量'); }
if (process.env.NODE_ENV === 'development') { let globalWithMongo = global as typeof globalThis & { _mongoClientPromise?: Promise<MongoClient>; }; if (!globalWithMongo._mongoClientPromise) { client = new MongoClient(uri, options); globalWithMongo._mongoClientPromise = client.connect(); } clientPromise = globalWithMongo._mongoClientPromise; } else { client = new MongoClient(uri, options); clientPromise = client.connect(); }
export default clientPromise;
|
在 API 路由中使用 MongoDB:
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
| import { NextResponse } from 'next/server'; import clientPromise from '@/lib/mongodb';
export async function GET() { try { const client = await clientPromise; const db = client.db("myDatabase"); const users = await db.collection("users").find({}).toArray(); return NextResponse.json(users); } catch (error) { return NextResponse.json( { error: '数据库查询失败' }, { status: 500 } ); } }
export async function POST(request: Request) { try { const client = await clientPromise; const db = client.db("myDatabase"); const userData = await request.json(); const result = await db.collection("users").insertOne(userData); return NextResponse.json( { message: '用户创建成功', id: result.insertedId }, { status: 201 } ); } catch (error) { return NextResponse.json( { error: '用户创建失败' }, { status: 500 } ); } }
|
PostgreSQL 集成
安装依赖:
创建数据库连接:
1 2 3 4 5 6 7 8
| import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.POSTGRES_URL, });
export default pool;
|
在 API 路由中使用 PostgreSQL:
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
| import { NextResponse } from 'next/server'; import pool from '@/lib/postgres';
export async function GET() { try { const result = await pool.query('SELECT * FROM products'); return NextResponse.json(result.rows); } catch (error) { return NextResponse.json( { error: '数据库查询失败' }, { status: 500 } ); } }
export async function POST(request: Request) { try { const { name, price } = await request.json(); const result = await pool.query( 'INSERT INTO products(name, price) VALUES($1, $2) RETURNING *', [name, price] ); return NextResponse.json(result.rows[0], { status: 201 }); } catch (error) { return NextResponse.json( { error: '产品创建失败' }, { status: 500 } ); } }
|
8. 错误处理和验证
良好的错误处理和数据验证是构建健壮 API 的关键。
错误处理中间件
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
| import { NextResponse } from 'next/server';
export function errorHandler(error: any) { console.error('API 错误:', error); if (error.name === 'ValidationError') { return NextResponse.json( { error: '数据验证失败', details: error.message }, { status: 400 } ); } if (error.name === 'UnauthorizedError') { return NextResponse.json( { error: '未授权访问' }, { status: 401 } ); } if (error.code === 'P2025') { return NextResponse.json( { error: '请求的资源不存在' }, { status: 404 } ); } return NextResponse.json( { error: '服务器内部错误' }, { status: 500 } ); }
|
数据验证
使用 Zod 进行数据验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { z } from 'zod';
export const userSchema = z.object({ name: z.string().min(1, '姓名不能为空').max(50, '姓名不能超过50个字符'), email: z.string().email('请输入有效的邮箱地址'), age: z.number().min(0).max(150).optional(), });
export const productSchema = z.object({ name: z.string().min(1, '产品名称不能为空').max(100), price: z.number().positive('价格必须大于0'), description: z.string().max(1000).optional(), });
|
在 API 路由中使用验证:
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
| import { NextResponse } from 'next/server'; import { userSchema } from '@/lib/validation'; import { errorHandler } from '@/lib/error-handler';
export async function POST(request: Request) { try { const body = await request.json(); const validatedData = userSchema.parse(body); const newUser = { id: Date.now().toString(), ...validatedData }; return NextResponse.json(newUser, { status: 201 }); } catch (error: any) { if (error.name === 'ZodError') { return NextResponse.json( { error: '数据验证失败', details: error.errors }, { status: 400 } ); } return errorHandler(error); } }
|
自定义错误类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| export class ValidationError extends Error { constructor(message: string) { super(message); this.name = 'ValidationError'; } }
export class UnauthorizedError extends Error { constructor(message: string) { super(message); this.name = 'UnauthorizedError'; } }
export class NotFoundError extends Error { constructor(message: string) { super(message); this.name = 'NotFoundError'; } }
|
总结
通过本教程,你已经学会了:
- Next.js API 路由的基本概念和使用方法
- 如何处理不同的 HTTP 方法
- 动态 API 路由的实现
- 请求和响应的高级处理技巧
- 中间件的使用
- 数据库集成的方法
- 错误处理和数据验证的最佳实践
在下一节教程中,我们将探讨 Next.js 的性能优化、SEO 优化和部署策略等高级话题。