first commit
This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
# 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:
|
||||
|
||||
```ts
|
||||
// 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:
|
||||
|
||||
```ts
|
||||
// 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`](IIOS_INTEGRATION.md) for the full mapping.
|
||||
Reference in New Issue
Block a user