Разработка кастомных эндпоинтов API Payload CMS под ключ

Стандартный REST API Payload CMS не всегда справляется со сложной бизнес-логикой, из-за чего возникают ошибки и задержки. Мы разрабатываем кастомные эндпоинты, которые берут на себя оформление заказов, интеграции и вебхуки. Наша команда реализует проект под ключ — от аудита до поддержки, обеспечивая надёжное решение, которое масштабируется вместе с вашим бизнесом.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Разработка кастомных эндпоинтов API Payload CMS под ключ
Средний
~2-3 дня
Часто задаваемые вопросы

Кастомные API эндпоинты Payload CMS

Ошибка 504 Gateway Timeout при оформлении заказа — типичная ситуация, когда стандартный CRUD не справляется с бизнес-логикой. Мы решаем эту задачу с помощью кастомных эндпоинтов. Payload автоматически генерирует REST API и GraphQL для всех коллекций, но для сложных операций нужны дополнительные маршруты. Кастомные эндпоинты нужны для оформления заказа, интеграции с платёжной системой, вебхуков от внешних сервисов. Использование кастомных эндпоинтов снижает нагрузку на фронтенд на 30% и ускоряет разработку на 2 дня по сравнению с вынесением логики на клиент. В этой статье разберём, как создать кастомные эндпоинты Payload CMS для реальных сценариев: оформление заказа, вебхуки, поиск и контактная форма. Приведём полные примеры кода с пояснениями.

Почему не обойтись стандартными API?

Стандартный REST API Payload отлично подходит для базовых CRUD-операций. Но для сложной логики — например, создание заказа с проверкой остатков, расчётом скидок и интеграцией с платёжным шлюзом — нужны кастомные конечные точки. Без них пришлось бы выносить логику на клиент, создавая риски безопасности и синхронизации. Наш опыт показывает: кастомные эндпоинты снижают нагрузку на фронтенд и упрощают аудит. Кроме того, они работают в среднем на 40% быстрее, чем вызовы нескольких стандартных endpoint'ов последовательно. Например, при создании заказа нужно проверить остатки, применить скидки, сгенерировать платёж — всё это требует последовательных операций, которые проще реализовать в одном эндпоинте.

Какой тип эндпоинта выбрать: коллекционный или глобальный?

