📚

contentful.jsのv10から登場した2つのクライアントチェーン修飾子について解説

に公開

この記事で解説すること

  • contentful.jsを使ったエントリーの取得
  • contetful.jsでフィールドにReferencesのあるエントリーを取得したときの動作
  • クライアントチェーン修飾子のwithoutLinkResolutionとwithoutUnresolvableLinksについて解説

contentful.jsを使ったエントリーの取得

contentful.jsはCDA(Content Delivery API) と CPA(Content Preview API)を利用するためのjavascriptのクライアントライブラリです。
このライブラリを使用することでコンテンツの取得と利用が簡単に行えるようになります。
例として、fetchを使用する場合とcontentful.jsを使用する場合を比べます。
fetchを使ってCDAにリクエスト送り、単一のエントリーを取得する場合はこのようになります。

 fetch(
      "/spaces/{space_id}/environments/{environment_id}/entries?sys.id={entry_id}",
      {
        headers: {
          authorization: access_token,
        },
      }
    )

一方、contentful.jsを使用する場合はこのようになります。
contentful.jsでは内部でaxiosが使われており、client.getEntry()を実行した場合、内部のaxiosを使ってCDAにリクエストが行われています。

import { createClient } from "contentful";

const client = createClient({
  environment: environment_id,
  space: space_id,
  accessToken: access_token,
});

client.getEntry(entry_id)

このように、 fetchを使用する場合は利用したいCDAのAPIに応じてエンドポイントを記述する必要がありますが、contentful.jsでは作成したclientのメソッドで実行できるので簡略化することができます。

contetful.jsでフィールドにReferencesがあるエントリーを取得したときの動作

contentful.jsによるReferenceの自動解決

contentful.jsを使うと、Referencesされたフィールドがあるエントリーを取得した際に自動でエントリー同士を解決してくれます。

例えば、以下のようなBookとUserというモデルがあるとします。
Bookモデルのautherというフィールドでは、UserモデルをReferenceするように定義しています。

Bookモデル

Userモデル

このモデルを使って、BookとUserのエントリーを1つずつ作成します。
今回は例として、「はらぺこあおむし」のBookのエントリーを作成しました。
Bookの作者(auther)として「エリック・カール」のUserのエントリーを参照しています。

Bookのエントリー

Userのエントリー

作成した「はらぺこあおむし」のBookデータを取得するリクエストを行います。

 fetch(
      "/spaces/{space_id}/environments/{environment_id}/entries?sys.id={entry_id}",
      {
        headers: {
          authorization: access_token,
        },
      }
    )

実行するとこのようなレスポンスが返ってきます。

解説のため、metaデータなどを取り除いて簡素化するとこのような構成になっています。

{
    "includes": {
        "Entry": [
            {
                "sys": {
                    "id": "1C6ikIdCM0uPxM5LupuqMW",
                },
                "fields": {
                    "name": "エリック・カール"
                }
            }
        ]
    },
    "items": [
        {
            "fields": {
                "title": "はらぺこあおむし",
                "auther": {
                    "sys": {
                        "type": "Link",
                        "linkType": "Entry",
                        "id": "1C6ikIdCM0uPxM5LupuqMW"
                    }
                }
            }
        }
    ],
}

itemsの中に含まれるのは、今回取得した「はらぺこあおむし」のデータです。
中のfieldsにはtitleautherが含まれていますが、autherはUserのエントリーを参照しているので、リンクオブジェクトになっています。
Userのデータはどこにあるのかというと、includesの中にあります。
includesには取得した記事が参照しているデータが格納されます。

もし、fetchを使用してこの記事を取得し、autherのデータを使用した場合、fieldsのデータからは直接使用できないため少し面倒です。

    fetch(
      "https://cdn.contentful.com/spaces/{space_id}/environments/{environment_id}/entries?sys.id=UpsUIh6ztwFOpiXdFI06q",
      {
        headers: {
          authorization: access_token,
        },
      }
    ).then((res) => {
      res.json().then((json) => {
        console.log(json.items[0].fields.auther); // autherはリンクオブジェクトになる
      });
    });

