Почему стандартных контроллеров 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 по контроллерам.







