💨

Strapi v4で「スラッグで記事を返す」GraphQLの作り方

2021/12/20に公開約6,300字

Strapi v4ではGraphQLが大幅に仕様変更され、nexusを使ったクエリ等の作成が可能になりました。

今回は「スラッグで記事を返す」ごく普通のクエリを実装します。

1. アプリ本体にregisterする

src/index.js
'use strict';

module.exports = {
  /**
   * アプリ初期化前に実行される
   * @see https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.html#register
   */
  register({ strapi }) {
    if (strapi.plugin('graphql')) {
      require('./custom/graphql')({ strapi });
      console.log(`bootstrap: Custom graphql loaded`);
    }
  },
};

https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/functions.html#register

register関数はStrapiサーバーの起動前に実行されます。

./custom/じゃなくても構いません。 GraphQLはconfigと違い、strapi自体のファイルを上書きして設定するわけではないので適当な命名にしています。

2. GraphQLカスタマイズファイルを作成

これが大幅に変わりました。api/XXXX/config/schema.graphql.jsは使えません。 代わりに別で定義します。

https://github.com/strapi/strapi/blob/master/packages/plugins/users-permissions/server/graphql/index.js

users-permissionsプラグインを参考にします。

mkdir -p src/custom/graphql
touch src/custom/graphql/{index,resolvers-configs}.js
src/custom/graphql/index.js
'use strict';
const getQueries = require('./queries');
const getResolversConfig = require('./resolvers-configs');

/**
 * GraphQLのカスタマイズ
 * @see https://github.com/strapi/strapi/blob/master/packages/plugins/users-permissions/server/graphql/index.js
 */
module.exports = ({ strapi }) => {
  const extensionService = strapi.plugin('graphql').service('extension');

  extensionService.use(({ nexus }) => {
    const queries = getQueries({ strapi, nexus });
    const resolversConfig = getResolversConfig({ strapi });

    return { types: [queries], resolversConfig };
  });
};

extensionService.useにnexusの設定を渡してカスタマイズします。なお、今回はミューテーションと型には触りません。

src/custom/graphql/resolvers-configs.js
'use strict';

/**
 * resolverの設定
 * @see https://github.com/strapi/strapi/blob/master/packages/plugins/users-permissions/server/graphql/resolvers-configs.js
 * @see https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#customization
 */
module.exports = () => {
  const postUID = 'api::post.post';
  return {
    [`Query.postBySlug`]: {
      auth: { scope: [`${postUID}.findOne`] },
    },
  };
};

https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#customization

今回はQuery.postBySlugを作ります。postBySlug}の認証をここで無効化します。

Query.postBySlugにはfindOneのスコープを紐付けたので、管理画面でfindOneにチェックを入れないと、該当ロールでは取得できません。

3. クエリのindex.jsを作成

mkdir -p src/custom/graphql/queries
touch src/custom/graphql/queries/{index,postBySlug}.js
src/custom/graphql/queries/index.js
'use strict';

const postBySlug = require('./postBySlug');
/**
 * クエリのカスタマイズ
 * @see https://github.com/strapi/strapi/blob/master/packages/plugins/users-permissions/server/graphql/queries/index.js
 */
module.exports = ({ strapi, nexus }) => {
  return nexus.extendType({
    type: 'Query',

    definition(t) {
      t.field('postBySlug', postBySlug({ strapi, nexus }));
    },
  });
};

今後クエリはここに登録していきます。

4. EntityResponseとして返す

いよいよクエリ自体を書きます。

src/custom/graphql/queries/postBySlug.js
'use strict';

/**
 * slugでpostを取得
 * @see https://github.com/strapi/strapi/blob/master/packages/plugins/users-permissions/server/graphql/queries/me.js
 * @see https://github.com/strapi/strapi/issues/11745
 */
module.exports = ({ strapi, nexus }) => {
  const { transformArgs } = strapi.plugin('graphql').service('builders').utils;
  const { toEntityResponse } = strapi
    .plugin('graphql')
    .service('format').returnTypes;
  const uid = 'api::post.post';
  return {
    type: 'PostEntityResponse',
    args: { slug: nexus.stringArg() },

    async resolve(parent, args, ctx) {
      const transformedArgs = transformArgs(args, {
        contentType: strapi.contentTypes[uid],
        usePagination: false,
      });
      if (!transformedArgs.slug) {
        throw new Error('スラッグを指定してください。');
      }

      const nodes = await strapi.entityService.findMany(uid, {
        filters: transformedArgs,
      });

      if (nodes.length > 0) {
        /**
         * 適切な形に変換する
         * @see https://github.com/strapi/strapi/issues/11745#issuecomment-997989294
         */
        return toEntityResponse(nodes[0], { transformArgs, uid });
      } else {
        throw new Error(ctx.koaContext.response.message);
      }
    },
  };
};

長いので一つづつ見ていきましょう。

args

{ slug: nexus.stringArg() }

https://nexusjs.org/docs/api/args

nexusのargsから選びます。

transformArgs

https://github.com/strapi/strapi/blob/master/packages/plugins/graphql/server/services/builders/utils.js#L96-L132

transformArgsはめっちゃ便利。ctx.argsを、GraphQLの引数に変換します。

strapi.entityService.findMany

IDが不明なのでfindOneが使えません。

if (!transformedArgs.slug) {
  throw new Error('スラッグを指定してください。');
}

const nodes = await strapi.entityService.findMany(uid, {
  filters: transformedArgs,
});

そこでこうします。Entity Service APIは、コンポーネントやダイナミックゾーンが絡んだデータを扱える新しいAPIです。

toEntityResponse

GraphQLのカスタマイズについて、どうフォーマットすればいいのかなと考えていたところ...

https://github.com/strapi/strapi/issues/11745#issuecomment-997989294

On a side note: toEntityResponseCollection is basically just a wrapper that matches the expected format for the findMany output which is { value, args, resourceUID }. For a single resource you can use toEntityResponse.

ちょうど数時間前、Strapi開発者の一人がtoEntityResponseという関数を紹介していました。

https://github.com/strapi/strapi/blob/master/packages/plugins/graphql/server/services/format/return-types.js

実装はこちら。

return toEntityResponse(nodes[0], { transformArgs, uid });

こうすれば{ value, info: { args, resourceUID } }の形になり、GraphQLに正しくPostEntityとして返すことができます。

動作確認

Apollo Studioでlocalhost:1337/graphqlを読ませます。クエリを超楽にテストできます。

スラッグ未指定時はエラーが出ます。スラッグに合う記事がないときはNot Foundになります。

参考

https://docs.strapi.io/developer-docs

https://github.com/strapi/strapi/issues/11745
GitHubで編集を提案

Discussion

ログインするとコメントできます