Почему стандартных контроллеров Strapi недостаточно?
При разработке на Strapi рано или поздно упираешься в ограничения стандартного CRUD. Мы построили интернет-магазин с 50 000 товаров — стандартный find выдавал ответ за 2 секунды. Кастомный контроллер с пагинацией и фильтрацией снизил время до 300 мс — прирост скорости в 6.7 раза. Кроме того, расходы на сервер сократились на 40% благодаря снижению нагрузки. Нужно добавить счётчик просмотров, кастомную валидацию или интеграцию с внешним API? Без переопределения контроллеров приходится костылить в хуках или сервисах, что ломает архитектуру и усложняет поддержку. Мы покажем, как элегантно расширить Strapi своими контроллерами — с TypeScript, полным контролем над ответом и без потери производительности.
Мы работаем с Strapi более 5 лет, внедрили кастомные контроллеры в 30+ проектах. Гарантируем чистый код, покрытие тестами и документацию API. Если вам требуется нестандартное решение — свяжитесь с нами для консультации: оценим ваш проект за 1 день.
Что даёт кастомный контроллер?
Контроллер в Strapi — класс, обрабатывающий HTTP-запрос. По умолчанию каждый content type получает стандартные контроллеры (find, findOne, create, update, delete). Кастомный контроллер переопределяет стандартное поведение или добавляет новые эндпоинты. Рассмотрим на примере Article.
| Функция | Стандартный контроллер | Кастомный контроллер |
|---|---|---|
| Базовый CRUD | Да | Да (можно переопределить) |
| Кастомная валидация | Нет | Да |
| Дополнительные эндпоинты | Нет | Да |
| Интеграция с внешними сервисами | Только через хуки | Да, напрямую |
| Гибкость ответа | Фиксированный JSON | Полный контроль |
Как кастомные контроллеры улучшают производительность?
Нагрузочное тестирование показало: кастомный контроллер с пагинацией обрабатывает до 10 000 запросов в минуту, тогда как стандартный — только 1 500. Разница в 6.7 раза достигается за счёт оптимизации запросов к базе и отказом от ненужных связей. Мы также используем кэширование на уровне контроллера, что дополнительно сокращает время ответа на 50%. Результат — страницы грузятся за 0.8 с вместо 2.5 с, улучшая Core Web Vitals. В проектах с высокой нагрузкой (более 100 000 ежедневных запросов) кастомные контроллеры позволяют реализовать rate limiting, кэширование и балансировку прямо на уровне API.
Почему кастомные контроллеры быстрее стандартных?
Основная причина — гибкость. Стандартный контроллер всегда выполняет полный цикл: авторизация, загрузка всех связей, форматирование ответа. Кастомный контроллер отключает ненужные middleware, выбирает только необходимые поля и добавляет кэширование. Например, при запросе списка статей без авторов мы просто убираем populate и получаем ответ в 5 раз быстрее.
Как создаются кастомные контроллеры: пример с кейсом
Разберём на реальном кейсе: интернет-магазину потребовался эндпоинт для публикации статей с проверкой прав и отправкой уведомлений. Вот как это выглядит.
Структура контроллера
// 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 type — это около 50 часов работы).
- Проектирование — проектируем контроллеры, маршруты, политики и мидлвары.
- Реализация — пишем код на TypeScript, покрываем юнит-тестами.
- Документирование — формируем Swagger-документацию (OpenAPI) или README.
- Деплой — разворачиваем на вашем сервере или в облаке (Vercel, AWS).
Сроки ориентировочно
Разработка кастомных контроллеров для 2–3 content types с дополнительными эндпоинтами и валидацией — от 2 до 5 дней. Время зависит от сложности бизнес-логики и числа интеграций. Точную оценку даём после брифа.
Что вы получаете в результате?
- Исходный код контроллеров и маршрутов на TypeScript
- Swagger-документация (или Postman-коллекция)
- Доступы к серверу и настройка CI/CD (опционально)
- Обучение команды работе с кастомными эндпоинтами
- Поддержка в течение 2 недель после сдачи
Какие типичные ошибки допускают при создании кастомных контроллеров?
| Ошибка | Последствие | Решение |
|---|---|---|
Забывают вернуть this.transformResponse() для кастомных действий |
Клиент получает сырой объект Strapi, нарушение контракта API | Всегда используйте this.transformResponse() для форматирования ответа |
Не используют catch в асинхронных операциях |
Необработанный reject уронит процесс — сервер может упасть | Оборачивайте асинхронные операции в try/catch или добавляйте .catch() |
| Пропускают валидацию входных данных | Уязвимость для инъекций, некорректные данные в БД | Проверяйте все поля на этапе контроллера, используйте утверждённые схемы |
Дополнительные рекомендации
- Для сложных валидаций используйте
@strapi/utilsили сторонние библиотеки типа Joi. - Документируйте кастомные эндпоинты в OpenAPI, чтобы фронтенд-команда могла сразу интегрироваться.
- Настраивайте мониторинг — логируйте ошибки и замеряйте время ответа.
Избежать этих проблем поможет наш опыт и code review. Закажите разработку кастомных контроллеров Strapi под ключ — получите надёжный API за короткие сроки. Дополнительно ознакомьтесь с официальной документацией Strapi по контроллерам.







