Next.js 教程系列(六)API Routes 与全栈开发基础
前言
大家好,我是鲫小鱼。是一名不写前端代码
的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!
第六章:API Routes 与全栈开发基础
教程简介
本章将带你深入理解 Next.js 的 API Routes 功能,掌握如何在同一个项目中实现前后端一体化开发。你将学会如何编写高质量的 API 接口,处理数据请求、鉴权、错误处理、性能优化等企业级场景。我们还会结合移动端适配、接口安全、全栈开发最佳实践,帮助你构建健壮、可维护的全栈应用。
理论讲解
1.1 API Routes 概述与架构演进
-
API Routes 是 Next.js 提供的后端接口开发能力,允许你在
/pages/api
或/app/api
目录下直接编写 Node.js 风格的接口。 - 支持 RESTful、GraphQL、Webhooks、BFF(Backend For Frontend)等多种接口风格。
- 与前端页面共享同一项目、同一依赖、同一部署流程,极大提升开发效率。
- 适合中小型全栈项目、BFF 模式、原型开发、企业级微服务网关等场景。
- 推荐分层架构:API 层(路由)、服务层(业务逻辑)、数据访问层(DAO),便于维护和扩展。
- 支持接口版本管理(如
/api/v1/
),便于平滑升级。
1.2 API Routes 的基本用法与进阶
- 在
/pages/api
目录下创建任意.ts
/.js
文件,即可自动成为一个 API 路由。 - 每个文件导出一个默认函数,接收
req
(请求对象)和res
(响应对象)。 - 支持 GET、POST、PUT、DELETE 等 HTTP 方法。
- 支持中间件、Cookie、Session、文件上传、数据库操作、Edge API Routes(边缘计算)、Server Actions(App Router)。
- 推荐按业务模块拆分目录,如
/api/user/
、/api/order/
、/api/admin/
。
1.3 API Routes 与全栈开发
- 前端页面通过
fetch
、axios
、SWR
、React Query
等方式请求本地 API。 - API 层可集成数据库(如 Prisma、TypeORM)、第三方服务(如 Stripe、微信支付)、缓存(如 Redis)、消息队列等。
- 支持 SSR/SSG/ISR 等多种渲染模式下的数据获取。
- 可作为微服务网关,聚合/转发后端服务。
1.4 企业级安全与权限控制
- 鉴权:结合 JWT、Session、OAuth2、API Key、第三方登录(如 GitHub、微信)实现用户身份校验。
- 接口限流:防止恶意刷接口,可用 Redis、内存、第三方服务实现。
- CSRF/XSS 防护:合理设置 CORS、校验 Referer、过滤输入。
- 敏感信息保护:环境变量、加密存储、日志脱敏、接口审计。
- 接口签名与幂等性:对关键接口请求参数签名校验,防止篡改和重复提交。
- 多租户支持:通过租户ID、Token、Header 实现多租户隔离。
1.5 性能优化与高可用
- 缓存:HTTP 缓存头、Redis、CDN、接口预热、缓存穿透防护。
- 批量/合并请求:减少接口数量,提升移动端体验。
- 异步处理:如队列、定时任务,避免接口阻塞主线程。
- 边缘计算:利用 Vercel Edge Functions 实现低延迟接口。
- 接口降级与容灾:主服务异常时自动降级到备用方案。
- 日志与监控:接口需有日志、埋点、告警,便于排查问题。
1.6 Mock、自动化测试与文档
- Mock Service Worker(MSW):前端可独立开发调试,后端未完成时模拟接口。
- Jest/Supertest:为 API Routes 编写单元测试、集成测试。
- 契约测试:保证前后端数据结构一致。
- OpenAPI/Swagger:自动生成接口文档,支持在线调试。
- 类型注释:结合 TypeScript 类型,提升文档准确性。
- CI 持续集成:自动化测试覆盖,保障接口质量。
1.7 多端适配与国际化
- 响应式接口:根据 UA/参数返回不同数据结构,适配 Web/移动/小程序/桌面端。
- 国际化:接口支持多语言返回,结合 i18n、next-intl。
- 图片/多媒体优化:返回合适尺寸的图片链接,支持 WebP、AVIF。
- 网络异常处理:接口需返回明确错误码和提示,前端可友好降级。
1.8 错误处理与监控
- 统一错误码与响应格式:便于前端处理和埋点。
- Sentry/LogRocket:接入错误监控,自动上报异常。
- 慢接口告警:接口超时自动告警,便于性能优化。
- 日志采集:接口请求日志、用户行为日志、异常日志。
代码示例
2.1 创建基础 API 路由
// pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(200).json({ message: 'Hello, Next.js API!' });
}
2.2 支持多种 HTTP 方法
// pages/api/user.ts
import type { NextApiRequest, NextApiResponse } from 'next';
let users = [
{ id: 1, name: '小明' },
{ id: 2, name: '小红' },
];
export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'GET') {
res.status(200).json(users);
} else if (req.method === 'POST') {
const { name } = req.body;
const newUser = { id: Date.now(), name };
users.push(newUser);
res.status(201).json(newUser);
} else {
res.status(405).json({ error: 'Method Not Allowed' });
}
}
2.3 接口鉴权与 JWT 校验
// pages/api/profile.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import jwt from 'jsonwebtoken';
const SECRET = process.env.JWT_SECRET || 'demo_secret';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: '未登录' });
}
try {
const user = jwt.verify(token, SECRET);
res.status(200).json({ user });
} catch {
res.status(401).json({ error: 'Token 无效' });
}
}
2.4 接口限流中间件
// lib/rateLimit.ts
const rateLimitMap = new Map<string, { count: number; last: number }>();
export function rateLimit(ip: string, limit = 10, windowMs = 60_000) {
const now = Date.now();
const entry = rateLimitMap.get(ip) || { count: 0, last: now };
if (now - entry.last > windowMs) {
rateLimitMap.set(ip, { count: 1, last: now });
return false;
}
if (entry.count >= limit) return true;
rateLimitMap.set(ip, { count: entry.count + 1, last: entry.last });
return false;
}
// pages/api/secure-data.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { rateLimit } from '@/lib/rateLimit';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const ip = req.headers['x-forwarded-for']?.toString() || req.socket.remoteAddress || '';
if (rateLimit(ip, 5, 60_000)) {
return res.status(429).json({ error: '请求过于频繁,请稍后再试' });
}
res.status(200).json({ data: '安全数据' });
}
2.5 文件上传与表单处理
// pages/api/upload.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import formidable from 'formidable';
import fs from 'fs';
export const config = {
api: {
bodyParser: false,
},
};
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const form = new formidable.IncomingForm();
form.parse(req, (err, fields, files) => {
if (err) return res.status(500).json({ error: '上传失败' });
// 假设保存到本地
const file = files.file as formidable.File;
fs.renameSync(file.filepath, `./public/uploads/${file.originalFilename}`);
res.status(200).json({ url: `/uploads/${file.originalFilename}` });
});
}
2.6 数据库操作(以 Prisma 为例)
// pages/api/products.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'GET') {
const products = await prisma.product.findMany();
res.status(200).json(products);
} else if (req.method === 'POST') {
const { name, price } = req.body;
const product = await prisma.product.create({ data: { name, price } });
res.status(201).json(product);
} else {
res.status(405).json({ error: 'Method Not Allowed' });
}
}
2.7 移动端分页与懒加载接口
// pages/api/feed.ts
import type { NextApiRequest, NextApiResponse } from 'next';
const allItems = Array.from({ length: 100 }).map((_, i) => ({ id: i + 1, title: `Item ${i + 1}` }));
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const page = parseInt(req.query.page as string) || 1;
const limit = parseInt(req.query.limit as string) || 10;
const start = (page - 1) * limit;
const end = start + limit;
res.status(200).json({
items: allItems.slice(start, end),
total: allItems.length,
page,
limit,
});
}
2.8 错误处理与统一响应格式
// lib/response.ts
export function success(data: any) {
return { code: 0, data };
}
export function error(message: string, code = 1) {
return { code, message };
}
// pages/api/unified.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { success, error } from '@/lib/response';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
try {
// ...业务逻辑
res.status(200).json(success({ msg: 'ok' }));
} catch (e) {
res.status(500).json(error('服务器异常'));
}
}
2.9 GraphQL API 基础
// pages/api/graphql.ts
import { ApolloServer, gql } from 'apollo-server-micro';
const typeDefs = gql`
type User { id: ID! name: String! }
type Query { user(id: ID!): User }
`;
const resolvers = {
Query: {
user: (_: any, { id }: { id: string }) => ({ id, name: '小明' }),
},
};
const apolloServer = new ApolloServer({ typeDefs, resolvers });
export const config = { api: { bodyParser: false } };
export default apolloServer.createHandler({ path: '/api/graphql' });
2.10 API Key 鉴权与签名校验
// pages/api/secure.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import crypto from 'crypto';
const API_KEY = process.env.API_KEY || 'demo_key';
const SECRET = process.env.API_SECRET || 'demo_secret';
function verifySignature(req: NextApiRequest) {
const signature = req.headers['x-signature'] as string;
const timestamp = req.headers['x-timestamp'] as string;
const raw = `${timestamp}${API_KEY}${SECRET}`;
const expected = crypto.createHash('sha256').update(raw).digest('hex');
return signature === expected;
}
export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.headers['x-api-key'] !== API_KEY || !verifySignature(req)) {
return res.status(401).json({ error: '无效签名' });
}
res.status(200).json({ data: '安全数据' });
}
2.11 幂等性与防重复提交
// lib/idempotency.ts
const idempotencyMap = new Map<string, number>();
export function isDuplicate(id: string) {
if (idempotencyMap.has(id)) return true;
idempotencyMap.set(id, Date.now());
setTimeout(() => idempotencyMap.delete(id), 60_000);
return false;
}
// pages/api/order.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { isDuplicate } from '@/lib/idempotency';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const id = req.headers['x-idempotency-key'] as string;
if (!id || isDuplicate(id)) {
return res.status(409).json({ error: '重复提交' });
}
// ...创建订单逻辑
res.status(201).json({ message: '订单创建成功' });
}
2.12 Mock 与自动化测试
// __tests__/api/user.test.ts
import handler from '../../pages/api/user';
import { createMocks } from 'node-mocks-http';
test('GET /api/user', async () => {
const { req, res } = createMocks({ method: 'GET' });
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(JSON.parse(res._getData())).toEqual([
{ id: 1, name: '小明' },
{ id: 2, name: '小红' },
]);
});
2.13 OpenAPI/Swagger 自动生成文档
// scripts/generate-openapi.js
// 使用 swagger-jsdoc 自动生成 openapi.json
const swaggerJSDoc = require('swagger-jsdoc');
const options = { ... };
const openapiSpec = swaggerJSDoc(options);
require('fs').writeFileSync('openapi.json', JSON.stringify(openapiSpec, null, 2));
2.14 多语言接口
// pages/api/i18n.ts
import type { NextApiRequest, NextApiResponse } from 'next';
const messages = {
zh: { hello: '你好' },
en: { hello: 'Hello' },
};
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const lang = req.query.lang || 'zh';
res.status(200).json({ message: messages[lang as string]?.hello || messages.zh.hello });
}
2.15 Sentry 错误监控集成
// pages/api/_middleware.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({ dsn: process.env.SENTRY_DSN });
export default function middleware(req, ev) {
try {
return NextResponse.next();
} catch (e) {
Sentry.captureException(e);
throw e;
}
}
实战项目
3.1 构建全栈商品管理系统
目标:实现一个支持商品增删改查、图片上传、用户鉴权、移动端适配的全栈商品管理系统。
主要功能:
- 商品列表页:支持分页、搜索、移动端自适应。
- 商品详情页:展示商品图片、价格、描述。
- 后台管理页:支持商品的新增、编辑、删除。
- 用户登录鉴权:JWT 登录、接口权限控制。
- 图片上传:支持多图上传、进度显示。
- API 接口:全部基于 Next.js API Routes 实现。
- 错误处理与统一响应格式。
- 性能优化:接口缓存、按需加载。
- 日志与监控:接口请求日志、错误告警。
- 国际化与移动端适配。
关键代码片段:
// pages/products/index.tsx
import useSWR from 'swr';
import { useState } from 'react';
const fetcher = (url: string) => fetch(url).then(res => res.json());
export default function ProductsPage() {
const [page, setPage] = useState(1);
const { data, error } = useSWR(`/api/feed?page=${page}&limit=10`, fetcher);
if (error) return <div>加载失败</div>;
if (!data) return <div>加载中...</div>;
return (
<div>
<ul>
{data.items.map((item: any) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
<button disabled={page === 1} onClick={() => setPage(page - 1)}>上一页</button>
<button disabled={data.items.length < 10} onClick={() => setPage(page + 1)}>下一页</button>
</div>
);
}
// pages/admin/products.tsx
import { useState } from 'react';
export default function AdminProducts() {
const [name, setName] = useState('');
const [price, setPrice] = useState('');
const [msg, setMsg] = useState('');
const handleAdd = async () => {
const res = await fetch('/api/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, price: Number(price) }),
});
const data = await res.json();
setMsg(data.code === 0 ? '添加成功' : data.message);
};
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} placeholder="商品名" />
<input value={price} onChange={e => setPrice(e.target.value)} placeholder="价格" type="number" />
<button onClick={handleAdd}>添加商品</button>
<div>{msg}</div>
</div>
);
}
3.2 多租户商城 API 设计
- 支持多租户(如不同商家/品牌)数据隔离。
- 每个租户有独立的商品、订单、用户数据。
- 通过 Header、Token、子域名等区分租户。
- 接口返回结构需兼容多端(Web/移动/小程序)。
- 支持租户级别的权限、限流、定制化配置。
3.3 订单系统与支付回调
- 订单创建接口需防止重复提交(幂等性)。
- 支付回调接口需校验签名、防止伪造。
- 订单状态流转需有日志、告警。
- 支持异步通知、消息推送。
3.4 实时消息与 WebSocket
- 使用 nextjs-websocket、socket.io 实现实时订单状态推送。
- 支持移动端、桌面端多端同步。
- 接口需有鉴权、限流、断线重连机制。
3.5 复杂参数校验与批量导入导出
// lib/validate.ts
import * as z from 'zod';
export const productSchema = z.object({
name: z.string().min(2),
price: z.number().positive(),
});
// pages/api/products.ts
import { productSchema } from '@/lib/validate';
export default async function handler(req, res) {
if (req.method === 'POST') {
const parse = productSchema.safeParse(req.body);
if (!parse.success) {
return res.status(400).json({ error: '参数校验失败', details: parse.error.errors });
}
// ...创建逻辑
}
}
- 支持 Excel/CSV 批量导入商品,接口需校验格式、异步处理。
- 导出接口支持大数据量分片导出,防止超时。
3.6 长轮询与 WebSocket
- 长轮询接口需有超时、重试机制。
- WebSocket 需支持断线重连、心跳包。
常见问题与最佳实践
- API 目录混乱:建议按业务模块拆分,统一命名规范。
- 环境变量泄漏:敏感信息只放 .env,不要暴露到前端。
- SSR/CSR 下接口复用:建议统一用 API Routes,避免重复实现。
- 接口 Mock 不一致:Mock 数据与真实接口保持同步,自动化测试覆盖。
- 接口文档滞后:用 OpenAPI/Swagger 自动生成,CI 校验。
- 多端适配遗漏:接口返回结构需兼容 Web/移动/小程序。
- 接口安全被忽视:务必加鉴权、限流、签名、日志。
- 性能瓶颈:接口加缓存、CDN、边缘计算,定期分析慢接口。
- 错误处理不统一:统一响应格式,前端友好提示。
片尾
最后感谢阅读!欢迎关注我,微信公众号:
《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!!