Strapi (Headless CMS)を使ってみたので使い方まとめ + GraphQL


とよももです！
お仕事でHeadlessCMSのstrapiを触る機会がありました。情報が少なくて苦労したので、誰かの助けになる様にまとめたいと思います。

ちなみにvue + strapiでアプリを作ってみました。axiosでgraphqlを使いたい方など、参考に見てみてください。
・[レポジトリ](https://github.com/mtoyopet/vue-graphql-restaurant-list)

Strapiでの認証方法記事も書きました。
・[Strapiに認証機能を追加する（特定のユーザしかAPIが呼べない仕様に変更する）](https://qiita.com/mtoyopet/items/027bb614473b9c7f5c93)

## 事前知識
### CMSとは
CMSとは、`Contents Management System`の略です。CSSやHTMLなどのWEBサイトを構築するための専門的な知識を持っていなくてもWEBサイトやコンテンツを構築し、管理ができるシステムです。

最も代表的なCMSは「WordPress」。「はてなブログ」や「Qiita」、「Note」もCMSです。

CMSを使うことで、簡単にWEBサイトを構築できます。詳しくは下記の記事がわかりやすいです。
[CMSとは？初心者でもわかるCMSの基礎知識とメリット、導入事例](https://www.hitachi-solutions.co.jp/digitalmarketing/sp/column/cms_vol01/)

### Headless CMSとは
`コンテンツを管理するバックエンド側だけを提供する`のが「Headless CMS」です。

ここでいうヘッド(Head)というのは、画面に表示するビュー（フロントエンド）のことです。ヘッドレス(Headless)なので、フロントエンド側のないCMSということになります。

従来のCMSは、コンテンツを作って管理するバックエンドと、コンテンツを表示するフロントエンド、２つの機能があります。Headless CMSは、コンテンツを制作して管理するバックエンドしかありません。そのため、APIとしてHeadless CMSを使用することで簡単にコンテンツを管理できます。

Headless CMSの説明についてはこちらの記事がとてもわかりやすいです。
[ヘッドレスCMSとは何か？従来CMSとの違いやメリデメを解説！](https://microcms.io/blog/what-is-headlesscms/)

## Strapiセットアップ
では早速Headless CMSの[Strapi](https://strapi.io/)を触っていきたいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/a4327580-7097-9bd9-b176-032853467329.png)

### Strapiをインストール&新規プロジェクトを作成
下記コマンドを実行してstrapiをインストールします。

```
$ npm install strapi@alpha -g
```

次に、strapiアプリを新規作成しましょう。

```terminal
$ npx create-strapi-app my-project --quickstart

// または
$ yarn create strapi-app my-project --quickstart
```

### 管理者アカウントを作成
インストールが終わると、Strapiのアカウント作成画面が自動で開くので、登録を完了させます。自動で開かない場合は、http://localhost:1337/admin にアクセスします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/175492b2-5153-a047-6543-3e313823954b.png)

### ローカルサーバを動かすコマンド
万が一ローカルサーバを止めてしまったら下記のコマンドで動かしてください。

```
$ npm run develop
```

## コンテンツタイプの作成

いきなり`コンテンツタイプ`と出てきて混乱してしまいますが、通常の`データベース`の

- `コンテンツタイプ` → テーブル名
- `フィールド` → カラム名
- `エントリー` → レコード

だと思って進めるとわかり易いです。

### 初めてのコンテンツタイプを作成
まずは「テーブル名」となる`コンテンツタイプ`を作成していきます。
本記事では、「Restaurant」という`コンテンツタイプ`を作成します。また、「name」と「description」という２つの`フィールド`を作成します。

左側のタブから「Content Types Builder→「Create new collection type」を選択します。
Display Nameを入力し、「続ける」を押下します。ちなみに、Display Nameは単数形で問題ありません。Strapiが自動で複数形に直してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/05dfca7e-2c6c-6f05-c5c0-0442492bbec4.png)

content typeフィールドを選択し、名前を入力し「Finish」を選択します。今回は「text」タイプの「name」というフィールドを作成しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/4b329a88-475f-4030-0f40-5acac7f78865.png)

最後に、画面右側に詳細が表示されるので、保存ボタンで上書きしましょう。

### エントリーを作成
次に、レコードとなる`エントリー`を作成していきます。左側のタブからRestaurantsを選択し「Restaurantを追加」ボタンを選択します。
レストラン名と詳細を入力し保存すると、`レコード`が作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/0b68ff94-e7c9-1018-afa9-263b5fd7b467.png)

レコードが作成されたあと、右上の「Publish」ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/f59e9b6b-11be-f303-1ae3-2fe97b162ddd.png)


## 権限の設定
CRUD操作を行うには、別途権限を付与する必要があります。**設定を行ってない場合は、APIを呼び出しても404エラーになってしまう**のでこの設定は忘れない様にしてください。

左側のタブから「設定」を選び、「ロールと権限」→「Public」を選択します。Restaurantの「findone」にチェックをいれるとレストランを1つだけ呼び出すためのAPIのURLが右側に表示されます。あとで「create」「update」「delete」もやるので、全てにチェックを入れておきましょう。右上の「save」ボタンを忘れずにクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/26b51fd4-b3d2-0b34-4bc8-384b1db91728.png)


