Strapi パターン は何をしますか?
クイックガイド: StrapiをオープンソースのヘッドレスCMSとして使用し、コンテンツタイプスキーマから自動生成されるREST/GraphQL APIを活用します。v5では、バックエンドのデータアクセスに、非推奨のEntity Service APIではなくDocument Service API(
strapi.documents())を使用します。REST APIの応答はフラット形式(data.fieldName、data.attributes.fieldNameではありません)です。リレーションとメディアはデフォルトでは取得されません — 常にpopulateを渡してください。複雑なクエリ文字列を構築するにはqsを使用します。コンテンツタイプはデフォルトで非公開です。権限は「ユーザー権限」プラグインまたはAPIトークンで設定してください。
<critical_requirements>
クリティカル: このスキルを使用する前に
すべてのコードは、CLAUDE.mdのプロジェクト規約に従う必要があります(ケバブケース、名前付きエクスポート、インポート順序、
import type、名前付き定数)
(Strapi v5のすべてのバックエンドデータアクセスには、strapi.documents('api::content-type.content-type')(Document Service API)を必ず使用してください — Entity Service APIは削除されました)
(リレーション、メディア、コンポーネント、ダイナミックゾーンが必要な場合は、常に populate を渡す必要があります — Strapiはデフォルトでリレーションを返しません)
(複雑なREST APIクエリ文字列をフィルター、populate、ソートとともに構築するには、qsライブラリを必ず使用してください — 手動での文字列構築はネストされたパラメータで失敗します)
(カスタムコントローラーでは、this.sanitizeQuery(ctx)、this.sanitizeOutput()、this.validateQuery(ctx)を使用して、入力と出力の両方をサニタイズおよびバリデーションする必要があります)
(すべてのコンテンツタイプエンドポイントの権限は、管理パネルまたは設定を介して設定する必要があります — すべてのルートはデフォルトで非公開です)
</critical_requirements>
自動検出: Strapi, strapi, @strapi/strapi, createCoreController, createCoreService, createCoreRouter, Document Service, strapi.documents, content-type, schema.json, api::, plugin::, lifecycle hooks, Users & Permissions, /api/auth/local, populate, qs.stringify
使用すべき場合:
- StrapiをヘッドレスCMSとして使用し、コンテンツ管理アプリケーションを構築する場合
- フィールド、リレーション、コンポーネント、ダイナミックゾーンを含むコンテンツタイプスキーマ(
schema.json)を定義する場合 - フィルター、populate、ソート、ページネーションを使用してREST APIをクエリする場合
- カスタムコントローラー、サービス、ルート、ポリシー、またはミドルウェアを作成する場合
- 下書き/公開ワークフローでバックエンドCRUDにDocument Service APIを使用する場合
- 「ユーザー権限」プラグインでJWT認証を実装する場合
- コンテンツタイプにライフサイクルフックを追加して副作用を発生させる場合
- コンテンツスキーマのTypeScriptタイプを生成する場合
カバーされる主要パターン:
- コンテンツタイプスキーマ定義(
schema.json) qsを使用したREST APIクエリ(フィルター、populate、ソート、ページネーション)- Document Service API(
findMany、findOne、create、update、delete、publish、unpublish) - カスタムコントローラー、サービス、ルート、ポリシー、ミドルウェア
- ライフサイクルフック(
beforeCreate、afterUpdateなど) - JWT認証(登録、ログイン、認証済みリクエスト)
- TypeScriptタイプ生成
使用すべきでない場合:
- Strapi以外のCMSプラットフォーム(使用しているCMS専用のスキルを使用してください)
- StrapiのAPI層をバイパスした直接データベースクエリ(StrapiのDocument Serviceを使用してください)
- 生SQLが必要な複雑なトランザクションロジック(Strapiはデータベースを抽象化します)
詳細リソース:
- 意思決定フレームワークとクイックリファレンステーブルについては、reference.mdを参照してください
コア&REST API:
- examples/core.md — コンテンツタイプスキーマ、REST APIクエリ、Document Service API、エラーハンドリング
バックエンドカスタマイズ:
- examples/backend.md — カスタムコントローラー、サービス、ルート、ポリシー、ミドルウェア、ライフサイクルフック、Document Serviceミドルウェア
認証:
- examples/auth.md — JWT認証、登録、ログイン、ロールと権限
哲学
Strapiは、Node.js(Koa)上に構築されたオープンソースのヘッドレスCMSであり、コンテンツタイプスキーマからRESTfulおよびGraphQL APIを自動生成します。コンテンツはJSONスキーマで定義され、管理パネルを通じて管理され、生成されたAPIエンドポイントを介して消費されます。
コア原則:
- スキーマ駆動のコンテンツ — コンテンツタイプは、フィールド、リレーション、コンポーネント、ダイナミックゾーンを記述する
schema.jsonファイルで定義されます。管理パネルのコンテンツタイプビルダーはビジュアルエディターを提供しますが、スキーマはリポジトリに存在するコードです。 - 自動生成API — すべてのコンテンツタイプは、自動的にCRUD RESTエンドポイント(
/api/:pluralApiId)とオプションのGraphQLサポートを取得します。標準操作には手動のルート/コントローラー作成は不要です。 - Document Service API(v5) — カスタムコード、プラグイン、ライフサイクルフックからコンテンツにアクセスするためのバックエンドAPIです。v4のEntity Serviceを置き換えます。プライマリ識別子としてデータベースの
idではなくdocumentIdを使用します。 - 下書き&公開 — コンテンツタイプは下書き/公開ワークフローを持つことができます。Document Serviceはデフォルトで
status: 'draft'になります。公開されたコンテンツにはstatus: 'published'または明示的なpublish()呼び出しが必要です。 - 権限優先 — すべてのコンテンツタイプエンドポイントはデフォルトで非公開です。アクセスは管理パネル(「ユーザー権限」プラグイン)またはAPIトークンを介して明示的に許可する必要があります。
- バックエンドカスタマイズ — コントローラー、サービス、ルート、ポリシー、ミドルウェアはすべてカスタマイズ可能です。StrapiはKoa上に構築されたMVCに似たパターンに従います。
コアパターン
パターン1: コンテンツタイプスキーマ
コンテンツタイプは、./src/api/[api-name]/content-types/[content-type-name]/schema.jsonにあるschema.jsonファイルで定義されます。複数ドキュメントコンテンツ(記事、製品)にはcollectionTypeを、単一ドキュメントコンテンツ(サイト設定、ホームページ)にはsingleTypeを使用します。
{
"kind": "collectionType",
"info": {
"singularName": "article",
"pluralName": "articles",
"displayName": "Article"
},
"options": { "draftAndPublish": true },
"attributes": {
"title": { "type": "string", "required": true },
"slug": { "type": "uid", "targetField": "title" },
"author": {
"type": "relation",
"relation": "manyToOne",
"target": "api::author.author",
"inversedBy": "articles"
},
"blocks": {
"type": "dynamiczone",
"components": ["blocks.hero", "blocks.rich-text"]
}
}
}
主要フィールド: uidはtargetFieldからスラッグを自動生成します。relationタイプはinversedBy/mappedByでカーディナリティを定義します。componentは再利用可能なブロックを埋め込み、dynamiczoneは混合コンポーネントタイプを許可します。完全なコレクションおよびシングルタイプの例については、examples/core.mdを参照してください。
パターン2: qsを使用したREST APIクエリ
REST APIは、フィルタリング、ポピュレーション、ソート、ページネーションのための複雑なクエリパラメータを受け入れます。クエリ文字列の構築には常にqsライブラリを使用してください。
import qs from "qs";
const query = qs.stringify(
{
filters: { publishedAt: { $notNull: true } },
populate: {
author: { fields: ["name"] },
categories: { fields: ["name", "slug"] },
},
sort: ["publishedAt:desc"],
pagination: { page: 1, pageSize: 25 },
},
{ encodeValuesOnly: true },
);
const response = await fetch(`${STRAPI_URL}/api/articles?${query}`);
const { data, meta } = await response.json();
重要ポイント: encodeValuesOnlyはブラケットエンコーディングの問題を防ぎます。フィルターは演算子($eq、$notNull、$containsi、$or、$and)を使用します。fieldsを使用したターゲット指定のpopulateは過剰取得を回避します(本番環境では決してpopulate=*を使用しないでください)。ダイナミックゾーンコンポーネントはon構文を使用します。完全なフィルタリング、ポピュレーション、ページネーションの例については、examples/core.mdを参照してください。
パターン3: Document Service API(バックエンド)
Document Service APIは、カスタムコントローラー、サービス、ライフサイクルフック、プラグインでサーバーサイドからコンテンツにアクセスするために使用されます。v4のEntity Serviceを置き換えます(v5で削除されました)。
const CONTENT_TYPE_UID = "api::article.article";
// 公開されたコンテンツを検索(デフォルトは'draft')
const articles = await strapi.documents(CONTENT_TYPE_UID).findMany({
status: "published",
filters: { categories: { slug: { $eq: "news" } } },
populate: { author: true },
});
// CRUDはデータベースidではなくdocumentIdを使用します
const article = await strapi
.documents(CONTENT_TYPE_UID)
.findOne({ documentId });
const created = await strapi
.documents(CONTENT_TYPE_UID)
.create({ data: { title: "New" } });
await strapi.documents(CONTENT_TYPE_UID).publish({ documentId });
重要ポイント: strapi.documents(uid)はstrapi.entityServiceを置き換えます(v5で削除されました)。documentIdはロケールおよび下書き/公開バージョン間で永続的な識別子です。デフォルトのステータスは'draft'です(明示的にstatus: 'published'を渡してください)。専用のpublish()/unpublish()/discardDraft()メソッドがあります。完全なCRUDおよび公開/非公開の例については、examples/core.mdを参照してください。
パターン4: カスタムコントローラー
自動生成されたコントローラーアクションを拡張または置き換えます。コントローラーはリクエスト/レスポンスロジックを処理し、サービスに委譲します。ファクトリーのcreateCoreControllerを使用してサニタイズヘルパーを継承します。
// src/api/article/controllers/article.ts
export default factories.createCoreController(
"api::article.article",
({ strapi }) => ({
async find(ctx) {
await this.validateQuery(ctx);
const sanitizedQuery = await this.sanitizeQuery(ctx);
const { results, pagination } = await strapi
.service("api::article.article")
.find(sanitizedQuery);
const sanitizedResults = await this.sanitizeOutput(results, ctx);
return this.transformResponse(sanitizedResults, { pagination });
},
}),
);
なぜ良いのか: validateQuery + sanitizeQuery + sanitizeOutputはロールベースのフィールドアクセスを強制します。transformResponse()は出力を{ data, meta }エンベロープでラップします。
カスタムアクション(findBySlug、incrementViews、search)を含む完全なコントローラー例については、examples/backend.mdを参照してください。
パターン5: カスタムルート
カスタムルートを定義して、カスタムコントローラーアクションを公開します。カスタムルートファイルはコアルーターと同じ場所に配置します。
// src/api/article/routes/custom-article.ts
export default {
routes: [
{
method: "GET",
path: "/articles/slug/:slug",
handler: "api::article.article.findBySlug",
config: { auth: false },
},
],
};
なぜ良いのか: コアルーターとは別のファイルです。handlerは完全なUIDを使用します。auth: falseでパブリックアクセスを許可します。ポリシー/ミドルウェアはルートごとにアタッチ可能です。
onlyによるルート制限、ミドルウェアのアタッチ、ポリシー設定については、examples/backend.mdを参照してください。
パターン6: ライフサイクルフック
コンテンツタイプ操作に副作用を登録します。フックはコンテンツタイプスキーマと同じ場所にあるlifecycles.tsファイルで定義します。
// src/api/article/content-types/article/lifecycles.ts
export default {
async beforeCreate(event) {
/* event.params.data -- 入力を変換するために変更 */
},
async afterCreate(event) {
/* event.result -- 作成されたドキュメント */
},
async beforeUpdate(event) {
/* event.params.data -- 保存前に変更 */
},
async afterDelete(event) {
/* event.result -- 関連リソースをクリーンアップ */
},
};
なぜ良いのか: フックはDocument Service操作で自動的に起動します。beforeXxxはevent.params.dataを変更できます。afterXxxはevent.resultを持ちます。
注意: v5の横断的な関心事(監査ログ、キャッシュ無効化)には、src/index.tsに登録されたDocument Serviceミドルウェア(strapi.documents.use((ctx, next) => { ... }))を優先してください。ライフサイクルフックは、コンテンツタイプ固有の副作用のために引き続き使用できます。
完全なライフサイクル例(スラッグ生成、プログラムによる購読、監査ログを含む)については、examples/backend.mdを参照してください。
パターン7: サービス
サービスは、コントローラーによって呼び出される再利用可能なビジネスロジックを含みます。createCoreServiceを使用してデフォルトのCRUDを継承し、カスタムメソッドを追加します。
export default factories.createCoreService(
"api::article.article",
({ strapi }) => ({
async findPublished(filters = {}) {
return strapi
.documents("api::article.article")
.findMany({ status: "published", filters });
},
}),
);
// 以下のように呼び出し: strapi.service("api::article.article").findPublished()
なぜ良いのか: デフォルトのCRUDを継承します。カスタムメソッドは再利用可能なクエリロジックをカプセル化します。コントローラーからstrapi.service(uid)を介して呼び出されます。
関連記事クエリや公開+通知パターンを含む複雑なサービス例については、examples/backend.mdを参照してください。
パターン8: ポリシー
ポリシーは、ルートへのアクセスを許可または拒否する読み取り専用の関数です。true(許可)またはfalse(拒否)を返し、リクエストを変更することはできません。
// src/api/article/policies/is-owner.ts
export default async (policyContext, config, { strapi }) => {
const article = await strapi.documents("api::article.article").findOne({
documentId: policyContext.params.documentId,
populate: { author: true },
});
return article?.author?.documentId === policyContext.state.user?.documentId;
};
なぜ良いのか: 読み取り専用チェック(リクエストの変更なし)。コアルーター設定のアクションごとに適用されます。
ポリシーのルートへの適用、レート制限ポリシー、マルチポリシーチェーンについては、examples/backend.mdを参照してください。
<decision_framework>
意思決定フレームワーク
REST API vs Document Service API
コードはどこで実行されますか?
+-- クライアントサイド(ブラウザ、フロントエンドアプリ)
| +-- REST APIを使用(fetch /api/:pluralApiId)
+-- サーバーサイド(カスタムコントローラー、サービス、プラグイン、ライフサイクルフック)
+-- Document Service APIを使用(strapi.documents(uid))
ポピュレーション戦略
関連データが必要ですか?
+-- いいえ --> populateを渡さない(レスポンスを軽量化)
+-- はい --> どの程度の制御が必要ですか?
+-- すべてのリレーション、1レベル --> populate: '*'(便利だが過剰取得)
+-- 特定のリレーション --> populate: { relation: { fields: [...] } }
+-- ネストされたリレーション
…
[翻訳のため省略]
</decision_framework>