consnole.logの結果

{
    "type": "Link",
    "linkType": "Entry",
    "id": "1C6ikIdCM0uPxM5LupuqMW"
}

一方でcontentful.jsを使用した場合、この問題を自動で解決してくれます。
contentful.jsは取得したエントリーがリンクオブジェクトだった場合、includesの中から一致するエントリーがないか確認し、あった場合は直接参照できるようにしてくれます。

    const result = await client.getEntry("UpsUIh6ztwFOpiXdFI06q");
    console.log(result.fields.auther); 

consnole.logの結果

{
    "metadata": {
        ...
    },
    "sys": {
        ...
    },
    "fields": {
        "name": "エリック・カール"
    }
}

Referencesされたエントリーが存在しなかった場合

上記の「はらぺこあおむし」のautherフィールドには「エリック・カール」のUserのエントリーが参照されていました。
ではもし、そのユーザーのエントリーが消えてしまった場合どうなるでしょうか?
実際に削除してみるとautherはこのような表示になります。

この状態でcontentful.jsを使ってエントリーを取得した場合、以下のような結果になります。

    const result = await client.getEntry("UpsUIh6ztwFOpiXdFI06q");
    console.log(result.fields.auther); 

consnole.logの結果

{
    "type": "Link",
    "linkType": "Entry",
    "id": "1C6ikIdCM0uPxM5LupuqMW"
}

CDAから返ってきたときと同じ、リンクオブジェクトのデータになります。
includesの中にUserのデータが存在しなかったので、Referencesが解決できなかったためです。
これではresult.fields.autherを利用する際に、autherの値が解決されたエントリーのデータになる場合とリンクオブジェクトになる場合の2つになるため、複雑な条件分岐が発生します。

クライアントチェーン修飾子を使用してReferencesを解決するときの挙動を変更する

上記の問題が起きたときに有効なのがv10から追加されたクライアントチェーン修飾子withoutLinkResolutionwithoutUnresolvableLinksです。
それぞれ解説していきます。

withoutLinkResolution

withoutLinkResolutionはエントリーのReferrencesを解決しないようにする修飾子です。
上記のモデルで使用した場合、autherの中身はリンクオブジェクトになります。

エントリーの状態

    const result = await client.withoutLinkResolution.getEntry("UpsUIh6ztwFOpiXdFI06q");
    console.log(result.fields.auther); 

consnole.logの結果

{
    "type": "Link",
    "linkType": "Entry",
    "id": "1C6ikIdCM0uPxM5LupuqMW"
}

withoutUnresolvableLinksはエントリーのReferrencesが解決できない場合、リンク オブジェクトを削除する修飾子です。
解決できた場合は修飾子をつけない場合と変わりませんが、解決できなかった場合はundefinedになります。

エントリーの状態

    const result = await client.withoutUnresolvableLinks.getEntry("UpsUIh6ztwFOpiXdFI06q");
    console.log(result.fields.auther); // undefined

それぞれどのように使えばいいか?

ReferrencesされたフィールドがあるEntryを取得するときは、基本的にはwithoutUnresolvableLinksを使用すればいいと思います。
Referrencesされたフィールドの値は解決されたエントリーの値もしくはundefinedになるので、条件分岐が楽になります。
typescriptを使用しているならオプショナルチェーンを使うことでもっと楽に使用できます。
もしオプショナルチェーンを使用しなかった場合は、解決されたエントリーの値、リンクオブジェクト、またはそもそも値が入稿されていない場合のundefinedの3パターンになるので、条件分岐が複雑になります。

withoutLinkResolutionはあまり使用するタイミングはないと思いますが、私は循環参照されたReferrencesのフィールドを利用するときに1度だけ使わざるを得ないことがありましたが詳しくは省略します。

Discussion