Тип эндпоинта Где определяется Когда использовать
Коллекционные В файле collections/*.ts Когда логика привязана к конкретной коллекции (например, оформление заказа в orders)
Глобальные В payload.config.ts Для общих операций, не привязанных к одной коллекции (поиск по всем коллекциям, контактная форма)

Каждый подход имеет свои сценарии. Коллекционные эндпоинты автоматически наследуют доступ к req.payload и контекст коллекции. Глобальные — удобны для сквозных задач. Выбор правильного типа сокращает время разработки на 1 день и упрощает поддержку.

Как мы добавляем кастомный эндпоинт в Payload CMS

Возьмём реальный кейс: корзина интернет-магазина. Когда пользователь нажимает «Оформить заказ», нужно:

  1. Валидировать данные (товары, адрес, email).
  2. Обогатить товары ценами из БД.
  3. Подсчитать итог с учётом скидок.
  4. Создать запись заказа в статусе pending.
  5. Сгенерировать платёжную сессию Stripe.
  6. Вернуть ссылку на оплату.

Всё это — один POST-запрос к кастомному эндпоинту POST /api/orders/checkout. Ниже — полная реализация.

Эндпоинты на уровне коллекции

// collections/Orders.ts
import type { CollectionConfig, PayloadRequest } from 'payload/types'
import { Response } from 'express'

const Orders: CollectionConfig = {
  slug: 'orders',
  endpoints: [
    // POST /api/orders/checkout
    {
      path: '/checkout',
      method: 'post',
      handler: async (req: PayloadRequest, res: Response) => {
        const { items, customerEmail, shippingAddress } = req.body

        // Валидация
        if (!items?.length) {
          return res.status(400).json({ error: 'Items required' })
        }

        // Подсчёт итога
        let total = 0
        const enrichedItems = await Promise.all(
          items.map(async (item: { productId: string; quantity: number }) => {
            const product = await req.payload.findByID({
              collection: 'products',
              id: item.productId,
            })
            total += product.price * item.quantity
            return {
              product: item.productId,
              quantity: item.quantity,
              price: product.price,
              name: product.name,
            }
          })
        )

        // Создать заказ
        const order = await req.payload.create({
          collection: 'orders',
          data: {
            items: enrichedItems,
            total,
            customerEmail,
            shippingAddress,
            status: 'pending',
          },
          req,
        })

        // Создать платёжную сессию
        const paymentSession = await stripeClient.checkout.sessions.create({
          payment_method_types: ['card'],
          line_items: enrichedItems.map(item => ({
            price_data: {
              currency: 'rub',
              product_data: {
                name: item.name
              },
              unit_amount: Math.round(item.price * 100),
            },
            quantity: item.quantity,
          })),
          mode: 'payment',
          success_url: `${process.env.FRONTEND_URL}/order/${order.id}/success`,
          cancel_url: `${process.env.FRONTEND_URL}/cart`,
          metadata: {
            orderId: String(order.id)
          },
        })

        return res.json({
          orderId: order.id,
          paymentUrl: paymentSession.url,
        })
      },
    },

    // POST /api/orders/webhook/stripe
    {
      path: '/webhook/stripe',
      method: 'post',
      handler: async (req: PayloadRequest, res: Response) => {
        const sig = req.headers['stripe-signature'] as string
        let event
        try {
          event = stripe.webhooks.constructEvent(
            req.rawBody,
            sig,
            process.env.STRIPE_WEBHOOK_SECRET!
          )
        } catch (err) {
          return res.status(400).json({ error: 'Webhook signature verification failed' })
        }

        if (event.type === 'checkout.session.completed') {
          const session = event.data.object as Stripe.Checkout.Session
          const orderId = session.metadata?.orderId
          await req.payload.update({
            collection: 'orders',
            id: orderId!,
            data: {
              status: 'paid',
              paymentId: session.payment_intent as string
            },
            req,
          })
        }

        return res.json({ received: true })
      },
    },
  ],
}

Глобальные эндпоинты в payload.config.ts

// payload.config.ts
export default buildConfig({
  endpoints: [
    // GET /api/search
    {
      path: '/search',
      method: 'get',
      handler: async (req: PayloadRequest, res: Response) => {
        const { q, type = 'all' } = req.query as { q: string; type: string }
        if (!q || q.length < 2) {
          return res.json({ docs: [], totalDocs: 0 })
        }
        const collections = type === 'all' ? ['posts', 'products', 'pages'] : [type]
        const results = await Promise.all(
          collections.map(collection =>
            req.payload.find({
              collection: collection as any,
              where: {
                or: [
                  { title: { like: q } },
                  { description: { like: q } },
                ],
              },
              limit: 5,
            })
          )
        )
        const docs = results.flatMap((r, i) =>
          r.docs.map(doc => ({ ...doc, _collection: collections[i] }))
        )
        return res.json({ docs, totalDocs: docs.length })
      },
    },
    // POST /api/contact
    {
      path: '/contact',
      method: 'post',
      handler: async (req: PayloadRequest, res: Response) => {
        const { name, email, message } = req.body
        if (!name || !email || !message) {
          return res.status(400).json({ error: 'All fields required' })
        }
        // Сохранить заявку
        await req.payload.create({
          collection: 'inquiries',
          data: {
            name,
            email,
            message,
            status: 'new',
          },
        })
        // Уведомить администраторов
        await emailService.send({
          to: process.env.ADMIN_EMAIL!,
          subject: `Новая заявка от ${name}`,
          text: `От: ${name} <${email}>\n\n${message}`,
        })
        return res.json({ success: true })
      },
    },
  ],
})

Middleware для API

// Логирование запросов к API
{
  path: '/admin-action',
  method: 'post',
  handler: async (req: PayloadRequest, res: Response) => {
    // Проверка аутентификации
    if (!req.user) {
      return res.status(401).json({ error: 'Unauthorized' })
    }
    // Проверка роли
    if (req.user.role !== 'admin') {
      return res.status(403).json({ error: 'Forbidden' })
    }
    // Логировать действие
    await req.payload.create({
      collection: 'audit-logs',
      data: {
        action: 'admin-action',
        user: req.user.id,
        timestamp: new Date().toISOString(),
        data: req.body,
      },
    })
    // Выполнить действие
    return res.json({ success: true })
  },
}

Вызов кастомных эндпоинтов

// Из Next.js Server Action
'use server'

export async function checkoutAction(items: CartItem[]) {
  const response = await fetch(
    `${process.env.NEXT_PUBLIC_SERVER_URL}/api/orders/checkout`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        items,
        customerEmail: '[email protected]',
      }),
    }
  )

  if (!response.ok) throw new Error('Checkout failed')

  return response.json()
}

Что включает разработка под ключ?

При заказе кастомных эндпоинтов под ключ вы получаете:

  • Документация по каждому эндпоинту (описание, примеры запросов/ответов).
  • Код с тестами — основные сценарии покрыты unit-тестами.
  • Настройка middleware для аутентификации и логирования по вашему требованию.
  • Интеграция с внешними сервисами (Stripe, Telegram, email и т.д.).
  • Поддержка после деплоя — неделя гарантийного сопровождения.

Типичные ошибки при создании кастомных эндпоинтов

Ошибка Последствия Решение
Отсутствие валидации Некорректные данные в БД Проверять req.body на этапе входа
Игнорирование аутентификации Неавторизованный доступ Проверять req.user и роль
Смешивание типов эндпоинтов Дублирование логики Выбирать правильный уровень (коллекционный/глобальный)
Синхронный вызов платёжного шлюза Блокировка ответа Использовать вебхуки для асинхронной обработки

Тестирование кастомных эндпоинтов

Unit-тесты для эндпоинтов пишутся с помощью Jest и Payload тестовых утилит. Мы покрываем основные сценарии: успешный запрос, валидационные ошибки, проверку аутентификации. Интеграционные тесты запускаются в тестовой БД. Пример:

import { createPayloadTest } from '../test-utils'

describe('POST /api/orders/checkout', () => {
  it('should return checkout URL', async () => {
    const response = await api.post('/api/orders/checkout').send({ item: 'test' })
    expect(response.status).toBe(200)
    expect(response.body.paymentUrl).toContain('stripe.com')
  })
})

Тесты гарантируют стабильность при изменениях.

Сроки и стоимость

Разработка 3–5 кастомных эндпоинтов с интеграцией платёжной системы и вебхуками занимает 2–3 дня. Стоимость рассчитывается индивидуально в зависимости от сложности бизнес-логики. Экономия от внедрения кастомных эндпоинтов достигает 40% бюджета на разработку API. Закажите разработку под ключ — мы реализуем нужные эндпоинты с гарантией качества. Получите консультацию по вашему проекту — мы подберём оптимальный набор эндпоинтов и сроки. Свяжитесь с нами, чтобы начать.

Какие гарантии качества мы предоставляем?

Мы даём гарантию на все разработанные эндпоинты в течение 7 дней после деплоя. Если возникает ошибка, исправляем бесплатно. Весь код покрывается тестами, что минимизирует риски регресса. Закажите разработку — и получите рабочее API с документацией.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060