BFF (Backend for Frontend) Pattern Implementation

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
BFF (Backend for Frontend) Pattern Implementation
Complex
~2-4 weeks
FAQ
Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Implementing BFF (Backend for Frontend) Pattern

BFF is a pattern where each client type (web, mobile, TV app) gets its own backend layer. Instead of one universal API trying to satisfy all clients, each BFF returns data optimized for its client.

Problem Without BFF

A mobile app makes 5 requests to display one profile screen:

  • GET /users/{id} — basic data
  • GET /orders?userId={id}&limit=3 — recent orders
  • GET /notifications/unread — notification count
  • GET /recommendations?userId={id} — recommendations
  • GET /loyalty/points/{id} — loyalty points

Each request is a separate round trip. On mobile networks, this totals 500–1500ms.

Solution with 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 Implementation

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

  // Parallel requests to microservices
  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`)
    ]);

  // Aggregation with partial failure handling
  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);
});

// Transform data for mobile UI
function transformOrderForMobile(order: Order): MobileOrder {
  return {
    id: order.id,
    status: localizeStatus(order.status),  // 'Delivered' instead of 'DELIVERED'
    total: formatCurrency(order.total, 'USD'),
    date: formatRelativeDate(order.createdAt)  // '2 days ago'
  };
}

Web BFF — Different Format for Same Data

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

  // Web version requests more data for rich 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 format — more data, different structure
  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

If the client is a React app with Apollo Client, BFF can export 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 };
    }
  }
};

Authorization and Authentication in BFF

BFF is the natural place for JWT verification and session management. Especially for browser clients:

// BFF stores refresh token in httpOnly cookie,
// not exposing it to browser 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 days
  });

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

Caching in 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;
}

// Cache recommendations for 5 minutes
const recommendations = await getCachedOrFetch(
  `recommendations:${userId}`,
  300,
  () => recommendationService.get(`/recommendations/${userId}`)
);

Implementation Timeline

  • BFF for one client (3–5 aggregating endpoints) — 1–2 weeks
  • GraphQL BFF + authorization + caching — 2–3 weeks
  • BFF for 2–3 clients with shared service call library — 3–4 weeks