アーキテクチャ

BFFとは?API Gatewayとの違い・採用判断とアンチパターンを実装視点で解説

BFFとは?API Gatewayとの違い・採用判断とアンチパターンを実装視点で解説

BFF(Backends For Frontends)は、Webやモバイルなど画面ごとに専用のバックエンドを置く設計パターンです。読み方は「ビーエフエフ」。フロントエンドが必要とする形にデータを整えてから返す中間層で、複数のマイクロサービスを画面側で束ねる手間を肩代わりします。この記事では、BFFが解決するover-fetching/under-fetchingの課題、API Gatewayやマイクロサービスとの役割分担、どの条件なら採用に見合いどの規模では過剰投資になるのかを整理し、さらにNode.jsとGraphQLで実際に動く集約処理・タイムアウト設計・トークンの秘匿までコードつきで示します。

まとめ:BFFの本質と採用可否の結論

BFFは「1つの画面種別につき1つのバックエンド」を用意し、そのフロントエンド専用にAPIを設計する層です。複数サービスの呼び出し集約、画面に合わせたレスポンス整形、認証情報の秘匿を担い、フロントエンドの通信を1本にまとめます。パターンとしての初出は2015年9月のPhil Calçado氏の記事The Back-end for Front-end Pattern (BFF)で、同年11月にSam Newman氏がPattern: Backends For Frontendsとして定式化しました。MicrosoftもAzure Architecture CenterのBackends for Frontendsパターンで同名のパターンを扱っています(いずれも2026年9月時点で公開中)。

採用の分岐点ははっきりしています。フロントが2種類以上(Web・iOS・Androidなど)あり、背後がマイクロサービスに分かれ、画面ごとにレスポンス要件が食い違うなら、BFFは費用対効果に見合います。逆に、フロントが1つでバックエンドが単一なら、BFFは運用対象を1つ増やすだけの過剰投資でしょう。判断を誤らないための条件と、パススルー化・共有BFFの肥大化といった失敗パターンは本文で具体的に示します。

実装面の結論も先に置きます。BFFの実体は「複数の下流呼び出しを並列に投げ、画面に要る項目だけを1つのレスポンスに詰め直すHTTPサーバー」であり、素のNode.jsとExpressで数十行から書けます。難所はコードの量ではなく、下流1本あたりのタイムアウト値、部分失敗をどう縮退させるか、アクセストークンをブラウザへ渡さない置き場所の3点です。この記事の後半では、その3点をそのまま動かせる形で示します。設計から実装体制まで含めて相談したい場合は、API開発・システム連携で構成段階から検討できます。

BFFとは何か、フロントエンド専用バックエンド層が担う集約と整形の役割

BFFの位置づけは単純です。フロントエンドとマイクロサービス群の間に立ち、画面が使いやすい形に整えたAPIだけを公開します。フロント側は個々のサービスを直接知らずに済み、通信の複雑さをBFFに閉じ込められる構造です。そもそもフロントとバックの責務がどう分かれるのかが曖昧なら、フロントエンドとバックエンドの違いを先に押さえておくと、BFFがどちら側の持ち物なのかが読み解きやすくなります。

BFFの読み方とBackend for Frontendという名称が指すもの

BFFはBackend for Frontendの略で、読み方は「ビーエフエフ」です。パターン名としては複数形の「Backends For Frontends」で語られることが多く、これは「フロントエンドの種類ごとにバックエンドを分ける」という設計思想を表しています。Web用とモバイル用で必要なデータの粒度や項目は異なるため、1つの汎用APIで両方をまかなうより、画面種別ごとに専用の口を用意するという発想です。汎用の共有APIを1本置く方式とは、ここで明確に分かれます。

Newman氏の原典では、この分割の単位を「1つのユーザー体験につき1つのBFF」と表現しています。つまり分ける軸はデバイスそのものではなく体験です。iOSとAndroidで同じ画面設計・同じデータ要件なら、無理に2本へ割らず1本のモバイル用BFFで受けます。逆に同じWebでも、一般ユーザー向け画面と社内向け管理画面で必要な項目がまるで違うなら、分けたほうが変更の波及は小さくなります。

BFFがover-fetchingとunder-fetchingを解消する仕組み

画面が汎用APIを直接叩くと、2つの無駄が生まれます。1つはover-fetching。一覧画面に名前しか要らないのに、住所や購入履歴まで含む重いレスポンスを受け取ってしまう状態です。もう1つはunder-fetching。1画面を描くのに商品・在庫・レビューと複数のAPIへ何度も往復し、通信回数が膨らむ状態を指します。

