Кастомные контроллеры в Strapi
Контроллер в Strapi — класс, обрабатывающий HTTP-запрос. По умолчанию каждый content type получает стандартные контроллеры (find, findOne, create, update, delete). Кастомный контроллер переопределяет стандартное поведение или добавляет новые эндпоинты.
Структура контроллера
// src/api/article/controllers/article.ts
import { factories } from '@strapi/strapi'
export default factories.createCoreController('api::article.article', ({ strapi }) => ({
// Переопределить find — добавить дополнительную логику
async find(ctx) {
// Добавить счётчик просмотров к ответу
const response = await super.find(ctx)
// Добавить мета-информацию
response.meta.generatedAt = new Date().toISOString()
return response
},
// Переопределить findOne — увеличить счётчик просмотров
async findOne(ctx) {
const response = await super.findOne(ctx)
if (response.data) {
const { id } = ctx.params
// Обновить счётчик асинхронно (не блокировать ответ)
strapi.entityService.update('api::article.article', id, {
data: { viewCount: (response.data.attributes.viewCount || 0) + 1 },
}).catch(console.error)
}
return response
},
// Кастомное действие
async publish(ctx) {
const { id } = ctx.params
const article = await strapi.entityService.findOne('api::article.article', id)
if (!article) {
return ctx.notFound('Article not found')
}
if (article.publishedAt) {
return ctx.badRequest('Article already published')
}
const updated = await strapi.entityService.update('api::article.article', id, {
data: { publishedAt: new Date().toISOString() },
})
// Отправить уведомления подписчикам
await strapi.service('api::newsletter.newsletter').notifySubscribers(updated)
return this.transformResponse(updated)
},
}))
Маршрут для кастомного действия
// src/api/article/routes/article.ts
import { factories } from '@strapi/strapi'
export default factories.createCoreRouter('api::article.article', {
// Добавить кастомный маршрут
config: {
find: {},
findOne: {},
create: { middlewares: ['api::article.check-quota'] },
update: {},
delete: {},
},
})
// src/api/article/routes/custom-article.ts
export default {
routes: [
{
method: 'POST',
path: '/articles/:id/publish',
handler: 'article.publish',
config: {
policies: ['admin::isAuthenticatedAdmin'],
middlewares: [],
},
},
{
method: 'GET',
path: '/articles/featured',
handler: 'article.getFeatured',
config: { auth: false },
},
],
}
Контроллер с пагинацией и фильтрацией
async getFeatured(ctx) {
const { category, limit = 6 } = ctx.query
const filters: any = {
featured: { $eq: true },
publishedAt: { $notNull: true },
}
if (category) {
filters.category = { slug: { $eq: category } }
}
const articles = await strapi.entityService.findMany('api::article.article', {
filters,
populate: ['cover', 'category', 'author'],
sort: { publishedAt: 'desc' },
limit: Number(limit),
})
return { data: articles }
}
Контроллер с валидацией
async create(ctx) {
const { title, content, category } = ctx.request.body.data || {}
// Кастомная валидация
if (!title || title.length < 5) {
return ctx.badRequest('Title must be at least 5 characters')
}
if (content && content.length > 50000) {
return ctx.badRequest('Content too long (max 50000 chars)')
}
// Проверить уникальность заголовка
const existing = await strapi.entityService.findMany('api::article.article', {
filters: { title: { $eq: title } },
limit: 1,
})
if (existing.length > 0) {
return ctx.conflict('Article with this title already exists')
}
// Установить автора автоматически
ctx.request.body.data.author = ctx.state.user.id
return super.create(ctx)
}
Сроки
Разработка кастомных контроллеров для 2–3 content types с дополнительными эндпоинтами и валидацией — 2–3 дня.







