Communitygithub.com

agents-inc/skills – Strapi Patterns

CMS headless de código abierto – esquemas de tipos de contenido, API REST, API de Servicio de Documentos, controladores personalizados, hooks de ciclo de vida, autenticación, TypeScript

¿Qué es skills – Strapi Patterns?

Esta habilidad proporciona una guía completa para trabajar con Strapi como un CMS headless de código abierto. Cubre la definición de esquemas de tipos de contenido, el uso de la API REST y la API de Servicio de Documentos (Document Service API) de Strapi v5, la implementación de autenticación JWT, la creación de controladores, servicios, rutas, políticas y middlewares personalizados, y la gestión de hooks de ciclo de vida. Es útil para desarrolladores que construyen aplicaciones gestionadas por contenido con Strapi, ya que ofrece patrones probados y mejores prácticas para evitar errores comunes. La habilidad está diseñada para agentes de IA como Claude Code, Cursor o Codex, proporcionando instrucciones claras y ejemplos de código que garantizan un uso correcto de las APIs modernas de Strapi, como el uso obligatorio del Servicio de Documentos en lugar del obsoleto Entity Service, y la necesidad de pasar siempre el parámetro `populate` para obtener relaciones y medios. También incluye un marco de decisión para elegir entre la API REST y el Servicio de Documentos, y estrategias de población.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/api-cms-strapi

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Strapi Patterns

Guía Rápida: Usa Strapi como un CMS headless de código abierto con APIs REST/GraphQL generadas automáticamente a partir de esquemas de tipos de contenido. En v5, usa la API de Servicio de Documentos (strapi.documents()) para el acceso a datos en el backend en lugar del Entity Service obsoleto. Las respuestas de la API REST usan un formato plano (data.fieldName, no data.attributes.fieldName). Las relaciones y los medios NO se incluyen por defecto: siempre pasa populate. Usa qs para construir cadenas de consulta complejas. Los tipos de contenido son privados por defecto; configura los permisos a través del plugin de Usuarios y Permisos o tokens de API.


<critical_requirements>

CRÍTICO: Antes de Usar Esta Habilidad

Todo el código debe seguir las convenciones del proyecto en CLAUDE.md (kebab-case, exports con nombre, orden de importaciones, import type, constantes con nombre)

(DEBES usar strapi.documents('api::tipo-contenido.tipo-contenido') (API de Servicio de Documentos) para todo el acceso a datos en el backend en Strapi v5 – la API de Entity Service está eliminada)

(DEBES pasar siempre populate cuando necesites relaciones, medios, componentes o zonas dinámicas – Strapi NO devuelve relaciones por defecto)

(DEBES usar la biblioteca qs para construir cadenas de consulta complejas de la API REST con filtros, populate y ordenación – la construcción manual de cadenas se rompe con parámetros anidados)

(DEBES sanitizar y validar tanto la entrada como la salida en controladores personalizados usando this.sanitizeQuery(ctx), this.sanitizeOutput() y this.validateQuery(ctx))

(DEBES configurar permisos para cada endpoint de tipo de contenido a través del panel de administración o la configuración – todas las rutas son privadas por defecto)

</critical_requirements>


Detección automática: 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

Cuándo usarlo:

  • Construir aplicaciones gestionadas por contenido con Strapi como CMS headless
  • Definir esquemas de tipos de contenido (schema.json) con campos, relaciones, componentes y zonas dinámicas
  • Consultar la API REST con filtros, populate, ordenación y paginación
  • Crear controladores, servicios, rutas, políticas o middlewares personalizados
  • Usar la API de Servicio de Documentos para operaciones CRUD en el backend con flujos de trabajo de borrador/publicación
  • Implementar autenticación JWT con el plugin de Usuarios y Permisos
  • Agregar hooks de ciclo de vida a los tipos de contenido para efectos secundarios
  • Generar tipos TypeScript para los esquemas de contenido

Patrones clave cubiertos:

  • Definición de esquemas de tipos de contenido (schema.json)
  • Consultas a la API REST con qs (filtros, populate, ordenación, paginación)
  • API de Servicio de Documentos (findMany, findOne, create, update, delete, publish, unpublish)
  • Controladores, servicios, rutas, políticas y middlewares personalizados
  • Hooks de ciclo de vida (beforeCreate, afterUpdate, etc.)
  • Autenticación JWT (registro, inicio de sesión, solicitudes autenticadas)
  • Generación de tipos TypeScript

Cuándo NO usarlo:

  • Plataformas CMS que no sean Strapi (usa la habilidad dedicada para tu CMS)
  • Consultas directas a la base de datos que eviten la capa de API de Strapi (usa el Servicio de Documentos de Strapi)
  • Lógica transaccional compleja que requiera SQL puro (Strapi abstrae la base de datos)

