# 使用 Cloudflare Pages 建立 Serverless Next.js API (with D1 database)
[TOC]
## 前言
Cloudflare pages 可以根據 github commit 建立每個 Pull Request 的 staging 網頁
![截圖 2024-01-06 下午4.42.38](https://hackmd.io/_uploads/HyqFf5Udp.png)
有這樣的 staging deployment 可以有利於 PR review 及 debug。
我們也可以用 Cloudflare 提供的 [pages](https://pages.cloudflare.com/)/[workers](https://workers.cloudflare.com/) 建立 [serverless framework](https://www.cloudflare.com/zh-tw/learning/serverless/glossary/serverless-and-cloudflare-workers/) 處理 API,省去架設伺服器的困難及成本。
並用 [D1](https://developers.cloudflare.com/d1/) database 儲存資料,避免 serverless 狀態被清除。
## 文件
- pages: https://developers.cloudflare.com/pages
- d1: https://developers.cloudflare.com/d1
## 部署 Next.js site
> 參考 [Pages: Deploy a Next.js site](https://developers.cloudflare.com/pages/framework-guides/deploy-a-nextjs-site/#use-the-edge-runtime)
1. Create 一個 next.js project
```bash
npm create cloudflare@latest nextjs-pages-example -- --framework=next
```
設定 config
接著進入資料夾
```bash
cd nextjs-pages-example
```
原本應該就有個 default 的 `api/hello`
2. 上傳到 Github
```bash
git remote add origin https://github.com/<GITHUB_USER_NAME>/nextjs-pages-example.git
git branch -M main
git push -u origin main
```
3. 到 Cloudflare **Workers 和 Pages**
**建立應用程式**
![截圖 2024-01-06 下午5.04.31](https://hackmd.io/_uploads/Sk6ov98ua.png)
選擇 **Pages** 的 tab,並選連線至 Git
![截圖 2024-01-06 下午5.04.58](https://hackmd.io/_uploads/rJ_aD5Idp.png)
選擇 project 的 repository
並按 **開始設定**
選擇 **Next.js** 的設定
![截圖 2024-01-06 下午5.06.34](https://hackmd.io/_uploads/SkD7_cI_a.png)
進到 **設定** 中的 **函數**
![截圖 2024-01-06 下午5.10.24](https://hackmd.io/_uploads/rya-YcUOa.png)
選擇**相容性日期**: **2022-11-30** (生產跟預覽)
![截圖 2024-01-06 下午5.11.08](https://hackmd.io/_uploads/SJ5VFcUua.png)
設定相容性旗標: **`nodejs_compat`** (生產跟預覽)
![截圖 2024-01-06 下午5.13.26](https://hackmd.io/_uploads/H176Yc8ua.png)
再部署一次
![截圖 2024-01-06 下午5.13.59](https://hackmd.io/_uploads/B17kc5U_T.png)
4. 可以在新的 url 下呼叫 `api/hello`
e.g. https://c030509d.nextjs-pages-example.pages.dev/api/hello
## 建立 D1 database
> 參考 [D1: Get Started](https://developers.cloudflare.com/d1/get-started/)
1. 登入 Cloudflare
```bash
npx wrangler login
```
2. 新增 database 名稱為 `prod-d1-tutorial`
```bash
npx wrangler d1 create prod-d1-tutorial
```
如果成功會出現 d1 資訊
```toml
[[d1_databases]]
binding = "DB" # available in your Worker on env.DB
database_name = "prod-d1-tutorial"
database_id = "<unique-ID-for-your-database>"
```
3. 在 root 建立一個 `wrangler.toml` 檔案
```toml
[[d1_databases]]
binding = "DB" # available in your Worker on env.DB
database_name = "prod-d1-tutorial"
database_id = "<unique-ID-for-your-database>"
```
4. 在 root 建立一個 `schema.sql` 檔案
```sql
DROP TABLE IF EXISTS Customers;
CREATE TABLE IF NOT EXISTS Customers (CustomerId INTEGER PRIMARY KEY, CompanyName TEXT, ContactName TEXT);
INSERT INTO Customers (CustomerID, CompanyName, ContactName) VALUES (1, 'Alfreds Futterkiste', 'Maria Anders'), (4, 'Around the Horn', 'Thomas Hardy'), (11, 'Bs Beverages', 'Victoria Ashworth'), (13, 'Bs Beverages', 'Random Name');
```
5. 執行此 sql 指令
```bash
npx wrangler d1 execute prod-d1-tutorial --local --file=./schema.sql
```
若帶上 `--local` 的 flag 則表示只在 local 執行,不會變更到 Cloudflare D1 裡的資料
並會在 root 資料夾新增一個 `.wrangler` 資料夾
儲存 D1 的資料
6. 檢查是否儲存成功
```bash
npx wrangler d1 execute prod-d1-tutorial --local --command="SELECT * FROM Customers"
```
會回傳
```bash
🌀 Mapping SQL input into an array of statements
🌀 Executing on local database prod-d1-tutorial (21c8234f-e616-4128-93bc-b65ff78b73f5) from .wrangler/state/v3/d1:
┌────────────┬─────────────────────┬───────────────────┐
│ CustomerId │ CompanyName │ ContactName │
├────────────┼─────────────────────┼───────────────────┤
│ 1 │ Alfreds Futterkiste │ Maria Anders │
├────────────┼─────────────────────┼───────────────────┤
│ 4 │ Around the Horn │ Thomas Hardy │
├────────────┼─────────────────────┼───────────────────┤
│ 11 │ Bs Beverages │ Victoria Ashworth │
├────────────┼─────────────────────┼───────────────────┤
│ 13 │ Bs Beverages │ Random Name │
└────────────┴─────────────────────┴───────────────────┘
```
7. 若要上傳此 data 到 D1 則把 `--local` flag 去掉
## 建立呼叫 D1 的 API
1. 在 root 建立一個 `env.d.ts` 的檔案
> 參考 [Pages: Use bindings in your Next.js application](https://developers.cloudflare.com/pages/framework-guides/deploy-a-nextjs-site/#use-bindings-in-your-nextjs-application)
```ts
declare global {
namespace NodeJS {
interface ProcessEnv {
[key: string]: string | undefined;
DB: D1Database;
}
}
}
export { };
```
2. 建立一個 `src/app/api/db/route.ts` 的檔案
回傳 DB 中的資料
```ts
import type { NextRequest } from 'next/server'
export const runtime = 'edge'
export async function GET(request: NextRequest) {
const { results } = await process.env.DB.prepare(
"SELECT * FROM Customers WHERE CompanyName = ?"
)
.bind("Bs Beverages")
.all();
return new Response(JSON.stringify(results));
}
```
3. 設定 **D1 資料庫繫結** `DB` = `prod-d1-tutorial`(生產和預覽)
![截圖 2024-01-06 下午5.47.38](https://hackmd.io/_uploads/HkuabiL_6.png)
4. 此時 push code 到 github,就能看到新 deploy 的 pages 有 `api/db` 回傳 D1 的資料。 e.g. https://c97ea8ef.nextjs-pages-example.pages.dev/api/db
## 如何在 local 測試 D1 pages
> 參考 [Pages: Interact with your D1 databases locally](https://developers.cloudflare.com/pages/functions/bindings/#interact-with-your-d1-databases-locally)
1. 用在 cloudflare 的設定 build:
```bash
npx @cloudflare/next-on-pages@1
```
:::warning
⚠️ 每次更動 router 都必須執行此指定一次。
也必須重開 server
:::
2. 用 `npx wrangler pages dev` 啟動 server,並設定以下 flags
```bash
npx wrangler pages dev .vercel/output/static \
--compatibility-date 2022-11-30 \
--compatibility-flag nodejs_compat \
--d1 DB=<unique-ID-for-your-database>
```
e.g. 我的 id = `21c8234f-e616-4128-93bc-b65ff78b73f5`
```bash
npx wrangler pages dev .vercel/output/static \
--compatibility-date 2022-11-30 \
--compatibility-flag nodejs_compat \
--d1 DB=21c8234f-e616-4128-93bc-b65ff78b73f5
```
:::info
雖然不是真的跟 Cloudflare的 D1 互動,但是還是要用 cloudflare 上的 db id
:::
## 成果
Github: https://github.com/vivianjeng/nextjs-pages-example
Pages link: https://nextjs-pages-example.pages.dev