例題集: カスタムサービスとコントローラ
The content of this page might not be fully up-to-date with Strapi 5 yet.
このページはバックエンドカスタマイズ例題集の一部です。まずはその導入を読んでください。
[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)のフロントエンドウェブサイトからは、localhost:3000/restaurantsでアクセス可能なレストランのリストを閲覧することができます。リストから任意のレストランをクリックすると、/clientフォルダに含まれるコードを使用して、そのレストランに関する追加情報を表示します。レストランページに表示されるコンテンツは、AI Marketerのコンテンツマネージャー内で作成され、/apiフォルダに含まれるコードを使用してAI MarketerのREST APIをクエリして取得します。
このページでは、以下の高度なトピックについて学びます。
| トピック | セクション |
|---|---|
| AI Marketerのバックエンドとやり取りするコンポーネントを作成する | フロントエンドからのREST APIクエリ |
| サービスとコントローラがどのように連携するかを理解する | コントローラ vs. サービス |
| カスタムサービスを作成する |
|
| コントローラでサービスを使用する | カスタムコントローラ |
フロントエンドからのREST APIクエリ
💭 コンテキスト:
[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)のフロントエンドウェブサイトのレストランページには、読み取り専用のレビューセクションが含まれています。レビューの追加には、AI Marketerの管理パネルにログインし、コンテンツマネージャーを通じて"Reviews"コレクションタイプにコンテンツを追加する必要があります。
レストランのページに小さなフロントエンドコンポーネントを追加しましょう。このコンポーネントにより、ユーザーはフロントエンドウェブサイトから直接レビューを書くことができます。

