实现服务端逻辑
第一篇笔记我们已经介绍过,NextJS是一个全栈Web开发框架,前面我们介绍的内容大多是和前端开发相关的内容,这里我们继续学习如何用NextJS开发服务端功能。当然,NextJS的服务端在设计上相比一些成熟的服务端框架还是稍显逊色,实际开发中也经常将服务端功能交给其它语言或框架来实现,但总的来说用NextJS实现一些简单的逻辑也完全够用了,这里我们学习一下。
使用Route Handlers
在NextJS13的App Router下,“页面”和“路由处理器”是两个同等的概念。前者我们之前已经大量使用过,它由一组React组件构成,入口文件是page.jsx
,它最终会渲染成页面交给浏览器;而后者则对应一个服务端接口,约定文件名为route.js
。
路由处理器和页面都是基于App Router文件路由系统的,它们遵循完全相同的路由构建规则,但要注意一条路由下不能同时存在page.jsx
和route.js
。一个最佳实践是让路由处理器的路由都以api
开头,也就是所有路由处理器都放在app/api
文件夹下。下面是一个简单的例子。
|_app
|_api
|_dashboard
|_route.js
route.js
export const GET = async () => {
return Response.json({
code: 0,
message: "success",
data: null,
});
};
通过目录结构我们可以得知,上面定义了一条响应/api/dashboard
的路由,在route.js
中,我们导出了一个名为GET
的函数,函数名对应支持的HTTP方法,函数体内我们返回了一段JSON数据。这里我们仅编写了对应GET方法的处理函数,如果试图用其它HTTP方法请求该路由,NextJS会自动返回405(Method Not Allowed)错误。
另外要注意的一点是NextJS中GET请求的返回值默认是缓存的,但在以下条件下不会缓存数据:
- 在GET函数中通过Request对象读取数据
- 使用非GET请求
- 使用
cookies()
、headers()
等函数读取了请求参数 - 主动配置了
Segment Config
指定该路由不缓存数据
读取请求数据
读取请求参数
对于请求GET参数,我们可以直接用request.nextUrl.searchParams
属性来读取,下面是一个例子。
export const GET = async (request) => {
// 读取请求URL参数
const searchParams = request.nextUrl.searchParams;
return Response.json({
code: 0,
message: "success",
data: searchParams,
});
};
对于动态路由,我们可以使用处理函数的第二个参数接收,在params
属性中访问对应的动态路由参数名即可。下面是一个例子,我这里定义的路由是/api/dashboard/[...slug]
。
export const GET = async (request, { params }) => {
// 读取请求URL参数
const slugs = params.slug;
return Response.json({
code: 0,
message: "success",
data: slugs,
});
};
读取JSON请求
对于JSON请求,我们可以使用request.json()
来读取,注意该操作是异步的,因此需要加上await
关键字。
export const POST = async (request) => {
// 读取请求JSON
const reqJson = await request.json();
return Response.json({
code: 0,
message: "success",
data: reqJson,
});
};
上传文件
文件上传通常要使用FormData格式的表单请求。下面例子实现了一个简单的文件上传功能,代码中我们将上传的文件一次性读入内存,然后写入文件,因此仅适用于小文件。
import path from "path";
import { writeFile } from "fs/promises";
export const POST = async (request) => {
// 读取formData请求表单
const formData = await request.formData();
const file = formData.get("file");
if (file) {
// 读取文件全部数据
const bytes = await file.arrayBuffer();
// 转换成Buffer对象
const buffer = Buffer.from(bytes);
// 写入文件数据
await writeFile(path.resolve("D:/test.txt"), buffer);
}
return Response.json({
code: 0,
message: "success",
data: null,
});
};
返回响应
返回JSON数据
返回JSON数据非常简单,我们直接调用Response.json()
函数即可。
export const GET = async () => {
return Response.json({
code: 0,
message: "success",
data: null,
});
};
返回文件流
返回文件流稍微有点复杂,这需要调用NodeJS相关的API来实现。下面例子代码我们返回了一个文件流。
import fs from "fs";
import path from "path";
export const GET = async (request) => {
const filePath = path.resolve("D:/flag");
const readStream = fs.createReadStream(filePath);
return new Response(readStream, {
headers: {
"content-type": "application/octet-stream"
},
});
};
返回重定向
下面例子实现了在服务端返回重定向。
import { redirect } from "next/navigation";
export const GET = async () => {
return redirect("/dashboard");
};
读写Cookie
读写Cookie需要使用cookies
函数,下面是一个例子。
import { cookies } from "next/headers";
export const GET = async () => {
const cookie = cookies();
// 读取Cookie,返回结构类似 { name: 'user', value: 'test', path: '/' }
const user = cookie.get("user");
console.log(user);
// 设置Cookie
cookie.set("user", "test");
return Response.json({
code: 0,
message: "success",
data: null,
});
};
读写Header
写入Header前面我们已经介绍了,Header需要在Response中写入,例子如下。
return new Response(readStream, {
headers: {
"content-type": "text/plain"
},
});
读取Header可以使用headers()
函数实现。
import { headers } from "next/headers";
export const GET = async () => {
const header = headers();
// 读取Header
const ua = header.get("User-Agent");
console.log(ua);
return Response.json({
code: 0,
message: "success",
data: null,
});
};