# 使用 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