在學習 React 與前端開發時,每當完成一個專案,都希望能夠佈署到公開的環境。如果是單純 HTML + CSS + JavaScript 的專案,用 Github Page 的服務就能搞定,比方說這個貪食蛇的複刻專案。
而如果是用 React 等框架完成的專案,則可以透過 Netlify 輕鬆佈署,無論是 GUI 或 CLI 的操作方式都非常簡單。
但近期我在嘗試佈署海綿寶寶測驗這個專案時,使用了 json-server 當作假的 API 伺服器,結果遇到了一個問題:
該如何把 Json-Server 佈署到 Netlify 上面?
毫無頭緒之下上網查了些資料,意外發現 Netlify 客戶支援論壇有相關的討論:JSON Server Doesn’t Work on Netlify。
簡而言之,官方回覆,開發者無法在 Netlify 上面運行 live server,所以建議改用 Netlify 的 serverless function 服務:
No, you can’t get a live server to work in any case. With Netlify Functions you can get responses as JSON. Each function has its own URL. You can call the function, do some processing on the server and return a JSON response. Try here: https://functions-playground.netlify.app/
這篇文章會來簡單紀錄我是如何透過 Netlify function 服務回傳 JSON 資料,希望能幫助到其他也面臨到類似問題的人。
以前在 Google 經銷商工作時,曾耳聞過 Google Cloud Function 是 function-as-a-service (FaaS) 的服務,但沒有相關的實作經驗。
簡單來說,像這樣的無伺服器 (serverless) 服務,免去了我們準備伺服器的麻煩,也能夠去運行主機端的程式碼。開發者只需要專心建構程式碼即可。
Netlify function 也是類似的服務,它主要提供了以下兩種函式類型:
由於本次專案的資料,僅是 15 道海綿寶寶測驗題的 JSON 檔,用第一個選項就綽綽有餘了。
相關領域的資源可以參考:
首要之務是先去 Netlify 開通帳號,可以直接用 Github 註冊登入。
我的做法是在 Github 建立兩個 repo:
接著在 JSON API 的專案中,安裝 Netlify 的 CLI 工具。非必要,但相信大家都有共識,CLI 用起來效率比較高:
npm install netlify-cli -g
安裝完畢後,輸入 netlify login
進行身分驗證,瀏覽器會自動跳出驗證的視窗。
your-project
└── netlify
└── functions
└── hello-world.js
2 個資料夾, 1 個檔案
我們先在 JSON API 的專案建立上述的資料夾以及檔案,一開始以體驗為主,檔案名稱就取 hello-world.js
吧。現在開啟 hello-world.js
,輸入一些測試內容:
export default async (req, context) => {
return new Response("Hello, world!");
};
👉 函式檔案一定要使用 JavaScript modules 的語法,並且做 default export。如果對於 export 很陌生的話,可以參考我之前的文章和裡面的參考資料: 【React 學習】匯出與匯入元件。
👉 handler
函式要是一個 async
函式
👉 handler
函式要包含以下兩個參數,相信有和 fetch
打過交道的話不會太陌生:
👉 handler
函式回傳一個 HTTP response 物件。
測試很單純,因此沒有用到 req
和 context
兩個參數,若需要更客製化地處理,請參考官方文件。恭喜🎊 這就是我們第一個 serverless 函式!
現在回到終端機,輸入以下指令執行 Netlify function 的服務:
netlify functions:serve
當你看到類似以下的回覆,就代表函式成功執行囉,接著到 http://localhost:9999/.netlify/functions/hello-world 應該就能看到 Hello, world! 被列印在畫面上。
我們把 questions.json
檔案加入到資料夾結構當中:
這邊 JavaScript 檔案依循 Netlify 官方的建議,修改成了 .mjs
,以便使用現代的 ES module 語法。
Naming your function with the .mjs
extension lets you use the modern ES modules syntax. To learn more about the different module formats, refer to runtime.
函式程式碼的部分,自然得用 import
的方式來匯入 questions.json
:
import questions from "../../questions.json";
export default async ({ req, context }) => {
const json = JSON.stringify(questions); // 把 questions 物件轉為 JSON 格式字串
const options = {
status: 200, // 沒什麼,我只是單純喜歡 200
headers: {
"Content-Type": "application/json; charset=utf-8", // 確保不會發生 encoding 問題
"Access-Control-Allow-Origin": "*", // API 開放給所有人,因為大家都愛海綿寶寶
},
};
return new Response(json, options); // 兩個參數,第一個為 body,第二個為 header options
};
更新完成後,回到 http://localhost:9999/.netlify/functions/api 檢查一下,阿呀,成功拿到 JSON 資料了:
測試過沒問題之後,接下來就是把 api 專案 push 到 Github 上,然後佈署至 Netlify 了。這部分可以參考官方教學。做過一次就會驚覺,現在實在有太多方便的工具了......
佈署完成後,Netlify 會產生一組隨機名稱的 URL,像這樣:https://helpful-kleicha-41f04d.netlify.app/.netlify/functions/api。
當然如果你有自己的網域,可以在專案建立 netlify.toml
檔案,建立 [[redirect]]
的規則,但筆者是個窮酸小子,沒有購買域名,所以就乖乖維持這個奇奇怪怪的 URL 了。
這部分不多加贅述 React 專案的程式碼了,簡單說一下,我在 App
根元件使用 useEffect
來處理 fetch
的副作用。因為只希望 effect 函式在 App
元件掛載 (mount) 時執行,所以依賴陣列 (dependency array) 置入空陣列。
至於 dispatch
是因為我在此專案特意練習 useReducer
進行 state 管理。
// Handle side effect: fetch questions data
useEffect(() => {
const fetchQuestions = async () => {
try {
const res = await fetch(
"https://helpful-kleicha-41f04d.netlify.app/.netlify/functions/api" 👈
);
const data = await res.json();
dispatch({ type: "dataReceived", payload: data });
} catch (err) {
dispatch({ type: "dataFailed" });
console.log(err);
}
};
fetchQuestions();
}, []);