サーバレスなCMS、Webinyを使ってみた

# Webinyとは
WebinyはオープンソースのCMSです。
いわゆるサーバレスアーキテクチャで構成されています。

AWSのアカウントを用意しコマンド実行するだけで、GraphQLや管理画面の環境が構築されます。
サーバレス故に維持費が安く、個人開発用途なら殆ど無料で使えると思います。

https://www.webiny.com/

本記事ではこのWebinyの導入の際のメモと、使ってみた感想をまとめました。

※この記事を書いている今現在、日本語の情報がほとんど無く、Qiitaにも記事がありませんでした。
ノウハウやカスタマイズ方法などご存知の方おられれば、シェアして頂ければと思います。

## Webinyを使った理由
**HeadlessCMSを安く**使いたかったからです。
個人開発でCMSを使いたかったのですが、

- 自前でホストしたい
  - 中身のソースを弄ってカスタマイズしたい（そこそこ複雑な要件）
    - でも1から作るのはしんどい
  - GraphQL使いたい
- 初期コストを抑えたい
  - できれば無料からで…！

という要件があり、Webinyはそれを満たすものだと思いました。

### 構成
TypeScriptでできており、GraphQLのAPIベースのHeadlessCMS、フロント側の静的ページなどで構成されます。

Webinyはコマンド一つでリソースのデプロイ、削除が行えます。
deployコマンドを実行すると、LambdaやDynamoDBなど必要なリソースが全てAWS上に構築されます。

リソースの管理には**Pulumi**（IaC）が用いられており、表の画面の表示からインフラ構成まで全てTypeScriptで書かれています。

# 導入
本記事では自前でホストしますが、SaaS版もあります。
（現在順番待ち状態でした。ユーザー枠1人なら無料で使えるようです…！）

https://www.webiny.com/pricing

## 実行環境
今回私が試した環境は以下の通りです。
```
Node.js 16.13.2
Yarn 3.2.3
Webiny 5.32.0
```
※Node.js v18はサポートされていません。nodenvやDockerでバージョン固定すると良いと思います

