Next.js 是一个全端框架,除了提供 SSR 与 SSG 的功能之外,还能够建立 API 提供前端页面使用。
你可以使用 API routes 建立 REST API,如果有 graphql 的需求,也可以用来建立 graphql API。官方文件提供了许多的范例,让我们可以快用用一些模板建立服务:
在这篇文章中将以 REST API 作为范例,体验 Next.js 的 API routes。
API routes 的概念与前端页面一样,都是使用 file-based routing,所有的 API 都会放在 pages/api
这个资料夹底下,例如 pages/api/products
即是对应 api/products
这个 endpoint。
在这这资料夹中的所有档案将不会被当作页面的 url,因此在 pages/api
中的档案都不会被打包近客户端的 bundle 中,如果使用者在浏览器的网址列输入 api/products
,即是跟服务器端请求 API,而并非是一个页面。
举一个例子,在 pages/api/products.ts
中建立一个 API,回传 json 格式的资料,并且包含 200 的 HTTP 状态码:
import { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(200).json({ products: [{ name: "item" }] });
}
从以上范例中可以看到,API routes 是一个 export default
的 function,可以使用 res.status
指定 HTTP 的状态码,并使用 res.json
回传资料至客户端。
接着,在浏览器中输入 api/products
就会看到 API 回应的资料,打开 Chrome 的 Network 也可以看到 HTTP 状态码为 200。
我们从 req
与 res
的型别定义中可以看到:
req
的型别 NextApiRequest
继承了 http.IncomingMessage
,是一个 IncomingMessage
的 instance。res
的型别 NextApiResponse
继承了 http.ServerResponse
,是一个 ServerResponse
的 instance。但是两者与原生 node.js 的写法不太一样,像是 Next.js 封装了 req
,让我们可以用 req.query
取得 url 上的参数,还可以使用 req.body
取得 body 中的内容。此外,Next.js 也封装了 res
这个物件,让我们能够用 chain function 的方式使用 res
,如上方的范例。
如果想要处理不同的 HTTP method,可以透过 req.method
这个属性判断:
import { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "GET") {
res.status(200).json({ products: [{ name: "item" }] });
} else if (req.method === "POST") {
// 建立产品资料
} else if (req.method === "DELETE") {
// 删除产品资料
}
}
既然 API routes 是基於 file-based routing,所以也能够处理动态的资源,例如在前面章节实作的「产品详细页面」,其对应的页面是 pages/products/[id].tsx
,在这个页面中的 id
是动态的,会根据使用者浏览的产品对应至不同的值,因此在详细页面中也会需要呼叫不同的 API endpoint,像是 api/products/[id]
。
要建立 dynamic API routes 其概念与「页面」一样, api/products/[id]
即是对应 api/products/[id].ts
:
import { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const { id } = req.query;
res.status(200).json({ productId: id });
}
接着,在浏览器中输入 api/products/123
就会看到 API 回应的资料,打开 Chrome 的 Network 也可以看到 HTTP 状态码为 200。
以部落格的例子来说,一篇贴文的 url 以「年月日」来设计,所以 url 可能这个样子 /posts/<year>/<month>/<day>
, 如果 API 要像下方这样子建立很多个资料夹,工程师们大概会觉得很麻烦:
pages/
└── api/
└──posts/
└── [year]/
└── [month]/
└── [day].ts
为了解决这个情况,所以 API routes 也有 catch all routes 的实作,以上方的例子来说,只要定义 /pages/api/[...date].ts
就可以匹配「年月日」的参数,而且甚至可以无限地加上新的参数,例如颗粒度想要细到小时、分钟、秒,都是可以的:
pages/
└── api/
└──posts/
└── [...date].ts
[...date]
的资料最後会以阵列被储存在 router.query
中:
import { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const { date } = req.query;
res.status(200).json({ date });
}
以 /api/posts/2021/12/31
这个例子来说, date
会是以下这个模样:
{
date: [2021, 12, 31];
}
如果一个 API 的资料夹同时包括 [id].ts
与 [...date].ts
两种 pattern 的话,当呼叫 /api/posts/abc
会先匹配 /api/posts/[id].ts
这个 API routes,而 2 个以上的参数才会匹配 [...date].ts
。
这是 dynamic routes 的最後一个 pattern,前面提到的 [id].ts
与 [...date].ts
都不能用来匹配 /api/products
这种 API endpoint,但是 optional catch all API routes 可以用来匹配所有的 API endpoint,它是以两个钟括号作为定义,例如 [[...slug]].ts
。
所以,如果想要用一个 API routes 定义部落格中所有的贴文 API,则可以定义 /api/posts/[[...slug]].ts
,这一个 API 则可以同时匹配以下几种 API endpoints:
/api/posts
/api/posts/123
/api/posts/2021/12/31
而这几个 API endpoints 的 req.query
会是以下这个样子:
{}
{ slug: [123] }
{ slug: [2021, 12, 31] }
Next.js 为了让 API 定义更弹性一点,提供了各种不同的 API routes 的定义模式,包括以下几种:
/api/posts.ts
/api/posts/[id].ts
/api/posts/[...date].ts
/api/posts/[[...slug]].ts
它们彼此之前的关系是由上到下,後面的 API routes 不会盖掉前面,举例来说如果同时在一个 API routes 的资料夹有四种不同的模式:
/api/posts/about.ts
匹配 /api/posts/about
/api/posts/[id].ts
匹配 /api/posts/123
/api/posts/[...date].ts
匹配 /api/posts/2021/12/31
/api/posts/[[...slug]].ts
不会匹配任何的 endpoints所以用这种方式思考一个特殊的情况,当一个 API routes 的 API routes 只有 [id].ts
跟 [[...slug]].ts
,但是没有 index.ts
,直觉的思考「是不是 [[...slug]].ts
会匹配 /api/posts
这种 endpoint」,但因为有 [id].ts
存在於 API routes 的资料夹中, /api/posts
将会回传 HTTP 404。
想要让 [[...slug]].ts
可以匹配 /api/posts
,则要删除 [id].ts
这个 API routes,让 [[...slug]].ts
做所有的事情。
现在我们了解了几种不同定义 API routes 的模式後,来尝试设计「产品列表页面」与「产品详细页面」中需要的 API,已知有两个 API endpoints :
/api/products
:回传产品列表/api/products/[id]
:回传一个产品的详细资讯所以,我们可以统整出几种不同的定义方式:
/api/products.ts
/api/products/[id].ts
/api/products/index.ts
/api/products/[id].ts
/api/products/[[...slug]].ts
方法一与方法二的 [id].ts
可以用 [...slug].ts
或 [[...slug].ts
取代,但是如果使用[[...slug]].ts
则没有意义,因为 /api/products
已经由 index.ts
定义了,所以不如使用 [...slug].ts
避免造成 API routes 混乱。
今天我们了解了如何定义 API routes,可以藉由 req.method
判断 API 的 HTTP method,用以切分不同的实作,想要获得 endpoint 上的资讯,则可以透过 req.query
取得。当我们要回应 API 请求时,可以使用 res.status
定义 HTTP 状态码,并透过 res.json
回传 JSON 格式的资料。
而 API routes 有四种模式匹配各种路由,基本上是 file-based routing 的概念,只是套用在 API 身上。而每一种模式都有其先後顺序,在实作时要注意,否则可能会造成 API routes 看起来很混乱,让後续维护 API 时感到困扰。
>>: @Day27 | C# WixToolset + WPF 帅到不行的安装包 [额外的DLL引用]
Firebase提供现成的email/password验证功能供我们使用, thunkable提供s...
上一篇介绍了Bangla Numbers,由於数字太大,所以选择使用了BigInteger,但各位也...
如果 今天想要用 WixToolset 去做自订的页面了话 (ex:我们要设定系统要修改HelloW...
前言 有个Snapshot的功能,可以看当下的商品状况,让我们看看这个功能可以做啥吧! 参考网站:S...
我们继续熟悉资料库连接的操作吧! 这次是沿用漫画爬虫的程序码,但我目前只要两笔资料:漫画编号、漫画名...