Реалізація BFF (Backend for Frontend) паттерна

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація BFF (Backend for Frontend) паттерна
Складна
~2-4 тижні
Часті питання

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

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація BFF (Backend for Frontend) паттерна

BFF — паттерн, при якому для кожного типу клієнта (web, mobile, TV-додаток) створюється окремий backend-шар. Замість одного універсального API, яке намагається задовольнити всіх клієнтів одночасно, кожен BFF повертає дані у формі, оптимальній для свого клієнта.

Проблема без BFF

Мобільний додаток робить 5 запитів щоб показати один екран профілю:

  • GET /users/{id} — базові дані
  • GET /orders?userId={id}&limit=3 — останні замовлення
  • GET /notifications/unread — лічильник сповіщень
  • GET /recommendations?userId={id} — рекомендації
  • GET /loyalty/points/{id} — бали лояльності

Кожен запит — окремий round trip. На мобільній мережі це 500–1500ms сумарно.

Рішення з BFF

                    Mobile BFF          Web BFF
                    (Node.js)           (Node.js)
iOS App ──────────► /mobile/dashboard   │
Android App ───────► │                  │
                    │                  │
                    ├─► User Service   ◄─── Web SPA
                    ├─► Order Service  ◄──────────
                    ├─► Notification   ◄──────────
                    └─► Recommendation ◄──────────

Реалізація Mobile BFF

// mobile-bff/routes/dashboard.ts
router.get('/mobile/dashboard', authenticate, async (req, res) => {
  const userId = req.user.id;

  // Паралельні запити до мікросервісів
  const [userResult, ordersResult, notificationsResult, loyaltyResult] =
    await Promise.allSettled([
      userService.get(`/users/${userId}`),
      orderService.get(`/orders?customerId=${userId}&limit=3&fields=id,status,total,createdAt`),
      notificationService.get(`/notifications/${userId}/unread-count`),
      loyaltyService.get(`/loyalty/${userId}/summary`)
    ]);

  // Агрегація з обробкою часткових невдач
  const response = {
    user: userResult.status === 'fulfilled' ? {
      id: userResult.value.data.id,
      name: userResult.value.data.displayName,
      avatar: userResult.value.data.avatarUrl
    } : null,

    recentOrders: ordersResult.status === 'fulfilled'
      ? ordersResult.value.data.items.map(transformOrderForMobile)
      : [],

    unreadCount: notificationsResult.status === 'fulfilled'
      ? notificationsResult.value.data.count
      : 0,

    loyalty: loyaltyResult.status === 'fulfilled' ? {
      points: loyaltyResult.value.data.balance,
      tier: loyaltyResult.value.data.tier
    } : null
  };

  res.json(response);
});

// Трансформація даних під мобільний UI
function transformOrderForMobile(order: Order): MobileOrder {
  return {
    id: order.id,
    status: localizeStatus(order.status),  // 'Доставлено' замість 'DELIVERED'
    total: formatCurrency(order.total, 'UAH'),
    date: formatRelativeDate(order.createdAt)  // '2 дні тому'
  };
}

Web BFF — інший формат для тих же даних

// web-bff/routes/dashboard.ts
router.get('/web/dashboard', authenticate, async (req, res) => {
  const userId = req.user.id;

  // Web-версія запитує більше даних для багатого UI
  const [user, orders, stats, notifications] = await Promise.allSettled([
    userService.get(`/users/${userId}`),
    orderService.get(`/orders?customerId=${userId}&limit=10`),
    analyticsService.get(`/analytics/user/${userId}/stats`),
    notificationService.get(`/notifications/${userId}?limit=5&unread=true`)
  ]);

  // Web-формат — більше даних, інша структура
  res.json({
    user: user.status === 'fulfilled' ? user.value.data : null,
    orders: orders.status === 'fulfilled' ? orders.value.data : { items: [], total: 0 },
    analytics: stats.status === 'fulfilled' ? stats.value.data : null,
    notifications: notifications.status === 'fulfilled' ? notifications.value.data : []
  });
});

GraphQL BFF

Якщо клієнт — React-додаток з Apollo Client, BFF може експортувати GraphQL:

// web-bff/graphql/schema.ts
const typeDefs = gql`
  type Query {
    dashboard: Dashboard!
    order(id: ID!): Order
  }

  type Dashboard {
    user: User!
    recentOrders: [Order!]!
    stats: UserStats!
  }
`;

const resolvers = {
  Query: {
    dashboard: async (_, __, { userId }) => {
      const [user, orders, stats] = await Promise.all([
        userService.getUser(userId),
        orderService.getRecentOrders(userId),
        analyticsService.getUserStats(userId)
      ]);
      return { user, recentOrders: orders, stats };
    }
  }
};

Авторизація та аутентифікація в BFF

BFF — природне місце для перевірки JWT та управління сесіями. Особливо для браузерного клієнта:

// BFF зберігає refresh token у httpOnly cookie,
// не передаючи його в браузер JavaScript
router.post('/auth/refresh', async (req, res) => {
  const refreshToken = req.cookies.refresh_token;
  if (!refreshToken) return res.status(401).json({ error: 'No token' });

  const tokens = await authService.refreshTokens(refreshToken);

  res.cookie('refresh_token', tokens.refreshToken, {
    httpOnly: true, secure: true, sameSite: 'strict',
    maxAge: 30 * 24 * 60 * 60 * 1000  // 30 днів
  });

  res.json({ accessToken: tokens.accessToken });
});

Кешування в BFF

import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);

async function getCachedOrFetch<T>(
  key: string,
  ttl: number,
  fetcher: () => Promise<T>
): Promise<T> {
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  const data = await fetcher();
  await redis.setex(key, ttl, JSON.stringify(data));
  return data;
}

// Рекомендації кешуємо на 5 хвилин
const recommendations = await getCachedOrFetch(
  `recommendations:${userId}`,
  300,
  () => recommendationService.get(`/recommendations/${userId}`)
);

Терміни реалізації

  • BFF для одного клієнта (3–5 агрегуючих ендпоінтів) — 1–2 тижні
  • GraphQL BFF + авторизація + кешування — 2–3 тижні
  • BFF для 2–3 клієнтів з спільною бібліотекою викликів сервісів — 3–4 тижні