Recursos detallados:

  • Para marcos de decisión y tablas de referencia rápida, consulta reference.md

API Core y REST:

  • examples/core.md – Esquemas de tipos de contenido, consultas a la API REST, API de Servicio de Documentos, manejo de errores

Personalización del Backend:

  • examples/backend.md – Controladores, servicios, rutas, políticas, middlewares, hooks de ciclo de vida, middleware del Servicio de Documentos

Autenticación:

  • examples/auth.md – Autenticación JWT, registro, inicio de sesión, roles y permisos

Filosofía

Strapi es un CMS headless de código abierto construido sobre Node.js (Koa) que genera automáticamente APIs RESTful y GraphQL a partir de esquemas de tipos de contenido. El contenido se define mediante esquemas JSON, se gestiona a través de un panel de administración y se consume a través de endpoints de API generados.

Principios fundamentales:

  1. Contenido basado en esquemas – Los tipos de contenido se definen en archivos schema.json que describen campos, relaciones, componentes y zonas dinámicas. El Content-Type Builder del panel de administración proporciona un editor visual, pero los esquemas son código que reside en tu repositorio.
  2. APIs generadas automáticamente – Cada tipo de contenido obtiene automáticamente endpoints CRUD REST (/api/:pluralApiId) y soporte opcional de GraphQL. No se necesita creación manual de rutas/controladores para operaciones estándar.
  3. API de Servicio de Documentos (v5) – La API de backend para acceder al contenido desde código personalizado, plugins y hooks de ciclo de vida. Reemplaza el Entity Service de v4. Usa documentId (no el id de la base de datos) como identificador principal.
  4. Borrador y Publicación – Los tipos de contenido pueden tener flujos de trabajo de borrador/publicación. El Servicio de Documentos por defecto usa status: 'draft'; el contenido publicado requiere status: 'published' o llamadas explícitas a publish().
  5. Prioridad de permisos – Todos los endpoints de tipos de contenido son privados por defecto. El acceso debe concederse explícitamente a través del panel de administración (plugin de Usuarios y Permisos) o tokens de API.
  6. Personalización del backend – Los controladores, servicios, rutas, políticas y middlewares se pueden personalizar. Strapi sigue un patrón similar a MVC construido sobre Koa.

Patrones Principales

Patrón 1: Esquema de Tipo de Contenido

Los tipos de contenido se definen en archivos schema.json en ./src/api/[nombre-api]/content-types/[nombre-tipo-contenido]/schema.json. Usa collectionType para contenido de múltiples documentos (artículos, productos) y singleType para contenido de un solo documento (configuración del sitio, página de inicio).

{
  "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"]
    }
  }
}

Campos clave: uid genera slugs automáticamente a partir de targetField, los tipos relation definen la cardinalidad con inversedBy/mappedBy, component incrusta bloques reutilizables, dynamiczone permite tipos de componentes mixtos. Consulta examples/core.md para ejemplos completos de colecciones y tipos únicos.


Patrón 2: Consultas a la API REST con qs

La API REST acepta parámetros de consulta complejos para filtrado, población, ordenación y paginación. Usa siempre la biblioteca qs para construir cadenas de consulta.

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();

Puntos clave: encodeValuesOnly evita problemas de codificación de corchetes, los filtros usan operadores ($eq, $notNull, $containsi, $or, $and), populate dirigido con fields evita la sobrecarga (nunca uses populate=* en producción), los componentes de zona dinámica usan la sintaxis on. Consulta examples/core.md para ejemplos completos de filtrado, población y paginación.


Patrón 3: API de Servicio de Documentos (Backend)

La API de Servicio de Documentos se usa en controladores, servicios, hooks de ciclo de vida y plugins personalizados para acceder al contenido desde el lado del servidor. Reemplaza el Entity Service de v4 (eliminado en v5).

const CONTENT_TYPE_UID = "api::article.article";

// Buscar contenido publicado (el valor por defecto es 'draft')
const articles = await strapi.documents(CONTENT_TYPE_UID).findMany({
  status: "published",
  filters: { categories: { slug: { $eq: "news" } } },
  populate: { author: true },
});

// CRUD usa documentId (no el id de la base de datos)
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 });

Puntos clave: strapi.documents(uid) reemplaza strapi.entityService (eliminado en v5), documentId es el identificador persistente entre locales y versiones de borrador/publicado, el estado por defecto es 'draft' (pasa status: 'published' explícitamente), métodos dedicados publish()/unpublish()/discardDraft(). Consulta examples/core.md para ejemplos completos de CRUD y publicación/despublicación.