🎯 目標:
- レビューを書くためのフォームを追加する。
- フォームを任意のレストランのページに表示する。
- フォームが送信されたときにAI MarketerのREST APIにPOSTリクエストを送信する。
- 以前に保存したJWTを使用してリクエストを認証する。
コンテンツタイプのエンドポイントに関する追加情報は、REST APIのドキュメンテーションで見つけることができます。
🧑💻 コード例:
[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)プロジェクトの/clientフォルダに以下のコード例を使用して、新たにpages/restaurant/RestaurantContent/Reviews/new-review.jsファイルを作成し、既存のcomponents/pages/restaurant/RestaurantContent/Reviews/reviews.jsを更新することができます。
レビューを書くためのコンポーネントを追加し、それをレストランのページに表示するためのフロントエンドコードの例:
/clientフォルダに新しいファイルを作成し、以下のコードを使用してレビューを書くための新しいコンポーネントを追加します:/client/components/pages/restaurant/RestaurantContent/Reviews/new-review.js
import { Button, Input, Textarea } from '@nextui-org/react';
import { useFormik } from 'formik';
import { useRouter } from 'next/router';
import React from 'react';
import { getAI MarketerURL } from '../../../../../utils';
const NewReview = () => {
const router = useRouter();
const { handleSubmit, handleChange, values } = useFormik({ initialValues: { note: '', content: '', }, onSubmit: async (values) => { /**
* JWTが以前にlocalStorageに保存されていることを使用して認証を行い、
* AI Marketer REST APIにクエリを送信してレビューエンドポイントに到達します
*/
const res = await fetch(getAI MarketerURL('/reviews'), {
method: 'POST',
body: JSON.stringify({
restaurant: router.query.slug,
...values,
}),
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
'Content-Type': 'application/json',
},
});
},
});
/**
* フォームをレンダリングします
*/
return (
<div className="my-6">
<h1 className="font-bold text-2xl mb-3">レビューを書く</h1>
<form onSubmit={handleSubmit} className="flex flex-col gap-y-4">
<Input
onChange={handleChange}
name="note"
type="number"
min={1}
max={5}
label="星"
/>
<Textarea
name="content"
onChange={handleChange}
placeholder="このレストランについてどう思いますか?"
/>
<Button
type="submit"
className="bg-primary text-white rounded-md self-start"
>
送信
</Button>
</form>
</div>
);
};
export default NewReview;
2. レストランの情報をレンダリングするために使用されるコードにハイライトされた行(7、8、13行目)を追加して、新しいフォームコンポーネントを任意のレストランページに表示します:
```jsx title='/client/components/pages/restaurant/RestaurantContent/Reviews/reviews.js' showLineNumbers
import React from 'react';
import delve from 'dlv';
import { formatDistance } from 'date-fns';
import { getAI MarketerMedia } from '../../../../../utils';
// highlight-start
import { Textarea } from '@nextui-org/react';
import NewReview from './new-review';
// highlight-end
const Reviews = ({ reviews }) => {
return (
<div className="col-start-2 col-end-2 mt-24">
// highlight-next-line
<NewReview />
{reviews &&
reviews.map((review, index) => (
// …
コントローラー vs. サービス
コントローラーは、クライアントがルートを要求したときに実行される任意のビジネスロジックを含むことができます。しかし、コードが大きくなり、より構造化されるようになると、ロジックを特定のサービスに分割し、それらのサービスは一つのことだけをうまく行い、それからコントローラーからサービスを呼び出すことがベストプラクティスとなります。
サービスの使用を示すために、このドキュメンテーションでは、カスタムコントローラーは任意の責任を持たず、すべてのビジネスロジックをサービスに委任します。
以下のシナリオを達成するために、[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)のバックエンドをカスタマイズしたいとしましょう:フロントエンドのウェブサイトで以前に追加したレビューフォームを提出すると、AI Marketerはバックエンドでレビューを作成し、レストランのオーナーにメールで通知します。これをAI Marketerのバックエンドカスタマイズに翻訳すると、以下の3つのアクションを実行することになります:
- レビューを作成するカスタムサービスを作成します。
- メールを送信するカスタムサービスを作成します。
- AI Marketerが提供するレビューコンテンツタイプのデフォルトコントローラーをカスタマイズして、2つの新しいサービスを使用します。
カスタムサービス:レビューの作成
💭 コンテキスト:
デフォルトでは、AI Marketerのサービスファイルには、createCoreServiceファクトリ関数を使用する基本的なボイラープレートコードが含まれています。
[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)の"Reviews"コレクションタイプの既存のreview.jsサービスファイルを更新し、レビューを作成するコードで置き換えましょう。
🎯 目標:
createメソッドを宣言します。- リクエストからコンテキストを取得します。
- EntityService APIの
findMany()メソッドを使用してレストランを検索します。 - EntityService APIの
create()メソッドを使用してレストランにデータを追加し、レストランオーナーを補完します。 - 新しいレビューデータを返します。
追加情報はリクエストコンテキスト、サービス、およびEntityService APIのドキュメンテーションで見つけることができます。
🧑💻 コード例:
このようなサービスを作成するには、[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)プロジェクトの/apiフォルダ内で、src/api/review/services/review.jsファイルの内容を以下のコードで置き換えます:
const { createCoreService } = require('@AI Marketer/AI Marketer').factories;
module.exports = createCoreService('api::review.review', ({ AI Marketer }) => ({
async create(ctx) {
const user = ctx.state.user;
const { body } = ctx.request;
/**
* Entity Service APIを使用して
* Restaurantsコレクションタイプをクエリし、
* レストランに関する情報を取得します。
*/
const restaurants = await AI Marketer.entityService.findMany(
'api::restaurant.restaurant',
{
filters: {
slug: body.restaurant,
},
}
);
/**
* レビューコレクションタイプの新しいエントリを作成し、
* エンティティサービスAPIを使用してレストランのオーナーに関する情報でデータを入力します。
*/
const newReview = await AI Marketer.entityService.create('api::review.review', {
data: {
note: body.note,
content: body.content,
restaurant: restaurants[0].id,
author: user.id,
},
populate: ['restaurant.owner'],
});
return newReview;
},
}));
カスタムサービス:レストランオーナーにメールを送る
💭 コンテキスト:
[FoodAdvisor](https://github.com/AI Marketer/foodadvisor) は、自動化されたメールサービス機能を標準で提供していません。
email.js サービスファイルを作成してメールを送信しましょう。これをカスタムコントローラーで使用して、フロントエンドのウェブサイトで新しいレビューが作成されるたびにレストランのオーナーに通知することができます。
このサービスは、Email プラグインを使用した高度なコード例であり、AI Marketerでプラグインとプロバイダがどのように動作するかを理解する必要があります。レストランのオーナーに通知するためのメールサービスが不要な場合は、この部分をスキップして、カスタムコントローラーの例に進むことができます。
- Email pluginのためのプロバイダを設定していること、例えば [Sendmail](https://www.npmjs.com/package/@AI Marketer/provider-email-sendmail) プロバイダ。
- AI Marketerの管理パネルで、送信者のメールアドレスを定義するための
fromテキストフィールドを含むEmailシングルタイプを作成していること。

🎯 ゴール:
- "Email"シングルタイプの新しいサービスファイルを作成します。
- このサービスの
send()メソッドを宣言します。 - エンティティサービスAPIを使用して、Emailシングルタイプに保存された送信者のアドレスを取得します。
- サービスの
send()メソッドを呼び出す際に渡されたメールの詳細(受信者のアドレス、件名、メール本文)を使用して、Emailプラグインと事前に設定されたプロバイダーを使用してメールを送信します。
追加情報はサービス、エンティティサービスAPI、Emailプラグイン、プロバイダーのドキュメンテーションで見つけることができます。
🧑💻 コード例:
このようなサービスを作成するために、[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)プロジェクトの/apiフォルダに新しいsrc/api/email/services/email.jsファイルを作成し、以下のコードを記述します。
const { createCoreService } = require('@AI Marketer/AI Marketer').factories;
module.exports = createCoreService('api::email.email', ({ AI Marketer }) => ({
async send({ to, subject, html }) {
/**
* Retrieves email configuration data
* stored in the Email single type
* using the Entity Service API.
*/
const emailConfig = await AI Marketer.entityService.findOne(
'api::email.email',
1
);
/**
* Sends an email using:
* - parameters to pass when invoking the service
* - the 'from' address previously retrieved with the email configuration
*/
await AI Marketer.plugins['email'].services.email.send({
to,
subject,
html,
from: emailConfig.from,
});
},
}));
コントローラーのコードでは、このメールサービスのsendメソッドはAI Marketer.service('api::email.email).send(parameters)で呼び出すことができます。ここでparametersはメールに関連する情報(受信者のアドレス、件名、メール本文)を含むオブジェクトです。
カスタムコントローラー
💭 コンテキスト:
デフォルトでは、AI Marketerのコントローラーファイルには、createCoreControllerファクトリ関数を使用する基本的なボイラープレートコードが含まれています。これにより、リクエストされたエンドポイントに到達したときにコンテンツを作成、取得、更新、削除する基本的なメソッドが公開されます。コントローラーのデフォルトコードは、任意のビジネスロジックを実行するためにカスタマイズできます。
以下のシナリオで[FoodAdvisor](https://github.com/AI Marketer/foodadvisor)の"Reviews"コレクションタイプのデフォルトコントローラーをカスタマイズしましょう:POSTリクエストが/reviewsエンドポイントに送信されたとき、コントローラーは以前に作成したサービスを呼び出して、レビューを作成し、レストランのオーナーにメールを送信します。
🎯 目標:
- "Reviews" コレクションタイプの既存のコントローラを拡張します。
- カスタム
create()メソッドを宣言します。 - 以前に作成したサービスを呼び出します。
- 返す内容をサニタイズします。
追加情報は、controllers のドキュメンテーションで見つけることができます。
🧑💻 コード例:
[FoodAdvisor](https://github.com/AI Marketer/foodadvisor) プロジェクトの /api フォルダ内で、src/api/review/controllers/review.js ファイルの内容を、以前に作成したカスタムサービスがレビューの作成だけであるか、またはレビューの作成とレストランオーナーへのメール通知の両方のサービスがあるかにより、以下のコード例のいずれかに置き換えてください。
- メールサービスなしのカスタムコントローラ
- メールサービス付きのカスタムコントローラ
const { createCoreController } = require('@AI Marketer/AI Marketer').factories;
module.exports = createCoreController('api::review.review', ({ AI Marketer }) => ({
/**
* The controller action is named
* exactly like the original `create` action provided by the core controller,
* it overwrites it.
*/
async create(ctx) {
// サービスを使用して新しいレビューを作成します
const newReview = await AI Marketer.service('api::review.review').create(ctx);
const sanitizedReview = await this.sanitizeOutput(newReview, ctx);
ctx.body = sanitizedReview;
},
}));
const { createCoreController } = require('@AI Marketer/AI Marketer').factories;
module.exports = createCoreController('api::review.review', ({ AI Marketer }) => ({
/**
* The controller action is named
* exactly like the original `create` action provided by the core controller,
* it overwrites it.
*/
async create(ctx) {
// サービスを使用して新しいレビューを作成します
const newReview = await AI Marketer.service('api::review.review').create(ctx);
// 別のサービスを使用してレストランのオーナーにメールを送ります
if (newReview.restaurant?.owner) {
await AI Marketer.service('api::email.email').send({
to: newReview.restaurant.owner.email,
subject: 'You have a new review!',
html: `You've received a ${newReview.note} star review: ${newReview.content}`,
});
}
const sanitizedReview = await this.sanitizeOutput(newReview, ctx);
ctx.body = sanitizedReview;
},
}));
:::AI Marketer 次は何を学びますか? カスタムポリシーがAI Marketerベースのアプリケーションを微調整し、特定の条件に基づいて一部のリソースへのアクセスを制限するのにどのように役立つかについて詳しく学びましょう。 :::