BFFはこの往復とデータ選別をサーバー側に移します。画面が必要とする項目だけを、必要な数のサービスから集めて1回のレスポンスにまとめる。下流間の通信はデータセンター内で完結するため、モバイル回線をまたぐ往復が1回に減り、表示速度と実装の見通しが同時に良くなります。GraphQLを用いて画面ごとにクエリを設計する構成も、目的は同じ「画面に合わせた過不足のないデータ供給」です。

BFFの典型的な構成とNode.jsを中心とした技術スタック

実装ではNode.jsとTypeScriptの組み合わせが主流です。理由は明快で、フロントエンドと同じ言語・型定義を共有でき、画面を作るチームがそのままBFFを保守できるからです。バージョンはNode.js公式のリリース一覧でLTS表記のある系列を選びます。2026年9月時点では24系(コードネームKrypton)と22系(Jod)がLTS、26系がCurrentという並びで、本番のBFFはLTS側に寄せるのが無難です。

データ集約にGraphQLを据える構成ではApollo Serverの公式ガイドやNestJSのGraphQLクイックスタートが出発点になります。REST集約に留める構成も同じくらい多く、選択時の判断基準は下流の数と画面側の変更頻度です。GraphQLとRESTの性質の違いそのものはGraphQLとRESTの違いで整理しています。

近い機能をフレームワークが内蔵する例も増えました。Next.jsは公式ドキュメントにBackend for Frontendのガイドを持ち、Route HandlersでHTTPエンドポイントをアプリ内に定義できます。小さく始めるならフレームワーク内蔵の層で足り、独立運用が必要になった段階で専用サービスへ切り出す、という段階設計が現実的です。

API Gatewayやマイクロサービスとの役割分担で見るBFFの位置づけ

BFFはよくAPI Gatewayと混同されます。両者は隣り合う層ですが、設置の単位と目的が別物です。ここを取り違えると、Gatewayに画面ロジックを詰め込む、あるいはBFFで全社共通の認証をやり直すといった責務のねじれが起きます。

API GatewayとBFFの違いと現場での使い分けの判断軸

API Gatewayはシステム横断で1つ置き、認証・流量制御・ルーティングといった基盤機能をまとめる層です。対してBFFはフロント種別ごとに置き、その画面専用のデータ整形を担います。Gatewayは変更頻度が低い基盤側、BFFは画面に追従して頻繁に変わる層、と考えると役割が分かれます。

観点 API Gateway BFF
設置単位 システム横断で1つ フロント種別ごと
主な役割 認証・流量制御・集約 画面向けにデータ整形
配置場所 クライアントの手前 ゲートウェイの内側
変更頻度 低い(基盤寄り) 高い(画面に追従)
運用主体 インフラ・基盤チーム フロントエンドチーム

両者は排他ではなく、Gatewayの内側に各BFFを並べる構成が一般的です。認証やレート制限はGatewayに集約し、画面固有の整形はBFFに寄せる。Newman氏の原典でも、認証・認可やリクエストログのような汎用の境界処理をBFFに持たせるかは判断が割れる論点として扱われ、上流の別レイヤーへ切り出す案を本人は推しつつ、段数が増える分の遅延と開発環境の複雑化を代償として併記しています。この線引きの詳しい根拠は、APIゲートウェイの役割とリバースプロキシ・サービスメッシュとの違いで整理しています。

マイクロサービス構成におけるBFFの配置場所と責務の切り分け

BFFが力を発揮するのは、背後が複数のマイクロサービスに分かれている場合です。サービスが商品・注文・在庫と分割されているほど、画面を1つ描くための呼び出し先が増えます。その集約点をフロント側に置くと通信が煩雑になるため、画面専用のBFFに引き受けさせます。

逆に背後がモノリス1本なら、集約する相手がいないためBFFの旨味は薄いままです。自社がモノリスとマイクロサービスのどちらを採るべきか未整理なら、モノリスとマイクロサービスの違いとモジュラモノリスの整理を先に押さえると、BFF導入の是非も判断しやすくなります。BFFはあくまで分散構成を前提にした緩衝材で、クラウドネイティブなシステム構成の一部品として位置づけると設計が締まります。

SSRやNext.jsが担うBFF的な処理との重なりと切り分け

サーバーサイドレンダリング(SSR)とBFFは、機能が重なる部分があります。SSRは画面のHTMLをサーバー側で組み立てる際、複数APIからデータを集めるため、実質的にBFFの集約処理を内包します。小規模ならSSR層がBFFを兼ねて構いません。レンダリング方式そのものの違いはSSR・CSR・SSG・ISRの違いと使い分けで比較しています。