Patrón 4: Controladores Personalizados

Extiende o reemplaza las acciones de controlador generadas automáticamente. Los controladores manejan la lógica de solicitud/respuesta y delegan en los servicios. Usa createCoreController de factories para heredar los ayudantes de sanitización.

// 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 });
    },
  }),
);

Por qué es bueno: validateQuery + sanitizeQuery + sanitizeOutput aplican el acceso a campos basado en roles, transformResponse() envuelve la salida en un envoltorio { data, meta }

Consulta examples/backend.md para ejemplos completos de controladores que incluyen acciones personalizadas (findBySlug, incrementViews, search).


Patrón 5: Rutas Personalizadas

Define rutas personalizadas para exponer acciones de controlador personalizadas. Los archivos de rutas personalizadas se colocan junto al enrutador principal.

// src/api/article/routes/custom-article.ts
export default {
  routes: [
    {
      method: "GET",
      path: "/articles/slug/:slug",
      handler: "api::article.article.findBySlug",
      config: { auth: false },
    },
  ],
};

Por qué es bueno: Archivo separado del enrutador principal, handler usa el UID completo, auth: false para acceso público, políticas/middlewares adjuntables por ruta

Consulta examples/backend.md para restricción de rutas con only, adjuntar middlewares y configuración de políticas.


Patrón 6: Hooks de Ciclo de Vida

Registra efectos secundarios en las operaciones de tipos de contenido. Los hooks se definen en archivos lifecycles.ts junto al esquema del tipo de contenido.

// src/api/article/content-types/article/lifecycles.ts
export default {
  async beforeCreate(event) {
    /* event.params.data -- mutar para transformar la entrada */
  },
  async afterCreate(event) {
    /* event.result -- el documento creado */
  },
  async beforeUpdate(event) {
    /* event.params.data -- mutar antes de guardar */
  },
  async afterDelete(event) {
    /* event.result -- limpiar recursos relacionados */
  },
};

Por qué es bueno: Los hooks se activan automáticamente en las operaciones del Servicio de Documentos, beforeXxx puede mutar event.params.data, afterXxx tiene event.result

Nota: Para preocupaciones transversales en v5 (auditoría, invalidación de caché), prefiere el middleware del Servicio de Documentos (strapi.documents.use((ctx, next) => { ... })) registrado en src/index.ts. Los hooks de ciclo de vida siguen estando disponibles para efectos secundarios específicos de tipos de contenido.

Consulta examples/backend.md para ejemplos completos de ciclo de vida que incluyen generación de slugs, suscripción programática y registro de auditoría.


Patrón 7: Servicios

Los servicios contienen lógica de negocio reutilizable llamada por los controladores. Usa createCoreService para heredar el CRUD por defecto y agregar métodos personalizados.

export default factories.createCoreService(
  "api::article.article",
  ({ strapi }) => ({
    async findPublished(filters = {}) {
      return strapi
        .documents("api::article.article")
        .findMany({ status: "published", filters });
    },
  }),
);
// Llamado a través de: strapi.service("api::article.article").findPublished()

Por qué es bueno: Hereda el CRUD por defecto, los métodos personalizados encapsulan lógica de consulta reutilizable, se llama desde los controladores a través de strapi.service(uid)

Consulta examples/backend.md para ejemplos de servicios complejos que incluyen consultas de artículos relacionados y patrones de publicación con notificación.


Patrón 8: Políticas

Las políticas son funciones de solo lectura que permiten o deniegan el acceso a una ruta. Devuelven true (permitir) o false (denegar) y no pueden modificar la solicitud.

// 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;
};

Por qué es bueno: Verificación de solo lectura (sin mutación de la solicitud), se aplica por acción en la configuración del enrutador principal

Consulta examples/backend.md para la aplicación de políticas a rutas, políticas de limitación de velocidad y encadenamiento de múltiples políticas.


<decision_framework>

Marco de Decisión

API REST vs API de Servicio de Documentos

¿Dónde se ejecuta tu código?
+-- Lado del cliente (navegador, aplicación frontend)
|   +-- Usa la API REST (fetch /api/:pluralApiId)
+-- Lado del servidor (controlador personalizado, servicio, plugin, hook de ciclo de vida)
    +-- Usa la API de Servicio de Documentos (strapi.documents(uid))

Estrategia de Población

¿Necesitas datos relacionados?
+-- NO --> No pases populate (respuesta más ligera)
+-- SÍ --> ¿Qué nivel de control?
    +-- Todas las relaciones, 1 nivel --> populate: '*' (conveniente pero sobrecarga)
    +-- Relaciones específicas --> populate: { relation: { fields: [...] } }
    +-- Relaciones anidadas

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

Skills relacionados