保存をして、 試しに localhost:1337/restaurants/1 にアクセスしてみます。
取れた！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/caff5c0b-55b8-5477-4d62-0a83d8a08d45.png)

## GraphQLを使ってCRUD操作を行う
### プラグインを導入
GraphQLはデフォルトで入っていないため、プラグインとして別途導入する必要があります。

```
$ strapi install graphql
// ↑でインストール後エラーが出る場合、
// $ strapi uninstall graphql コマンドでアンインストールし、
// ↓のコマンドで試してみてください

//または
$ npm run strapi install graphql

//または
$ yarn strapi install graphql

```
導入が完了したら、 http://localhost:1337/graphql にアクセスします。
こんな画面が出てくるはず！ここでGraphQLをいじってデータを取ったり新規作成したりできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/d3a06f59-bc73-2402-e9e5-cc42150341bb.png)

## Graphqlの使い方を簡単に紹介

**やりたいこと**
・データを読み取るだけの場合は「query」を指定する
・データを修正したり新規作成などの編集を行う場合は「mutation」を指定する

**CRUD操作の名前とコンテンツタイプ名**
・get/create/update/deleteのどれかの後に、操作したいコンテンツタイプ名を指定する
・CRUD操作名は小文字から、コンテンツタイプ名は大文字から始める
・ここを間違えると動かないので注意

```graphql
# 構文：

# やりたいこと(query/mutation) CRUD操作の名前とコンテンツタイプ名 {
  # コンテンツタイプ名(id: id) {
      # id
      # name
      # description
    # }
  # }

# 例えば： idが1のrestaurantのid・name・descriptionを取りたい時
query getRestaurants {
  restaurant(id: 1) {
      id
      name
      description
    }
  }
```

### 注意点
やりたいこと(queryまたはmutation)の後に続く`CRUD操作の名前とコンテンツタイプ名`は、GraphQLの決められたルールに基づいています。**適当につけているわけではありません。**

**これを間違えるとCRUD操作が動かないので注意してください。**

### GET:レストランを一つだけ取ってみる

idが１のRestaurantを取り出してみます

```graphql
query getRestaurants {
  restaurant(id: 1) {
      id
      name
      description
    }
  }
```

取れた！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/91897959-9b59-7b4d-ed7c-31ebd5eccdf9.png)

### CREATE: Restaurantを新規作成する
```graphql
mutation {
  createRestaurant(input: {
    data: {
      name: "みっちゃん",
      description: "広島で有名なお好み焼き屋さん"
    }
  }) {
    restaurant {
      name
      description
    }
  }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/6acee3d3-fac2-6d40-d46b-d3ee1d6a5196.png)

無事できてるっぽい！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/249a2834-fb7d-55c2-ba98-446bae95c7a7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/8af12d29-3a3d-1fd5-08bd-e36ba804152c.png)

### DELETE:Restaurantを削除する

```graphql

mutation {
  deleteRestaurant(input: {
    where: {
      id: 3
    }
  }) {
    restaurant {
      name
      description
    }
  }
}
```

### UPDATE:Restaurantを編集する

```graphql
mutation {
  updateRestaurant(input: {
    where: {
      id: 26
    },
    data: {
      name: "モスバーガー"
    }
  }) {
    restaurant {
      name
    }
  }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/1fd95d3e-9579-7e0f-4ac7-b510b7f43d77.png)

## Herokuにデプロイする
### デプロイの準備
StrapiをHerokuにデプロイする方法です。詳しくは「[公式サイト](https://strapi.io/documentation/3.0.0-alpha.x/guides/deployment.html#heroku)」にめちゃくちゃ丁寧な解説が載っています。詳しく知りたい方は公式ページを読みながら進めることを強くお勧めします。

デプロイの前に下記を行ってください。
・Gitをインストール
・Herokuアカウントの新規作成
・Heroku CLIのインストール

Gitを有効化、最初のcommitをしておきます。

```
$ cd my-app
$ git init
$ git add .
$ git commit -am "Initial commit"
```

Herokuアプリを新規作成します。

```
$ heroku create
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/a539783b-6192-16f7-c169-e758c356cccf.png)
この時点で、自身のHerokuアカウントを覗くと、アプリが作成されているはずです。

### Postgresのアドオンを追加する
ローカルではsqliteを使用していましたが、Herokuでは今回はPostgresqlを使っていきます。HerokuのアドオンにPostgresを追加します。

```terminal
$ heroku addons:create heroku-postgresql:hobby-dev
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/f452a1aa-51b0-c7a9-a2d3-d2a64e52a5fc.png)

