Files
2026-07-17 21:48:37 +05:30

3.4 KiB

The BFF (Backend-For-Frontend)

The UI never talks to a real backend. It calls its own BFF — Next.js Route Handlers under apps/web/app/api/bff/**. This is the seam that keeps the frontend independent of whatever backend you eventually run (IIOS service, a NestJS BFF, Supabase, …).

Why a BFF

  • One shaped API per frontend. Endpoints return exactly what the UI needs, nothing more.
  • Secrets stay server-side. Tokens/keys for the real backend live in the route handlers, never in the browser bundle.
  • Backend independence. Change the handlers; the SDK + components don't move.

Endpoint contract

Base path: /api/bff. All JSON. (These mirror the BffClient methods in the SDK.)

Method Path Body / Query Returns
GET /bootstrap { me, workspace, workspaces, users, sections, channels }
GET /channels { channels }
GET /channels/:id/messages { messages }
POST /channels/:id/messages { body, parentId?, scheduledFor? } { message } (201)
GET /channels/:id/threads/:parentId { parent, replies }
POST /channels/:id/messages/:mid/reactions { emoji } { message }
POST /channels/:id/messages/:mid/pin { message }
POST /channels/:id/messages/:mid/save { message }
GET /inbox ?state=OPEN|SNOOZED|DONE|ARCHIVED { items }
PATCH /inbox/:id { state } { item }
GET /support/tickets ?scope=mine|assigned|all { tickets }
GET /support/tickets/:id { ticket }
PATCH /support/tickets/:id { state } { ticket }
POST /support/tickets/:id/reply { body } { ticket }
GET /support/availability { agents }
GET /search ?q= { results }
GET /notifications { notifications }

The mock store

apps/web/lib/mock/store.ts is a module singleton seeded from data.ts. It persists across requests in one Node process (resets on restart). Each handler is a thin adapter:

// app/api/bff/channels/[id]/messages/route.ts
export async function POST(req, { params }) {
  const { id } = await params;
  const { body, parentId, scheduledFor } = await req.json();
  const message = store.send(id, body, { parentId, scheduledFor });
  return NextResponse.json({ message }, { status: 201 });
}

Swapping in a real backend

Two options:

A. Keep the BFF inside Next (recommended). Replace the store.* calls with fetch() to your backend inside each handler. Add auth there. Example against IIOS:

// app/api/bff/channels/[id]/messages/route.ts
export async function GET(_req, { params }) {
  const { id } = await params;
  const token = await getIiosToken();               // server-side only
  const res = await fetch(`${process.env.IIOS_URL}/v1/threads/${id}/messages`, {
    headers: { authorization: `Bearer ${token}` },
  });
  const data = await res.json();
  return NextResponse.json({ messages: data.messages.map(toSdkMessage) });  // map shapes
}

B. Standalone NestJS BFF. Point the SDK at it with NEXT_PUBLIC_BFF_BASE_URL=https://bff… and implement the same 17 endpoints in NestJS. The BffClient prefixes /api/bff, so mount your Nest routes under that prefix (or adjust client.ts).

Either way the SDK and every component are unchanged. See IIOS_INTEGRATION.md for the full mapping.