初探 Strapi Headless CMS
這篇是某個晚上試玩 Strapi 這套 headless CMS 的心得,主要是談 Strapi 和 headless CMS 帶來的變革,不太會談到具體的操作過程。
先談談 headless CMS。
Headless CMS
Headless CMS 是前後端分離概念下的產物,headless CMS 可以簡單的理解為剝去前端的 CMS,headless CMS 以 API 的方式(通常是 RESTful API 或 GraphQL) 供應前端內容,前端(通常是 Aurelia、Svelte、Vue、React、Angular)也透過 API 與 headless CMS 溝通,取得內容呈現,或發送內容回 headless CMS。
在上面的前後端分離的架構下,headless CMS 必須具備幾項特性:
- 管理內容的能力,包括內容的欄位、資料型態、欄位關聯性、以及內容本身,以開發的角度講,就是 model 的制定與管理。另外一種內容是媒體管理,圖片、音檔、影片、PDF 等的媒體資產管理。
- 管理資料庫的能力,上面的內容都必須對應到資料庫,以開發的角度講,就是 ORM。
- 管理 API 的能力,上面的內容(model)除了向下對應到資料庫外,向外也要有對應的 API,並且 model、table、API 的連動是自動化的。
- 除了主要的內容外,還必須有權限、身份認證等系統必備的 API。
- 上面的每個特性都是有一個後台界面(Admin Panel)可以讓一般人操作,而不是只能透過程式碼的方式操作。
從上面幾點可以看出 headless CMS 相較於典型的 MCV web 框架(如 Masonite、Laravel),多了幾項特性:
- Model 是可以由用戶在 Admin Panel 自行定義的,不用由開發人員施工。
- Controller 是自動化建構的,只要在 Admin Panel 定義好 model,API 就會自動產生,不用開發人員施工。
在這樣的特性下,配合大前端時代的降臨,大部分的業務邏輯都往前端實做,開發人員的精力完全可以投注在前端工程上,headless CMS 的角色就專注於當個稱職的網站後端或應用後端,是不是很棒?
Strapi
Strapi 是個開源的 headless CMS 系統,底層則是 Node.js 的 web 框架 Koa。
依照 Strapi 的文件把範例建起來之後,在 Strapi Admin Panel 內建了一個 Restaurant 的 model(Strapi 稱為 Content Type):
Strapi 會自動幫我們產生 API 與文件:
而在專案目錄內,Strapi 會自動幫我們配置出 Restaurant 的路由、model 和 API:
my-project/┣ api/ ┃ ┗ restaurant/ ┃ ┣ config/ ┃ ┃ ┗ routes.json ┃ ┣ controllers/ ┃ ┃ ┗ restaurant.js ┃ ┣ documentation/ ┃ ┃ ┗ 1.0.0/ ┃ ┃ ┣ overrides/ ┃ ┃ ┗ restaurant.json ┃ ┣ models/ ┃ ┃ ┣ restaurant.js ┃ ┃ ┗ restaurant.settings.json ┃ ┗ services/ ┃ ┗ restaurant.js ┣ config/ ┃ ┣ functions/ ┃ ┃ ┣ responses/ ┃ ┃ ┃ ┗ 404.js ┃ ┃ ┣ bootstrap.js ┃ ┃ ┗ cron.js ┃ ┣ database.js ┃ ┗ server.js ┣ extensions/ ┃ ┣ documentation/ ┃ ┣ email/ ┃ ┣ upload/ ┃ ┗ users-permissions/ ┗ public/ ┣ uploads/ ┗ robots.txt
可以看到,如果有需要的話,可以再對 controller、model、service 做開發,下面分別看看這些原始碼的內容與架構。
Routing
{ "routes": [ { "method": "GET", "path": "/restaurants", "handler": "restaurant.find", "config": { "policies": [] } }, { "method": "GET", "path": "/restaurants/count", "handler": "restaurant.count", "config": { "policies": [] } }, { "method": "GET", "path": "/restaurants/:id", "handler": "restaurant.findOne", "config": { "policies": [] } }, { "method": "POST", "path": "/restaurants", "handler": "restaurant.create", "config": { "policies": [] } }, { "method": "PUT", "path": "/restaurants/:id", "handler": "restaurant.update", "config": { "policies": [] } }, { "method": "DELETE", "path": "/restaurants/:id", "handler": "restaurant.delete", "config": { "policies": [] } } ] }
Model
欄位定義在 api/models/restaurant.settings.json:
{ "kind": "collectionType", "collectionName": "restaurants", "info": { "name": "restaurant", "description": "" }, "options": { "increments": true, "timestamps": true, "draftAndPublish": true }, "attributes": { "name": { "type": "string", "required": true, "unique": true }, "description": { "type": "richtext" }, "BGM": { "collection": "file", "via": "related", "allowedTypes": [ "images", "files", "videos" ], "plugin": "upload", "required": false } } }
在 Admin Panel 定義的 model(Content Type)以及欄位都會有相對的 JSON 定義檔產生,這樣的好處是可以讓欄位定義檔本身也被 Git 管理,這也才有辦法讓其他的程式邏輯(如 controller)和 model 一同接受版控的管理。
另外一個是 model 的程式邏輯,在 api/restaurant/models/restaurant.js:
'use strict'; /** * Read the documentation (https://strapi.io/documentation/developer-docs/latest/concepts/models.html#lifecycle-hooks) * to customize this model */ module.exports = {};
內容相當簡單,只有一段引導我們去看 model 開發文件的註解。
後面的 controller、service 也都是類似的內容。
Controller
檔案在 api/controllers/restaurant.js:
'use strict'; /** * Read the documentation (https://strapi.io/documentation/developer-docs/latest/concepts/controllers.html#core-controllers) * to customize this controller */ module.exports = {};
Service
檔案在 api/services/restaurant.js:
'use strict'; /** * Read the documentation (https://strapi.io/documentation/developer-docs/latest/concepts/services.html#core-services) * to customize this service */ module.exports = {};
Strapi 的擴充機制
實際在 Strapi Admin Panel 定義好 Restaurant 以及看過專案目錄內的檔案後,可以歸納一下 Strapi 的設計及它的擴充機制,前面提過,在商業邏輯往前端移動的大前端時代的背景下,像 Strapi 這樣傻瓜型的 headless CMS 可以很快速讓我們定義出 model 的欄位以及產出相對應的 API 及文件,但因為 Strapi 依然是基於傳統的 web 框架 Koa,它還是保留了所有後端開發的架構,這樣的設計兼顧了速度與彈性。
在 Admin Panel 方面,除了 model 的定義與內容的管理外,看起來略顯陽春,但根據 Straip 的文件,Admin Panel 也是可以被客製的,另外 Strapi本身也有設計 plugin 的機制,包括 Strapi 自己的 GraphQL 也是以一支獨立的 plugin 的方式被使用。
總結
歸納一下 Strapi 的特點:
- 有 Admin Panel 用於定義資料與管理資料。
- 定義的資料會自動產出 API 與 API 文件給前端使用。
- 在 Admin Panel 定義的資料型態都會以 JSON 的格式儲存,因此可以被版控系統管理。
- 還是可以自行做後端開發與客製。
- 開源,可以自架,資料庫也放在自己家。
好處很明顯,API 的制定變得簡單又快速,time to market 時間可以省掉一半(寫後端的那一半)。
同場加映幾個也頗具特色的 headless CMS 及其它相關專案:
- Slicknode:headless CMS「服務」,無開源,資料放在 Slicknode 家,特色是跑在 AWS serverless 平台上,感覺比 Strapi 能應付更大的存取需求。
- Directus:和 Strapi 特色類似,也是開源專案,目前底層是 PHP 和 Zend,下一版 Directus 9 會改用 Node.js。
- FastAPI:把 headless CMS 的前台界面(如 Strapi 的 Admin Panel)再剝離的 web 框架,FastAPI 顧名思義是專門為 API 設計的框架,在程式碼內定義好 route、model、function 後 FastAPI 就會自動產出 API 文件,FastAPI 還有其它專為 API 設計的特性,可以訪問 FastAPI 網站了解。
補充
Strapi 有提供 rich text 型態的欄位,它在編輯區是以 Markdown 的方式做編輯,如下圖:
不過大家都知道 Markdown 本身的格式是受限的,例如不能指定 id
、class
,也不能改文字顏色,雖然 Markdown 允許在內文中直接插入 HTML,不過這樣就失去了這個 Admin Panel 存在的重要特性之一:讓非開發人員可以在此管理內容,殘念です。