[HeadlessCMS] contentful でエントリーを取得する色んなクエリ

contentful を触り始めてみました。
Content Delivery API を使ってエントリーを取得するクエリの記述を一部まとめてみました。

![logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49417/eca21f88-3232-b945-f80a-01146d5402e7.png)


ブログによくある以下のような記事を取得できます。

- 記事一覧
- カテゴリアーカイブ
- 月別アーカイブ
- ページネーション
- 検索一覧

その他詳細は[公式](https://www.contentful.com/developers/docs/references/content-delivery-api/)を参照してください。

## 準備

client には Contentful Delivery APIクライアント が入っています。

```javascript
const contentful = require('contentful')

const client = contentful.createClient({
  space: '<space_id>',
  accessToken: '<content_delivery_api_key>'
});
```

# getEntry('\<entry_id>')

エントリーIDを指定して、単一のエントリーを取得します。

```javascript
client.getEntry("31TNnjawUogHlfGUoMO0M2").then(entry => {
  console.log(entry.fields.title);
  console.log(entry.fields.body);
  console.log(entry.sys.createdAt);
});
```

このとき `fields` には Content type に自身で追加したフィールド（タイトル、本文など）が入っています。
`sys`にはシステム管理のメタデータ（id, revision, createdAtなど）が入っています。


# getEntries()

全てのエントリーを取得します。
引数にオプションを指定しない場合には、 `entries.items` に、記事や人やカテゴリなど、Content type を元にした全てのエントリーが配列で入ります。

```javascript
client.getEntries().then(entries => {
  console.log(entries.items[0].fields.title);
  console.log(entries.items[1].fields.title);
  console.log(entries.items[2].fields.title);
});
```

この `getEntries()` の引数に様々なオプションを含めることができます。

## 指定した Content type の全てのエントリーを取得

以下はブログ記事の一覧を取得します。

```javascript
client.getEntries({
  content_type: "blogPost"
}).then(entries => {
  console.log(entries.items); // blogPostのエントリー配列
});
```

## 指定したフィールドと一致する全てのエントリーを取得

Memoフィールドに `'hoge'` という __String__ を設定している全てのエントリーを取得します。
このように `fields` を使用した検索をする場合は、 `content_type` を指定する必要があります。

```javascript
client.getEntries({
  content_type: "blogPost",
  "fields.memo": "hoge"
}).then(entries => {
  console.log(entries.items);
});

```

## 指定したフィールドと一致しない全てのエントリーを取得

[ne]オペレーターを使うと、 `'hoge'` という __String__ を__設定していない__全てのエントリーを取得します。

```javascript
client.getEntries({
  content_type: "blogPost",
  "fields.memo[ne]": "hoge"
}).then(entries => {
  console.log(entries.items);
});

```


## List形式のフィールドの検索

Likesフィールド（List形式）に `'banana'` が含まれている全てのエントリーを取得します。

```javascript
client.getEntries({
  content_type: "blogPost",
  "fields.likes": "banana"
}).then(entries => {
  console.log(entries.items);
});
```

その他のに使えるオペレータはこちら。

| オペレータ |  |
|:------|:------------|
| [ne]  | 指定した値が含まれない全てのエントリーを取得  |
| [all]  | 指定した複数のパラメータが全て含まれている全てのエントリーを取得  |
| [in]  |  指定した複数のパラメータのどれかが含まれている全てのエントリーを取得 |
| [nin]  |  指定した複数のパラメータのどれも含まれている全てのエントリーを取得 |
| [exists]  |  フィールドに値が設定されている全てのエントリーを取得（パラメータが`true`の場合）   |
| [exists]  |  フィールドに値が設定されていない全てのエントリーを取得（パラメータが`false`の場合）   |

        


## 日付形式, 数値形式の検索

以下のような範囲を指定するオペレータが使用できます。

| オペレータ |  |
|:--|:--|
| [lt]  | より小さい  |
| [lte]  | 以下  |
| [gt]  | より大きい  |
| [gte]  | 以上  |

2019年10月のアーカイブスを表示したい場合、以下のようなクエリで取得します。

```javascript
const year = 2019;
const month = 10;

// 年と月からその月の最終日を返す関数
const getLastDay = (year, month) => {
  const lastDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  const isLeap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);

  if (isLeap && month === 2) {
    return 29;
  } else {
    return lastDays[month - 1];
  }
};

client.getEntries({
  content_type: "blogPost",
  "fields.publishDate[gte]": `${year}-${month}-01`, // その月の初めの日以上
  "fields.publishDate[lte]": `${year}-${month}-${getLastDay(year, month)}` // その月の最終日以内
}).then(entries => {
  console.log(entries.items);
});
```

## 全文検索

指定した文字列を含む全てのエントリーを取得できます。

```javascript
client.getEntries({
  content_type: "blogPost",
  query: "orange"
})
.then(entries => {
  console.log(entries.items);
});
```

[match]オペレータでフィールドを指定した全文検索も可能です。

```javascript
client.getEntries({
  content_type: "blogPost",
  "fields.description[match]": "Hello"
})
.then(entries => {
  console.log(entries.items);
});
```

## エントリーの並び替え

`order`を使用して、順序を指定できます。
このとき指定パラメータの接頭に `-` を付けると逆順になります。

```javascript
client.getEntries({
  content_type: "blogPost",
  order: "-sys.createdAt"
})
.then(entries => {
  console.log(entries.items);
});
```

指定できるパラメータは、日付や数値やブール値、256字以下の文字列のみです。
複数指定することも可能です。

```javascript
client.getEntries({
  order: '-sys.createdAt,sys.id'
})
```

## ページネーション（取得数制限とオフセット機能）

`limit`を使用して、取得数を制限できます。
`skip` で取得位置のオフセットを指定できます。

ページネーションは以下のようなクエリで実装できます。

```javascript
const MAX_ENTRY = 20;
const page = 3;

client.getEntries({
  content_type: "blogPost",
  order: "sys.createdAt",
  limit: MAX_ENTRY,
  skip: MAX_ENTRY * (page - 1)
})
.then(entries => {
  console.log(entries.items);
});
```

## Reference形式の検索

以下は、参照形式であるcategoryフィールドを指定した検索です。
指定する Reference の content type ID もクエリに含める必要があります。

```javascript
client.getEntries({
  content_type: "blogPost",
  "fields.category.sys.contentType.sys.id": "tag", // ←これも必要
  "fields.category.fields.name": "Game"
})
.then(entries => {
  console.log(entries.items);
});
```

__[追記]__
sys.id を直接指定する場合は contentTypeIDの指定はいらないっぽい。
この場合、リスト形式のReference（= Many references）での取得が可能。
指定する sys.id は contentful CMSサイト の contentページ-> 各contentページ -> 右上のInfoボタン -> ENTRY ID で確認できるランダムな英数字。

```javascript
client.getEntries({
  content_type: "blogPost",
  "fields.categories.sys.id": "<ENTRY ID>"
})
.then(entries => {
  console.log(entries.items);
});
```
上記は複数カテゴリ（単カテゴリでも可）を持つ記事を、カテゴリ検索する場合。 事前にカテゴリを getEntries して fields の内容と一緒に sys.id を取得した上でカテゴリ検索する必要がある。

# おわり

自分のサイトは何年もwordpressで運用してるので、 contentful に乗り換えてみようかと思います。