切り分けの目安は「そのデータ整形をWeb以外のクライアントも使うか」です。モバイルアプリも同じ整形結果を必要とするなら、SSR層に閉じ込めず独立したBFFへ出す。Webだけが使う整形なら、Next.jsのRoute Handlersに留めておく方が運用は軽くなります。将来モバイルが増える見込みがあるかどうかで、最初から分けるか後で切り出すかを決めます。

Node.jsとExpressでBFFを実装して複数APIを1本に集約する手順

ここからは動く形で示します。題材は「モバイルの商品詳細画面が、商品・在庫・レビューの3サービスから必要な項目だけを1回で受け取る」という定番の構図です。以下はExpress 5系での最小実装で、下流3本を並列に投げ、レビューが落ちても画面は描ける縮退まで含んでいます。

Node.jsとExpressで3つのサービスを並列に呼ぶBFFの最小実装

// bff/server.js  Node.js 24系(Active LTS)想定・Express 5系
import express from 'express';

const app = express();
const ORIGIN = process.env.SERVICES_ORIGIN;  // 例: http://internal.svc.local

async function json(path, signal) {
  const res = await fetch(ORIGIN + path, { signal });
  if (!res.ok) throw new Error(path + ' status=' + res.status);
  return res.json();
}

app.get('/bff/mobile/product/:id', async function (req, res) {
  const id = req.params.id;
  const signal = AbortSignal.timeout(800);  // 下流1本あたり800msで打ち切る

  const [product, stock, review] = await Promise.allSettled([
    json('/products/' + id, signal),
    json('/stocks/' + id, signal),
    json('/reviews/' + id + '?limit=3', signal),
  ]);

  // 商品本体だけは必須。落ちたら502で正直に返す
  if (product.status !== 'fulfilled') {
    return res.status(502).json({ error: 'product_unavailable' });
  }

  // 在庫とレビューは欠けても画面が描ける形へ縮退させる
  res.json({
    id,
    name: product.value.name,
    price: product.value.price,
    inStock: stock.status === 'fulfilled' ? stock.value.available : null,
    reviews: review.status === 'fulfilled' ? review.value.items : [],
  });
});

app.listen(3000);

読みどころは3つあります。Promise.allSettled を使っているため、下流のどれか1本が失敗しても他の結果は捨てません。AbortSignal.timeout は下流ごとの上限をBFF側で決める仕組みで、これが無いと画面の応答時間が下流の最悪値に引きずられます。そして必須と任意を分けて扱い、任意側は空配列やnullへ落とす。この3点が入っていないBFFは、下流が1本詰まっただけで画面全体が白くなります。

画面が必要とする項目だけを返すレスポンス整形とエラー時の縮退処理

整形の原則は「画面のワイヤーフレームに映っている項目だけを返す」です。商品サービスが30項目を返しても、モバイルの詳細画面が使うのが名前・価格・在庫・レビュー3件なら、BFFの出力もその4つに絞ります。転送量が減るだけでなく、下流のスキーマ変更が画面へ直接漏れなくなる副次効果があります。

縮退の設計はエラーの分類から始めます。商品本体のように欠けたら画面が成立しない下流は必須扱いにして502を返す。レビューや推薦のように無くても成立する下流は任意扱いにして空で返す。この線引きを表にしてフロントと合意しておくと、画面側の分岐実装が先に決まります。下流の障害が連鎖して全体を巻き込む段階まで来たら、サーキットブレーカーを挟んで呼び出し自体を一定時間止める判断に移ります。

GraphQLで画面ごとのクエリを設計するときのスキーマ定義の書き方

REST集約ではなくGraphQLでBFFを組むなら、最初に書くのは画面の要求そのものを写したスキーマです。次の定義は、先ほどのモバイル商品詳細画面に対応する最小のスキーマにあたります。

# bff/schema.graphql  画面が要る項目だけを型として宣言する
type Review {
  score: Int!
  body: String!
}

type Product {
  id: ID!
  name: String!
  price: Int!
  inStock: Boolean          # 在庫サービスが落ちたら null を許容する
  reviews(limit: Int = 3): [Review!]!
}

type Query {
  mobileProduct(id: ID!): Product
}

ここで inStock だけをnull許容にしている点が縮退設計の表明です。必須項目に感嘆符を付け、落ちてよい項目には付けない。この一行の判断が、そのまま先ほどのExpress実装の必須・任意の切り分けと対応します。リゾルバの実装手順やサーバーの立ち上げ方は、Apollo ServerとNestJSそれぞれの公式ドキュメントに沿うのが確実です。

BFFの認証情報の秘匿とタイムアウトを本番運用に耐える形へ整える

