こんにちは。キューでWebエンジニアをしている永井です。
今回は、Strapi v4のAPIを扱いやすくする方法を紹介します。
Strapi v4ではAPI周りに大きな変更がありましたが、v3と比べると扱いにくくなりました。
簡単にあげると以下のような点です。
- レスポンスの階層が深くなり、必要なデータにアクセスしづらい
- 関連テーブルのデータ取得にクエリを指定する必要がある
- カウントAPIが削除された
こちらを修正して扱いやすくしていきます。
前提
・Strapi v4のバックエンド環境がある
バックエンドの実装
コントローラーの編集
対象APIのコントローラーファイル(/api/[apiName]/controllers/[apiName].js)を編集します。
コントローラーファイルを編集することで、既存APIの動作を変更することができます。
「一覧取得(find)」と「詳細取得(findOne)」をオーバーライドしてレスポンスを変更します。
全体のコードはこちらです。
"use strict";
const { createCoreController } = require("@strapi/strapi").factories;
module.exports = createCoreController("api::xxx.xxx", () => ({
async find(ctx) {
ctx.query = { ...ctx.query, populate: "*" };
const { data } = await super.find(ctx);
return toV3Data(data, ctx.query);
},
async findOne(ctx) {
if (ctx.request.url.includes("/count")) {
const {
meta: {
pagination: { total },
},
} = await super.find(ctx);
return total;
}
ctx.query = { ...ctx.query, populate: "*" };
const { data } = await super.findOne(ctx);
return toV3Data(data, ctx.query);
},
}));
/**
* strapi v4のレスポンスをv3の形に変換する
*/
const toV3Data = (data, query) => {
if (data == null) return null;
if (data instanceof Array) {
return data.map((d) => toV3Data(d, query)).filter((d) => d);
}
return {
id: data.id,
...Object.entries(data.attributes).reduce((acc, [key, value]) => {
if (key === "createdAt" || key === "updatedAt" || key === "publishedAt") {
key = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
}
if (value != null && typeof value === "object" && "data" in value) {
value = toV3Data(value.data, query);
}
return { ...acc, [key]: value };
}, {}),
};
};
解説
クエリの追加
v4から関連テーブルのデータを取得するには、populate
を指定する必要があります。
今回はデフォルトで全て取得したいので、populate: "*"
を指定します。
全て取得したくない(クエリで制御したい)場合は、特に追加する必要はありません。
カウントの追加
v3にあった/[apiName]/count
のAPIはv4で削除されました。カウントは一覧取得のメタ情報から取得できますが、APIとして用意されていた方が便利なので追加します。
追加方法としては2つあります。
- findOneの関数内に処理を追加する方法
/[apiName]/count
を呼び出すと、fineOneのURLパターン/[apiName]/:id
に引っかかりfindOneが実行されてしまいます。そこで、findOneの関数内でidが"count"だった場合の処理を追加します。 - カスタムルーターを作成する方法
APIにカスタムルーターを作成してルーティングを変更します。例えば、findOneに向いているルーティングを別途用意したcountの関数に向けることができます。
今回はお手軽に実装できるfindOneの関数内に処理を追加する方法を採用しています。
カスタムルーターの詳細はこちらから確認できます。
v3変換用に関数を追加
v4のレスポンスのデータ構造を、v3のデータ構造に変換する関数を追加します。
v4のデータはdata
やattributes
といった階層が増えてとても扱いづらいです。
なので、そちらを削除してv3の時と同じデータにします。
・変換前のデータ(v4データ)
data: [
{
id: 1,
attributes: {
title: "Test",
content: "Test content",
publishedAt: "2023-01-30T09:24:58.606Z",
createdAt: "2023-01-30T09:24:03.890Z",
updatedAt: "2023-01-30T09:24:58.615Z",
tags: {
data: [
{
id: 1,
attributes: {
name: "Test Tag",
publishedAt: "2023-01-30T09:25:54.720Z",
createdAt: "2023-01-30T09:23:50.468Z",
updatedAt: "2023-01-30T09:25:54.741Z"
},
},
],
},
},
},
]
・変換後のデータ(v3と同等)
[
{
id: 1,
title: "Test",
content: "Test content",
published_at: "2023-01-30T09:24:58.606Z",
created_at: "2023-01-30T09:24:03.890Z",
updated_at: "2023-01-30T09:24:58.615Z",
tags: [
{
id: 1,
name: "Test Tag",
published_at: "2023-01-30T09:25:54.720Z",
created_at: "2023-01-30T09:23:50.468Z",
updated_at: "2023-01-30T09:25:54.741Z"
}
]
}
]
処理をまとめる
APIが複数ある場合、同じ処理を毎回書くのは面倒なのでまとめます。
Strapiの処理をまとめる方法は、以前書いたこちらの記事を参考に進めます。
ファイルの作成
処理をまとめる用のファイルを/config
に作成します。
ファイル名の指定はないので、functions.js
としておきます。
ファイルの編集
コントローラーファイルに実装していた処理をこちらのファイルに移動します。find
findOne
はコントローラーファイルから呼び出す必要があるのでエクスポートします。
module.exports = {
convertToV3Data: () => ({
async find(ctx) {
ctx.query = { ...ctx.query, populate: "*" };
const { data } = await super.find(ctx);
return toV3Data(data, ctx.query);
},
async findOne(ctx) {
if (ctx.request.url.includes("/count")) {
const {
meta: {
pagination: { total },
},
} = await super.find(ctx);
return total;
}
ctx.query = { ...ctx.query, populate: "*" };
const { data } = await super.findOne(ctx);
return toV3Data(data, ctx.query);
},
}),
};
const toV3Data = (data, query) => {
...
};
コントローラーから呼び出す
最後にコントローラーから/config/function.js
に定義した関数を呼び出せば完了です。
"use strict";
const { createCoreController } = require("@strapi/strapi").factories;
module.exports = createCoreController("api::xxx.xxx", strapi.config.functions.convertToV3Data);
まとめ
Strapi v4のAPIを扱いやすくする方法を紹介しました。
バックエンド側に手を加えることで、フロント側はシンプルに実装することができるのでおすすめです。