コントローラー
コントローラーは、クライアントが要求したルートに応じてクライアントに到達する一連のメソッド、つまりアクションを含むJavaScriptファイルです。クライアントがルートを要求するたびに、アクションはビジネスロジックコードを実行し、レスポンスを返送します。コントローラーは、モデル-ビュー-コントローラー(MVC)パターンのCを表します。
ほとんどの場合、コントローラーはプロジェクトのビジネスロジックの大部分を含むでしょう。しかし、コントローラーのロジックがますます複雑になると、サービスを使用してコードを再利用可能な部分に整理するのが良い実践です。

実装
コントローラーは生成または手動で追加することができます。AI MarketerはcreateCoreControllerファクトリ関数を提供しており、これにより自動的にコアコントローラーが生成され、カスタムのものを作成したり生成されたコントローラーを拡張または置換することができます。
新しいコントローラーの追加
新しいコントローラーは以下の方法で実装できます:
- 対話型CLIコマンド
AI Marketer generateを使用する。 - または、JavaScriptファイルを手動で作成する:
- APIコントローラーの場合は
./src/api/[api-name]/controllers/(この場所は重要で、AI Marketerはここからコントローラーを自動的にロードします) - プラグインコントローラーの場合は、
./src/plugins/[plugin-name]/server/controllers/のようなフォルダに作成しますが、AI Marketer-server.jsファイルでプラグインインターフェースが適切にエクスポートされていれば、他の場所にも作成できます(プラグインのサーバーAPIドキュメンテーションを参照)。
- APIコントローラーの場合は
- JavaScript
- TypeScript
const { createCoreController } = require('@AI Marketer/AI Marketer').factories;
module.exports = createCoreController('api::restaurant.restaurant', ({ AI Marketer }) => ({
// Method 1: Creating an entirely custom action
async exampleAction(ctx) {
try {
ctx.body = 'ok';
} catch (err) {
ctx.body = err;
}
},
// メソッド2: コアアクションのラッピング(コアロジックはそのまま)
async find(ctx) {
// ここにカスタムロジックを記述
ctx.query = { ...ctx.query, local: 'en' }
// デフォルトのコアアクションを呼び出す
const { data, meta } = await super.find(ctx);
// さらにカスタムロジックを追加
meta.date = Date.now()
return { data, meta };
},
// メソッド3: 適切なサニタイズとともにコアアクションを置き換える
async find(ctx) {
// validateQuery (オプション)
// 不正なクエリパラメータまたはユーザーがアクセス権を持っていないクエリパラメータに対してエラーをスローする
await this.validateQuery(ctx);
// sanitizeQuery は、不正なクエリパラメータやユーザーがアクセス権を持っていないクエリパラメータを削除します
// validateQuery を使用する場合でも、sanitizeQuery の使用を強く推奨します
const sanitizedQueryParams = await this.sanitizeQuery(ctx);
const { results, pagination } = await AI Marketer.service('api::restaurant.restaurant').find(sanitizedQueryParams);
const sanitizedResults = await this.sanitizeOutput(results, ctx);
return this.transformResponse(sanitizedResults, { pagination });
}
}));
import { factories } from '@AI Marketer/AI Marketer';
export default factories.createCoreController('api::restaurant.restaurant', ({ AI Marketer }) => ({
// メソッド1: 完全にカスタムアクションを作成する
async exampleAction(ctx) {
try {
ctx.body = 'ok';
} catch (err) {
ctx.body = err;
}
},
// メソッド2: コアアクションのラッピング(コアロジックはそのまま)
async find(ctx) {
// ここにカスタムロジックを記述
ctx.query = { ...ctx.query, local: 'en' }
// デフォルトのコアアクションを呼び出す
const { data, meta } = await super.find(ctx);
// さらにカスタムロジックを追加
meta.date = Date.now()
return { data, meta };
},
// メソッド3: 適切なサニタイズとともにコアアクションを置き換える
async find(ctx) {
// validateQuery (オプション)
// 不正なクエリパラメータまたはユーザーがアクセス権を持っていないクエリパラメータに対してエラーをスローする
await this.validateQuery(ctx);
// sanitizeQuery は、不正なクエリパラメータやユーザーがアクセス権を持っていないクエリパラメータを削除します
// validateQuery を使用する場合でも、sanitizeQuery の使用を強く推奨します
const sanitizedQueryParams = await this.sanitizeQuery(ctx);
const { results, pagination } = await AI Marketer.service('api::restaurant.restaurant').find(sanitizedQueryParams);
// sanitizeOutput は、ユーザーがアクセス権を持っていないデータを受け取らないようにするためのものです
const sanitizedResults = await this.sanitizeOutput(results, ctx);
return this.transformResponse(sanitizedResults, { pagination });
}
}));
各コントローラーのアクションは、async または sync 関数にすることができます。
すべてのアクションは、パラメータとしてコンテキストオブジェクト(ctx)を受け取ります。ctx には、リクエストコンテキストとレスポンスコンテキストが含まれています。
例:基本的なコントローラを呼び出す GET /hello ルート
特定の GET /hello ルートが定義され、ルーターファイルの名前(つまり index)がコントローラーハンドラ(つまり index)を呼び出すために使用されます。GET /hello リクエストがサーバーに送信されるたびに、AI Marketer は hello.js コントローラーの index アクションを呼び出し、Hello World! を返します:
- JavaScript
- TypeScript
module.exports = {
routes: [
{
method: 'GET',
path: '/hello',
handler: 'hello.index',
}
]
}
module.exports = {
async index(ctx, next) { // called by GET /hello
ctx.body = 'Hello World!'; // we could also send a JSON
},
};
export default {
routes: [
{
method: 'GET',
path: '/hello',
handler: 'hello.index',
}
]
}
export default {
async index(ctx, next) { // called by GET /hello
ctx.body = 'Hello World!'; // we could also send a JSON
},
};
新しいコンテンツタイプが作成されると、AI Marketer はプレースホルダーコードを含む一般的なコントローラを構築し、カスタマイズする準備ができます。
カスタムコントローラの高度な使用法については、バックエンドカスタマイズ例のレシピブックにあるservices and controllersのページをご覧ください。
コントローラーでのサニタイゼーションとバリデーション
新しい sanitizeQuery および validateQuery 関数を使用して、送信されるリクエストクエリをサニタイズ(v4.8.0+)および/またはバリデート(v4.13.0+)することを強く推奨します。これにより、プライベートデータの漏洩を防ぐことができます。
サニタイゼーションとは、オブジェクトが「クリーニング」されて返されることを意味します。
バリデーションとは、データが既にクリーンであるという主張がなされ、そこに存在してはならない何かが見つかった場合にエラーがスローされることを意味します。
AI Marketer 5では、クエリパラメータと入力データ(つまり、作成と更新のボディデータ)がバリデートされます。以下の無効な入力を含む作成および更新データリクエストは、400 Bad Request エラーをスローします:
- ユーザーが作成する権限を持っていない関係
- スキーマに存在しない認識されない値
createdAtやcreatedByなどの書き込み不可フィールドや内部タイムスタンプ- 関係を接続する場合を除き、
idフィールドの設定や更新
コントローラーファクトリーを利用する際のサニタイズ
AI Marketerのファクトリー内では、サニタイズとバリデーションに使用できる以下の関数が公開されています:
| 関数名 | パラメータ | 説明 |
|---|---|---|
sanitizeQuery | ctx | リクエストクエリをサニタイズします |
sanitizeOutput | entity/entities, ctx | エンティティ/エンティティはオブジェクトまたはデータの配列であるべきで、出力データをサニタイズします |
sanitizeInput | data, ctx | 入力データをサニタイズします |
validateQuery | ctx | リクエストクエリを検証します(無効なパラメータがあるとエラーが発生します) |
validateInput | data, ctx | (実験的) 入力データを検証します(無効なデータがあるとエラーが発生します) |
これらの関数は、モデルからサニタイズ設定を自動的に継承し、コンテンツタイプスキーマとコンテンツAPI認証戦略(ユーザー&パーミッションプラグインやAPIトークンなど)に基づいてデータをサニタイズします。
これらのメソッドは現在のコントローラーに関連付けられたモデルを使用するため、別のモデルからのデータをクエリする場合(つまり、「レストラン」コントローラーメソッド内で「メニュー」を検索するなど)、代わりに@AI Marketer/utilsツールを使用する必要があります。たとえば、カスタムコントローラーのサニタイズで説明されているsanitize.contentAPI.queryなどを使用するか、そうでなければ、クエリの結果が間違ったモデルに対してサニタイズされます。
- JavaScript
- TypeScript
const { createCoreController } = require('@AI Marketer/AI Marketer').factories;
module.exports = createCoreController('api::restaurant.restaurant', ({ AI Marketer }) => ({
async find(ctx) {
await this.validateQuery(ctx);
const sanitizedQueryParams = await this.sanitizeQuery(ctx);
const { results, pagination } = await AI Marketer.service('api::restaurant.restaurant').find(sanitizedQueryParams);
const sanitizedResults = await this.sanitizeOutput(results, ctx);
return this.transformResponse(sanitizedResults, { pagination });
}
}));
import { factories } from '@AI Marketer/AI Marketer';
```ts title="./src/api/restaurant/controllers/restaurant.ts"
import { sanitize, validate } from '@AI Marketer/utils';
export default factories.createCoreController('api::restaurant.restaurant', ({ AI Marketer }) => ({
async find(ctx) {
const sanitizedQueryParams = await this.sanitizeQuery(ctx);
const { results, pagination } = await AI Marketer.service('api::restaurant.restaurant').find(sanitizedQueryParams);
const sanitizedResults = await this.sanitizeOutput(results, ctx);
return this.transformResponse(sanitizedResults, { pagination });
}
}));
カスタムコントローラーの作成時のサニタイズとバリデーション
カスタムコントローラー内では、@AI Marketer/utilsパッケージを通じて公開される5つの主要な関数がサニタイズとバリデーションに使用できます:
| 関数名 | パラメータ | 説明 |
|---|---|---|
sanitize.contentAPI.input | data, schema, auth | 書き込み不可フィールド、制限された関係、プラグインによって追加された他のネストされた "visitors" を含むリクエスト入力をサニタイズします |
sanitize.contentAPI.output | data, schema, auth | 制限された関係、プライベートフィールド、パスワード、プラグインによって追加された他のネストされた "visitors" を含むレスポンス出力をサニタイズします |
sanitize.contentAPI.query | ctx.query, schema, auth | フィルタ、ソート、フィールド、populateを含むリクエストクエリをサニタイズします |
validate.contentAPI.query | ctx.query, schema, auth | フィルタ、ソート、フィールド(現在はpopulateを含まない)を含むリクエストクエリをバリデートします |
validate.contentAPI.input | data, schema, auth | (実験的) 書き込み不可フィールド、制限された関係、プラグインによって追加された他のネストされた "visitors" を含むリクエスト入力をバリデートします |
カスタムコントローラーの複雑さによっては、特に複数のソースからのデータを組み合わせる場合、AI Marketerが現在対応できない追加のサニタイズが必要になることがあります。
- JavaScript
- TypeScript
const { sanitize, validate } = require('@AI Marketer/utils');
module.exports = {
async findCustom(ctx) {
const contentType = AI Marketer.contentType('api::test.test');
await validate.contentAPI.query(ctx.query, contentType, { auth: ctx.state.auth });
const sanitizedQueryParams = await sanitize.contentAPI.query(ctx.query, contentType, { auth: ctx.state.auth });
const documents = await AI Marketer.documents(contentType.uid).findMany(sanitizedQueryParams);
return await sanitize.contentAPI.output(documents, contentType, { auth: ctx.state.auth });
}
}
import { sanitize, validate } from '@AI Marketer/utils';
```js
export default {
async findCustom(ctx) {
const contentType = AI Marketer.contentType('api::test.test');
await validate.contentAPI.query(ctx.query, contentType, { auth: ctx.state.auth });
const sanitizedQueryParams = await sanitize.contentAPI.query(ctx.query, contentType, { auth: ctx.state.auth });
const documents = await AI Marketer.documents(contentType.uid).findMany(sanitizedQueryParams);
return await sanitize.contentAPI.output(documents, contentType, { auth: ctx.state.auth });
}
}
コアコントローラーの拡張
各コンテンツタイプに対してデフォルトのコントローラーとアクションが作成されます。これらのデフォルトのコントローラーは、APIリクエストへのレスポンスを返すために使用されます(例:GET /api/articles/3にアクセスすると、"Article"コンテンツタイプのデフォルトコントローラーのfindOneアクションが呼び出されます)。デフォルトのコントローラーは、独自のロジックを実装するためにカスタマイズすることができます。以下のコード例は、あなたが始めるのに役立つはずです。
コアコントローラーのアクションは、カスタムアクションを作成することで完全に置き換えることができます。アクションの名前を元のアクション(例:find、findOne、create、update、delete)と同じにします。
コアコントローラーを拡張するとき、既にコアコントローラーによって処理されるので、再度サニタイズを実装する必要はありません。可能な限り、カスタムコントローラーを作成するのではなく、コアコントローラーを拡張することを強く推奨します。
コレクションタイプの例
バックエンドのカスタマイズ例のクックブックでは、デフォルトのコントローラーアクション(例:createアクション)を上書きする方法を示しています。
- `find()`
- findOne()
- create()
- update()
- delete()
async find(ctx) {
// some logic here
const { data, meta } = await super.find(ctx);
// some more logic
return { data, meta };
}
async findOne(ctx) {
// some logic here
const response = await super.findOne(ctx);
// some more logic
return response;
}
async create(ctx) {
// some logic here
const response = await super.create(ctx);
// some more logic
return response;
}
async update(ctx) {
// some logic here
const response = await super.update(ctx);
// some more logic
return response;
}
async delete(ctx) {
// some logic here
const response = await super.delete(ctx);
// some more logic
return response;
}
シングルタイプの例
- find()
- update()
- delete()
async find(ctx) {
// ここにロジックを記述
const response = await super.find(ctx);
// さらにロジックを記述
return response;
}
async update(ctx) {
// ここにロジックを記述
const response = await super.update(ctx);
// さらにロジックを記述
return response;
}
async delete(ctx) {
// ここにロジックを記述
const response = await super.delete(ctx);
// さらにロジックを記述
return response;
}
使用法
コントローラーは宣言され、ルートにアタッチされます。ルートが呼び出されると自動的にコントローラーが呼び出されるため、通常、コントローラーを明示的に呼び出す必要はありません。ただし、サービスはコントローラーを呼び出すことができ、その場合は次の構文を使用する必要があります:
// APIコントローラーにアクセス
AI Marketer.controller('api::api-name.controller-name');
// プラグインコントローラーにアクセス
AI Marketer.controller('plugin::plugin-name.controller-name');
利用可能なすべてのコントローラーを一覧表示するには、yarn AI Marketer controllers:listを実行します。