BFFを本番へ出すと、機能そのものより先に運用の粗さが表面化します。ブラウザにトークンを置いたまま、下流のタイムアウトを設定しないまま、障害時にどのサービスが遅いのか追えないまま。この3点はいずれも設定と数十行のコードで潰せます。

BFFにアクセストークンを置いてブラウザへ渡さない秘匿の実装

BFFの利点のひとつが、アクセストークンをサーバー側に閉じ込められることです。ブラウザにはHttpOnly Cookieでセッション識別子だけを渡し、実トークンはBFFの内部ストアから引く。この形にすると、JavaScriptからトークンを読み出せなくなります。

// bff/me.js  アクセストークンはBFFのサーバー側だけに置く
app.get('/bff/me', async function (req, res) {
  const token = await store.get(req.cookies.sid);  // HttpOnly Cookie のIDから引く
  if (!token) return res.status(401).json({ error: 'unauthenticated' });

  const upstream = await fetch(ORIGIN + '/users/me', {
    headers: { authorization: 'Bearer ' + token },
  });
  const me = await upstream.json();

  // ブラウザへ返すのは画面に要る項目だけ。トークンは返さない
  res.json({ id: me.id, displayName: me.display_name, plan: me.plan });
});

Next.jsの公式BFFガイドも、サーバー側でのみ秘密情報を扱うためにRoute Handlersを置く構図を推奨しています。ここで注意したいのは、認証そのものをBFFで作り直さないことです。トークンの発行と検証はAPI Gatewayや認可基盤の仕事で、BFFがやるのは受け取ったトークンを下流へ中継し、ブラウザへ漏らさないことに限ります。

下流のタイムアウトと再試行をBFF側で打ち切る設定値の決め方

タイムアウト値は画面の目標応答時間から逆算します。モバイル詳細画面を1.5秒で描きたいなら、BFF全体で1.2秒、下流1本あたり800ミリ秒、再試行を含めても2倍を超えないという配分になります。並列呼び出しなら下流の上限がそのまま全体の上限に近づくため、直列に積み上げない設計が前提です。

// bff/callDownstream.js  一時障害だけを1回拾い直す
export async function callDownstream(url, requestId, attempt = 0) {
  try {
    const res = await fetch(url, {
      signal: AbortSignal.timeout(800),
      headers: { 'x-request-id': requestId },
    });
    if (res.status === 503 && attempt === 0) throw new Error('retryable');
    return await res.json();
  } catch (err) {
    if (attempt === 0) return callDownstream(url, requestId, 1);  // 再試行は1回まで
    throw err;
  }
}

再試行は1回までに固定しています。回数を増やすと、下流が詰まっているときにBFFが負荷を上乗せする側へ回るためです。あわせて x-request-id を毎回付けて回すと、画面で発生した遅延がどの下流で生じたのかをログ横断で追えます。この1本のヘッダーが無いだけで、障害時の切り分けは推測作業に変わります。

BFFを採用すべき要件と過剰投資になる場面を分ける損益分岐の基準

BFFは万能の構成ではありません。運用するサービスが1つ増える以上、それに見合うだけの複雑さが背後にあって初めて元が取れます。ここでは採用に踏み切る条件と、逆に見送るべき基準を条件付きで言い切ります。

BFFの導入が費用対効果にしっかり見合うと言える3つの必要条件

次の条件が重なるほど、BFFの投資対効果は高くなります。

  • フロントが2種類以上ある(Web・iOS・Androidなど)。画面ごとにデータ要件が食い違い、汎用APIでは両立が苦しい。
  • 背後がマイクロサービスに分割され、1画面あたりの呼び出し先が3つ以上に及ぶ。
  • フロントエンドチームが自分たちでBFFを保守できる体制がある(言語・型を共有し、リリースを独立させたい)。

3つとも当てはまるなら、BFFはフロントの開発速度と通信効率を同時に引き上げます。実務ではまずこの3条件を満たすかどうかだけを確認すれば十分でしょう。設計や実装体制まで含めて外部の技術パートナーと詰めたい場合は、API開発・システム連携の相談窓口で構成段階から検討できます。

小規模なシステム構成においてBFFが過剰投資になる見送りの基準

採用しない判断も同じくらい大切です。フロントが1つしかなく、バックエンドも単一のアプリケーションなら、BFFは導入しません。集約する相手がおらず、整形もアプリ内で完結するため、BFFは単に監視・デプロイ・障害対応の対象を1つ増やすだけの負債になります。

