Wrap public OpenAPIs with your own BFF
Wrap public OpenAPIs with your own BFF
Calling a public-sector or third-party OpenAPI directly from the client gets the first screen up quickly. Run it for a few days, though, and the same kinds of incidents repeat — leaked keys, surprising result codes, protocol mismatches. So putting a single BFF layer of your own between the client and the external API is almost always the right call.
1. Four failures direct calls invite
| Failure | What happens with direct calls | What a BFF layer does |
|---|---|---|
| Key exposure | serviceKey ends up in the client bundle — a public secret |
Lives in server env vars; client can't see it |
| Result-code reading | HTTP 200 with resultCode=90 empty body → screen quietly shows empty cards |
BFF normalises 200 + resultCode≠0 to a 5xx |
| Protocol mismatch | "Docs say HTTPS" but only HTTP actually works | BFF calls HTTP; client only ever sees HTTPS |
| Schema drift | Vendor flips camelCase ↔ snake_case → client breaks | BFF normalises to one shape |
2. The most common trap — key propagation lag
After a public API approves your key, propagation through their gateways can take time. The same key can power dataset A immediately while dataset B stays dark for an hour. With direct calls that means "empty card on the launch day of any new dataset." With a BFF:
[Client] ──▶ [BFF /api/x]
│
▼
Validate resultCode
│
┌────────┴────────┐
▼ ▼
OK → transform·cache 90 → "Preparing" label
Explicitly showing "preparing" is far more reliable than an empty screen.
3. Five jobs the BFF takes on
- Key custody — one place in env. One place to rotate.
- Result-code normalisation — vendor's
resultCode/status/code→ HTTP status or a consistent{ok, data, error}. - Protocol normalisation — HTTP-only · self-hosted · standard-gateway quirks absorbed by the BFF.
- Cache policy — same query 10 times a second from the client = one call externally. Cache-Control headers live in the BFF.
- Legacy compatibility — when vendor moves v1 → v2, you change the BFF; clients keep working.
4. Before / after
| Aspect | Direct calls | BFF layer |
|---|---|---|
| Key location | Client bundle (exposed) | Server env (isolated) |
| Day 1 of new dataset | Empty card / console error | "Preparing" label |
| Vendor outage | Screen down | Last cache or explicit message |
| Vendor schema change | Edits scattered | One BFF file |
| Call-rate control | Per client | One BFF spot |
5. Adopting this in your own project
- Find every place the client directly hits an external domain (
fetch('https://external')grep). - For each call site, add a one-line
/api/xBFF —proxy + normalise + cache header. - Collapse the vendor's result codes into a single shape (HTTP status or
{ok,error}). - Move keys to
.env.*and delete them from client code. - When onboarding a new dataset, default to the "preparing" branch and flip to normal only after verification.
External APIs are outside your control, but where to absorb their quirks is your choice. The BFF layer is that choice.