【Strapi】バックエンドをカスタマイズしてUnique Fieldの値でエントリを取得する方法

# はじめに

Strapiは新規にエントリを作成すると、連番のIDが自動で付与されます。
REST APIではこのIDを使って特定のエントリを取得できますが、連番のIDではなく、一意のフィールドを使って検索をしたいような場面がありました。

今回はバックエンドをカスタマイズして、Unique Filedの値でエントリを取得する方法を紹介します。

なお、Strapiに関しては過去に軽く解説をした記事を投稿したので、こちらを参照してください。

https://qiita.com/nakamura0907/items/a8f564add089e0d60438

## 環境

- TypeScript
- Strapi: 4.19.1

# この記事を読むとできるようになること

- バックエンドをカスタマイズできる
- **Unique Fieldの値でエントリを取得できる**

# Unique Fieldの値で検索したい例

今回は、YYYY年MM月DD日の日記を検索したい、という例をモデルにします。
以下は日記Collection Typeのサンプルです。

| フィールド | 属性 |
|:-:|:-:|
| date | date, required, unique  |
|  body | text  |
| 以下略  |   |

以下のような検索結果になることを目指します。

```bash
$ curl <baseUrl>/api/diaries/YYYY-MM-DD \
 -H "Authorization:Bearer <token>
 
 {"data":{"id":1,"attributes":{"date":"2024-01-01","body":"今日はいい天気でした","createdAt":"2024-02-09T01:04:08.931Z","updatedAt":"2024-02-09T01:04:08.931Z"}},"meta":{}}
```

# デフォルトのREST APIを使う場合

デフォルトのREST APIを使う場合はどのような結果が得られるのか確認してみましょう。

## Get entries

Get entriesはクエリフィルタに一致するエントリの一覧を返します。
こちらを使う場合はクエリパラメータに`filters[field][operator]=value`と指定することで特定の日付を検索できます。ただし配列形式で取得されてしまうことに注意しましょう。

```bash
$ curl "<baseUrl>/api/diaries?filters\[date\]\[$eq\]=2024-01-01" \
 -H Authorization: Bearer <token>
 
{"data":[{"id":1,"attributes":{"date":"2024-01-01","body":"今日はいい天気でした","createdAt":"2024-02-09T01:04:08.931Z","updatedAt":"2024-02-09T01:04:08.931Z"}}],"meta":{"pagination":{"page":1,"pageSize":25,"pageCount":1,"total":1}}}
```

Get entriesの詳細については、[こちら](https://docs.strapi.io/dev-docs/api/rest#get-entries)を確認してください。
クエリパラメータについても同様にリンク先で確認できます。

## Get an entry

Get an entryは`id`を元にエントリを検索します。
今回の場合、idは自動で付与される連番になっているため日付で検索するとエラーが返ります。

```bash
$ curl <baseUrl>/api/diaries/2024-01-01 \
 -H Authorization: Bearer <token>
 
{"data":null,"error":{"status":404,"name":"NotFoundError","message":"Not Found","details":{}}}
```

Get an entryのレスポンス例については[こちら](https://docs.strapi.io/dev-docs/api/rest#get-an-entry)から確認してください。

# バックエンドをカスタマイズする

デフォルトの挙動では日付検索が不可能だと確認できたため、Strapiのバックエンドをカスタマイズしていきたいと思います。

Strapi v4のドキュメントには丁度、[Back-end customization](https://docs.strapi.io/dev-docs/backend-customization)という章が用意されています。

上記ページに掲載されている図と照らし合わせると、今回の場合はコントローラーを編集すれば日付で検索できるようになりそうです。

https://docs.strapi.io/dev-docs/backend-customization/controllers

# Unique Fieldの値で検索する

早速、コントローラーを編集して日付で検索できるようにしてみましょう。

編集対象のファイルは`./src/api/[api-name]/controllers/`にあります。今回の場合は`./src/api/diary/controllers/diary.ts`です。
JavaScriptの場合はモジュール部分など適宜書き変えてください。

```ts:./src/api/diary/controllers/diary.ts
/**
 * diary controller
 */

import { factories } from '@strapi/strapi'

export default factories.createCoreController('api::diary.diary', ({ strapi}) => ({
    async findOne(ctx) {
        await this.validateQuery(ctx);
        const sanitizedQueryParams = await this.sanitizeQuery(ctx);

        const entry = await strapi.db.query('api::diary.diary').findOne({
            where: {
                date: ctx.params.id
            },
            select: sanitizedQueryParams.fields
        });

        if (!entry) {
            return ctx.notFound();
        }

        const sanitized = await this.sanitizeOutput(entry, ctx);
        return this.transformResponse(sanitized);
    }
}));
```

## サニタイズとバリデーション

処理の最初とレスポンス前でサニタイズとバリデーションを行っています。
以下の部分です。

```ts
await this.validateQuery(ctx);
const sanitizedQueryParams = await this.sanitizeQuery(ctx);

// ...

const sanitized = await this.sanitizeOutput(entry, ctx);
```

これらは各種設定に基づいてデータのサニタイズを行っています。
関数の概要など詳細については、[こちら](https://docs.strapi.io/dev-docs/backend-customization/controllers#sanitization-and-validation-in-controllers)から確認できます。

## Query Engine API

実際の検索は[Query Engine API](https://docs.strapi.io/dev-docs/api/query-engine)を使っています。
コードでは以下の部分です。

```ts
const entry = await strapi.db.query('api::diary.diary').findOne({
    where: {
        date: ctx.params.id
    },
    select: sanitizedQueryParams.fields
});
```

Query Engine APIはStrapiが提供しているAPIの一つで、より低いレベルでデータベース・レイヤーと対話できます。

現時点（2024年2月現在）では、Unique Field検索はこのQuery Engine APIを使うほかないようです。
しかしほとんどのケースでは、[Entity Service API](https://docs.strapi.io/dev-docs/api/entity-service/)が推奨されています。

> 👉 In most use cases, it's recommended to use the Entity Service API instead of the Query Engine API.
>
> https://docs.strapi.io/dev-docs/api/query-engine

# 最終確認

編集後、Get an entryのエンドポイントをもう一度叩いてみます。

```bash
$ curl <baseUrl>/api/diaries/2024-01-01 \
 -H Authorization: Bearer <token>

{"data":{"id":1,"attributes":{"date":"2024-01-01","body":"今日はいい天気でした","createdAt":"2024-02-09T01:04:08.931Z","updatedAt":"2024-02-09T01:04:08.931Z"}},"meta":{}}
```

無事、期待通りの検索結果を受け取ることができました。

# まとめ

今回はStrapiのバックエンドをカスタマイズして、Unique Fieldの値でエントリを取得する方法を紹介しました。