スタートアップの立ち上げ期も見送り側です。仕様が固まらないうちに層を分けると、変更のたびにフロント・BFF・サービスの3箇所を直すことになり、開発が遅くなります。この段階ではNext.jsのRoute Handlersなどフレームワーク内蔵の層でBFF的な処理を代用し、フロントが2種類目に増えた時点で独立サービスへ切り出す。過剰な先回りは避けます。

BFFの運用で陥りやすい3つのアンチパターンとその回避の指針

導入後に価値を削るのは、次の3つの崩れ方です。

  • パススルー化。BFFが整形も集約もせず、受けたリクエストをそのまま下流へ流すだけになる。この状態なら層を挟む意味がなく、削除の対象です。
  • 共有BFFの肥大化。全画面向けの処理を1つのBFFに集めた結果、フロント種別ごとの独立性が失われ、変更が全画面に波及する。フロント単位で分ける原則に戻します。
  • ビジネスロジックの流入。値引き計算や在庫引当といった業務ルールをBFFに書き始め、下流サービスと責務が二重化する。BFFは整形と集約に徹し、業務ロジックはサービス側へ返します。

回避の指針は一貫しています。BFFに置くのは「その画面のための整形と集約」だけ。判断に迷う処理が出たら、それを他のクライアントも必要とするか、業務ルールそのものかを問い、どちらかに該当すればBFFの外へ出します。コードレビューでは「このハンドラは下流を2本以上呼んでいるか」「返却フィールドを絞っているか」の2問を機械的に当てると、パススルー化を早期に見つけられます。

BFFの導入検討でよく寄せられる質問と実装・判断の観点からの回答

BFFの導入検討で実際に挙がる質問を、実装と判断の観点から5つ整理します。

BFFとAPI Gatewayはどちらを先に導入すべきですか?

基盤としての認証・流量制御が課題なら先にAPI Gatewayを、画面ごとのデータ整形が課題なら先にBFFを検討します。多くの現場ではGatewayを外周に置き、その内側にBFFを並べる順序が収まりやすい構成です。両方を同時に立てる必要はなく、痛みの大きい側から入れて構いません。

BFFはNode.js以外の言語でも実装できますか?

可能です。GoやKotlin、Javaで実装する現場もあります。Node.jsとTypeScriptが好まれるのは、フロントエンドと言語・型を共有でき、画面を作るチームがそのままBFFを保守できるためでしょう。保守の担い手が誰かで言語を選ぶと、運用の負荷が下がります。

BFFとGraphQLは何が違うのですか?

BFFは設計パターン、GraphQLはデータ取得のための技術です。両者は競合せず、BFFの実装手段としてGraphQLを選ぶ関係にあります。GraphQLなら画面ごとに必要な項目だけをクエリで指定でき、over-fetchingの抑制が可能です。下流が3〜4本でスキーマ変更も少ないなら、REST集約でBFFを組む選択肢も十分に残ります。

フロントエンドが1つでもBFFは必要ですか?

原則として不要です。フロントが1種類で背後も単一なら、集約も整形もアプリ内で完結し、BFFは運用対象を増やすだけになります。ただしブラウザにアクセストークンを置きたくないという理由だけでサーバー側の層が要る場合は、Next.jsのRoute Handlersのような内蔵の層で受けるのが軽い解です。将来モバイルアプリなど2種類目のフロントが加わる見込みが立った時点で、独立したBFFへ切り出す判断が現実的でしょう。

BFFを導入すると開発体制はどう変わりますか?

フロントエンドチームがBFFの保守も担う体制へ寄ります。画面の変更に合わせてBFFのAPIも同じチームが直せるため、バックエンドチームへの依頼待ちが減る点が利点です。一方でデプロイ・監視の対象が増えるので、少人数のチームでは兼務の負荷を見積もったうえで導入します。BFFごとに応答時間とエラー率のダッシュボードを1枚用意しておくと、増えた運用負荷を可視化したまま回せます。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.06 テックブログ 大和証券の不正アクセスと約11万人分の口座番号:問い合わせ管理の委託先に残さない設計
  2. 2026.10.06 テックブログ 焼肉きんぐの不正アクセスと1,078万件の会員情報|全件規模の流出を防ぐAPIとログの点検
  3. 2024.06.11 コラム 個人情報漏えい件数の推移をグラフで解説|最新データと過去最多(約1.9万件)
  4. 2026.10.05 テックブログ WSL Containersとは?wslcの使い方とDocker Desktopとの使い分け【WSL 3.0.1時点】
  5. 2026.10.05 テックブログ 東京メトロ(メトポ)の不正アクセスと約5.9万件のメールアドレス|配信停止リストを残さない設計

RELATED POSTS 関連記事

目次