### AWSアカウント
前述の通り、AWS上にデプロイするためアカウントとユーザー認証情報が必要です。
私は個人環境なのもあり**AdministratorAccess**のユーザーで実行しましたが、
[必要なポリシーのみに絞った権限の構成](https://www.webiny.com/docs/infrastructure/aws/configure-aws-credentials#attaching-a-fine-grained-policy)も提供されています。

### Webinyのインストール
```
npx create-webiny-project プロジェクト名
```
1. デプロイ先リージョン
2. DB構成

を聞かれるので、`ap-northeast-1`(東京)、`DynamoDB`をそれぞれ選択。

2.は`DynamoDB + Elasticsearch`も選択できますが、**月額約25ドル**の固定費がかかります。
お試し用途なら選択しないのが無難だと思います。私は使ったことがありません。
![install.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385718/8050e620-8b1c-dfea-5b5e-ff83098a4c75.png)
プロジェクトはgit管理下にある状態で作成されます。
`.gitignore`のみの初期コミットのみがされた状態です。
```
cd プロジェクト名
```
でプロジェクトのフォルダに移動し、他ファイルもコミットしておきます。

### デプロイ
```
yarn webiny deploy
```

で開発環境がデプロイされます。
これだけでAPIや管理画面などがAWS上にどんどん構築されていきます…！
![deploy_completed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385718/bdf298a2-9445-90e2-cbde-f7a778f5d76c.png)
私の環境では10分ほどでデプロイが完了しました。
コンソールに表示されたURL（Admin app）にアクセスすると、管理画面が開きます。
![setup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385718/4671cb3a-b9fe-e966-5c91-767379d29b35.png)
初回は管理ユーザーやサイト名、locale（言語）などを設定していきます。
この辺りはStrapiなど他のCMSと同じです。

### 実際に使ってみる（コンテンツを作成し、APIで取得する）
インストールが完了したので、実際に使ってみます。
今回はブログ用途を想定し、以下を行います。

1. 記事の**Model**（テーブル）を作成 
2. 記事Modelの**Entry**（レコード）を作成
3. APIで記事を取得

管理画面は英語ですが割と直感的に操作でき、あまり迷うことはありませんでした。

#### Model（テーブル）を作成
「Headless CMS」の「Model」メニューより、「+NEW MODEL」からモデルを生成します。
左メニューから適切なタイプを選びドラッグ&ドロップでField（カラム）を追加します。
![new_model.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385718/8c57f8e1-1c87-e955-14f6-e9341715b42c.png)
`subject(text)`、`body(long text)`フィールドを持つ、`post`モデルを作成しました。

Referenceで他モデルと関連付けることもできます。一般的なカラムタイプは一通りサポートされていると思います。

#### Entry（レコード）を作成
メニューにいま作成した`posts`が追加されているので、「+NEW ENTRY」から記事を作成します。
![new_entry.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385718/b400cb81-c165-31e3-5660-0b01c4ab109d.png)
画面上に「**V1**(**DRAFT**)」とあるのにお気づきでしょうか。後述しますが、コンテンツは全てリビジョン管理されます。
今はひとまず、右上の「SAVE & PUBLISH」で記事の保存と公開をします。

#### APIで記事を取得
作成した記事をAPIで取得してみましょう。メニュー内「API Playround」を開きます。
「Main」と「Manage」「Read」「Preview」の4種類のAPIがありますが、
今回は「**Read**」を選択。下記を貼り付けて実行します。

```
{
  listPosts {
    data {
      id
      entryId
      subject
      body
    }
    error {
      code
      message
    }
  }
}

```
![playground.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385718/245122f4-2985-4324-7ad1-e49c5c811ef6.png)
先ほど作成した記事を取得できました！

API Playground内でSchemaの詳細やドキュメントなどが確認できます。
こちらもGraphQLの基本を抑えていれば、特段迷うことはないと思います。

実際に外部からAPIを実行するにはAPI Keyを発行する必要が出てくると思います。
これもメニュー内から簡単に作成できますが、本記事では省略します。

### 環境の削除
環境の削除の仕方についても触れておきます。

```
└── apps
    ├── admin 管理画面(CMS)
    ├── api   API（GraphQL）
    ├── core　 サイトの固有データ(DynamoDBなど)
  ( ├── theme )
    └── website フロントエンド部
```

Webinyは上記のようにapps/配下の4つのアプリで構成されており、
削除時は一つずつ削除対象のアプリを指定してやる必要があります。envも明示的に指定が要ります。
```
yarn webiny destroy apps/website --env dev
yarn webiny destroy apps/admin --env dev
yarn webiny destroy apps/api --env dev
yarn webiny destroy apps/core --env dev
```

また、必ず**この順序でないとうまく削除できない**です。
おそらく`apps/admin`は`apps/api`に、`apps/api`は`app/core`に…と依存しているからだと思われます。

※`apps/core`を消すとDB内のデータも全て消えてしまうので注意。
レコードだけでなくModelの設定などサイト情報の全てが消えます。

# 感想・補足
画面もドキュメントも英語ですが、何らかのHeadlessCMSを使ったことがあれば特に迷うことなく操作できると思いました。
機能も充実しており、カスタマイズなしでも十分使えると思います。

何より初期コストが抑えられるのが嬉しい。

## コンテンツのリビジョン管理について
コンテンツを更新するたびにV2、V3…と増えていき、いつでも前のリビジョンに遡ることができます。
それぞれのリビジョンは**Draft**（下書き）、**Published**（公開）、**Unpublished**（非公開）の状態を持ち、表側からはPublishedである1つのリビジョンのみが見えるようになっています。

これにより、よくある承認フローなんかも作れます。
デフォルトでこれだけ機能が作り込まれているのはありがたいです。

一方でAPIでコンテンツの更新をするのに、
更新対象のコンテンツの新しいリビジョンを作る → 作ったリビジョンを公開状態にする
と実行しなければならないのが面倒でした。
ちょっとしたモデルでもリビジョン管理されるため、やや冗長に感じるかもしれません。

## プラグインベースで拡張がしやすい
本記事では管理画面上でモデル作成しましたが、コード内でモデルやクエリを定義することもできます。

https://www.webiny.com/docs/headless-cms/extending/content-models-via-code#create-content-model-group-and-content-model-plugins

このように、ModelであればModelプラグインのクラスを読み込み、実装していきます。
Webiny自体、大部分がプラグインで構成されており、カスタマイズが前提になっているようでした。
TypeScriptの補完が効くこともあり、カスタマイズしやすい印象を受けました。

## DynamoDB
DynamoDBでデータを管理しているため、NoSQLのデメリットも享受することになります。
複雑なデータ検索は得意ではありません。
一覧取得のwhere条件で関連モデルを参照できますが、IDしか指定できません。
関連モデルの関連モデルを指定することもできませんでした。


# まとめ
- WebinyはサーバレスなCMS
- コマンド実行だけで環境丸ごとデプロイ・削除できる
- 初期コストを抑えられて、個人開発向き

本記事では紹介しきれなかった機能はまだたくさんあります。
例えばノーコードで静的ページを作れるPageBuilderは目玉の一つです。（私はCMSのみあれば良かったので使っていません）

冒頭にも書きましたが、今のところ日本語のノウハウ記事が見当たりません。
本記事で興味を持たれた方、ぜひ使ってみて情報をシェアして頂ければと思います。