### 環境変数を設定する
``heroku config``を打つと、今入れたPostgresデータベースの情報が出てくると思います。
例：``DATABASE_URL: postgres://ebitxebvixeeqd:dc59b16dedb3a1eef84d4999sb4baf@ec2-50-37-231-192.compute-2.amazonaws.com: 5432/d516fp1u21ph7b``

これをもとに環境変数を設定していきます。
``postgres:// USERNAME : PASSWORD @ HOST : PORT : DATABASE_NAME``


```terminal
$ heroku config:set DATABASE_USERNAME=ebitxebvixeeqd
$ heroku config:set DATABASE_PASSWORD=dc59b16dedb3a1eef84d4999a0be041bd419c474cd4a0973efc7c9339afb4baf
$ heroku config:set DATABASE_HOST=ec2-50-37-231-192.compute-2.amazonaws.com
$ heroku config:set DATABASE_PORT=5432
$ heroku config:set DATABASE_NAME=d516fp1u21ph7b
```

:warning: ⬆️のデータベースは既に削除してます。本来であれば機密情報なので、⬆️のように大衆が見る場所にデータベース情報を晒さないよう、十分注意してください。

### database configファイルの修正
`` ./config/environments/production/database.json``の中身を下記の様に置き換えます。

```json:database.json
{
  "defaultConnection": "default",
  "connections": {
    "default": {
      "connector": "bookshelf",
      "settings": {
        "client": "postgres",
        "host": "${process.env.DATABASE_HOST}",
        "port": "${process.env.DATABASE_PORT}",
        "database": "${process.env.DATABASE_NAME}",
        "username": "${process.env.DATABASE_USERNAME}",
        "password": "${process.env.DATABASE_PASSWORD}",
        "ssl": true
      },
      "options": {}
    }
  }
}
```

### pgモジュールをインストール
PostgreSQLのStrapiをインストールしていない場合は、下記コマンドを実行してpg nodeモジュールをインストールします。

```
$ npm install pg --save
```


### 最後にデプロイ！

準備が整ったら、最後にHerokuにstrapiアプリをデプロイしましょう。

```
$ git commit -am "database設定をアップデート"
$ git push heroku master
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/415f2341-400a-e345-a04e-3cdee4bcba5f.png)

「herokuアプリのurl/admin」にアクセスすると、productionの管理画面にアクセスすることができます。

### ローカルとプロダクションの違いや注意点
- CRUD操作の権限は「ロールと権限」から付け直す必要がある
- productionモードでは、コンテンツタイプの新規作成や削除はできない
- よってコンテンツタイプの編集はローカルで行い、herokuにデプロイすることで反映させる必要がある
- エントリーも作り直す必要がある

### APIとして使用する場合
ローカルは「 http://localhost:1337/graphql 」で呼び出すことができます。herokuの場合は「 herokuアプリurl/graphql 」です。

## 複数のコンテンツタイプで一対多の関係性を持たせたい時
簡単なやること管理ツールを作ると仮定して「Project」と「Todo」というコンテンツタイプを作成しました。それぞれ「name」というフィールドを持たせています。

まず「Project has many todos」という関係性になるので、そのリレーションをはっていきます。

### リレーションをはる
コンテンツタイプ作成から「Todo」、「Add another field」を選択します。Select a field for your content typeの画面で「Relation」を選択します。
Add new Relation fieldの画面で適したリレーションを選びます。今回の場合は「Project has many todos」ですね。「Finish」、「保存」を忘れずに。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/39fc0e2a-889b-2dbd-efb4-cecc3bb2b04b.png)

### リレーションをはったエントリーを作成する
左側のタブからTodosを選び、「Todoを追加」、nameを入力します。nameの横に「Project」と書いてあるボックスがあるので、そこで紐付けたいProjectを選択します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/364ee885-694f-af55-0f3d-6fe2ca2a753a.png)


### TodoとProjectが取れるかどうかチェック
※ロールと権限から権限付与を忘れずに！

```graphql
query getTodos {
  todos {
    id
    name
    project {
      id
      name
    }
  }
}
```
取れた！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/1a15b7a8-2a5c-7e1f-4811-6b04933991af.png)

### すべてとれるか試してみる
今回は「status」というコンテンツタイプも作成しました。これは「todo has one status」というリレーションで紐づいています。

```graphql
query getTodo {
  todos {
    id
    name
   status {
    id
  }
   project {
      id
    }
  }
}
```

取れた！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/9e1dead4-e8b0-e6c9-57f9-02befb924507.png)

### あるTodoのプロジェクトIDを変更してみる

```graphql

mutation {
  updateTodo(input: {
    where: {
      id: 46
    },
    data: {
      name: "9時に起きる"
      project: 10
    }
  }) {
    todo {
      id
      name
      project {
        id
        name
      }
    }
  }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/0df45444-8bef-dfec-9033-f8ca8a466aff.png)


strapiをAPIとして使用しているアプリをvueで作っているので、axiosでgraphqlを使いたい方など、参考に見てみてください。
https://github.com/mtoyopet/vue-graphql-restaurant-list