概要
BFFって聞くけど、なんやねんって思う人いますよね💦
ベテランさんとかもなかなか教えてくれたりしないです。
前は「新人は使えない」「なんでこんなのも知らないの?」みたいな技術マウントを
取られることなどよくありました。そう言っている割に教えてくれなかったり、
やたら難しい用語で説明してきたりで、ちんぷんかんぷんでした。
なのでそうならないように小学生にもわかるレベルで、解説していきます。
今回はRails,Hono,Next.jsで実装してますが、
皆さんが使っている言語など置き換えて学んでくださいね!
AI時代だからこそ設計を学ぶ
コードを書く価値は無くなってきたので、設計を学ぶ意義は大きいと思います!
設計を語れる方が大事な時代だからです!
小学生にもわかる説明
「料理屋さん」で考えてみましょう!あなたが料理屋さんに入って席に座ったとします。
注文するとき、こんなことを考えませんよね!
「ドリンクは飲み物担当に、料理はシェフに、デザートはパティシエに…」
お客さんはホール係に「コース料理お願いします」と1回言うだけ。
あとはホール係が各担当に振り分けて、全部まとめてテーブルに届けてくれます。
これがBFF(backend for frontend)です。
BFFがないと何が困るの?
あなたが料理屋さんに入って席に座りました。でも、この店にはホール係がいません。
例えばマッチングアプリのプロフィール画面を作るとしたら、注文するとき、こうなります。
「名前を聞きたいから名前厨房へ…いいね数を聞きたいからいいね厨房へ…マッチング数はマッチング厨房へ…」
お客さん(Next.js)が自分で各厨房に4回取りに行かないといけないんです。
これの何が問題かというと、
- 表示が遅い 4回取りに行くから、料理が揃うまで時間がかかる
- コードが複雑 いろんな厨房に直接頼むので、Next.jsのコードが複雑になる
- 修正が大変 厨房が増えるたびに、Next.jsも修正が必要になる
お客さんはただ「プロフィール画面を見たい」だけなのに、自分で各厨房を走り回らないといけない。
これはお客さんの仕事じゃないですよね。💦
✅ BFFがあると何が変わる?
ホール係(BFF)が間に入るだけで、さっきの問題が全部解決します。
お客さん(Next.js)はホール係(BFF)に1回言うだけ。
あとはホール係が各厨房に取りに行って、全部まとめてテーブルに届けてくれます。
さっきの3つの問題がこう変わります。
| 問題 | BFFあり |
|---|---|
| 表示が遅い | ✅ 1回のリクエストで済むので速い |
| コードが複雑 | ✅ Next.jsはBFFを叩くだけでシンプル |
| 修正が大変 | ✅ 厨房が増えてもBFFだけ直せばOK |
ホール係(BFF)がいるだけで、お客さん(Next.js)はラクになる。これがBFFの役割です。
全体のアーキテクチャ
実際のアプリではこういう構成になります。
┌─────────────────┐
│ Next.js │ フロントエンド(画面)
│ port: 3000 │
└────────┬────────┘
│ HTTP
┌────────▼────────┐
│ BFF │ フロント専用API・データの取りまとめ
│ port: 4000 │
└────────┬────────┘
│ HTTP
┌────────▼────────┐
│ Rails(API) │ ビジネスロジック・DB操作
│ port: 8000 │
└────────┬────────┘
│
┌────────▼────────┐
│ DB │ PostgreSQL
└─────────────────┘
ポイントはそれぞれが隣しか知らないことです。
- Next.jsはBFFしか知らない
- BFFはRailsしか知らない
- RailsはDBしか知らない
これがクリーンに保てる理由です。
BFFに何を使う?
BFFを作るとき、何のフレームワークを使えばいいか迷いますよね。
よく候補に上がるのはこの3つです。
| Rails | NestJS | Hono | |
|---|---|---|---|
| 言語 | Ruby | TypeScript | TypeScript |
| Next.jsとの相性 | △(言語が違う) | ◎(同じTS・型共有) | ◎(同じTS・型共有) |
| 学習コスト | 低い | 高い | 低い |
| 速度 | 普通 | 普通 | 速い |
| 向いてる場面 | チームがRuby慣れ | 大規模・マイクロサービス | BFF・軽量API |
RailsをBFFにするとどうなる?
動きますが、Next.jsと言語が違うのでフロントエンジニアがBFFを触りにくくなります。また責務の分離(コードの整理整頓) が崩れやすくなります。
新人エンジニアは知らないとやばい😱 「責務の分離」ってなーに?🤔( フロントエンドで解説)
NestJSをBFFにするとどうなる?
TypeScriptで書けますが、デコレーター・モジュール・DIなど覚えることが多く、
BFFだけに使うには過剰です。
HonoがBFFに向いている理由
BFFに必要なのは「リクエストを受けて・APIを叩いて・まとめて返す」だけです。
HonoはそれをTypeScriptで最もシンプルに書けます。
この記事ではHonoを使って実装します。
ディレクトリ構成
ここからはマッチングアプリのプロフィール画面を例に、実際のコードを見ていきます。アプリ全体のディレクトリ構成はこうなります。
matching-app/
├── frontend/ # Next.js(画面)
├── bff/ # Hono(BFF)
└── backend/ # Rails(APIサーバー)
それぞれが完全に分離しています。
-
frontend/はBFFのURLしか知らない -
bff/はRailsのURLしか知らない -
backend/はDBしか知らない
次の章からそれぞれの実装を見ていきます。
1️⃣ Rails(APIサーバー)― 厨房
Railsは「厨房」 です。料理を作ることに集中します。
ホール係(BFF) が何人いるかも、お客さん(Next.js)が誰かも知りません。
ただ「作ってください」と言われたら作るだけです。
技術的に言うと、ビジネスロジックとDB操作に集中するので、
BFFのことは知りません。
module Api
module V1
class UsersController < ApplicationController
def show
user = User.find(params[:id])
render json: {
id: user.id,
nickname: user.nickname,
age: user.age,
prefecture: user.prefecture,
bio: user.bio,
thumbnail: user.thumbnail_url
}
end
end
end
end
module Api
module V1
class LikesController < ApplicationController
def count
count = Like.where(to_user_id: params[:user_id]).count
render json: { count: count }
end
end
end
end
module Api
module V1
class MessagesController < ApplicationController
def unread_count
count = Message.where(to_user_id: params[:user_id], read: false).count
render json: { count: count }
end
end
end
end
2️⃣ Hono(BFF)― ホール係
Honoは「ホール係」 です。お客さん(Next.js)から注文を受けて、
各厨房(Rails)に取りに行き、まとめて返します。
技術的に言うと、複数のAPIをまとめてNext.jsが必要な形に整形して返します。
Railsしか知りません。
import { Hono } from "hono";
const app = new Hono();
const RAILS_API = process.env.RAILS_API_URL;
// プロフィール画面用API
// Next.jsはここを1回叩くだけでOK
app.get("/bff/profiles/:userId", async (c) => {
const userId = c.req.param("userId");
// Railsの複数APIを並列で叩く
const [userRes, likeRes, messageRes] = await Promise.all([
fetch(`${RAILS_API}/api/v1/users/${userId}`),
fetch(`${RAILS_API}/api/v1/likes/count?user_id=${userId}`),
fetch(`${RAILS_API}/api/v1/messages/unread_count?user_id=${userId}`),
]);
const [user, likes, messages] = await Promise.all([
userRes.json(),
likeRes.json(),
messageRes.json(),
]);
// Next.jsが必要な形にまとめて返す
return c.json({
user: {
id: user.id,
nickname: user.nickname,
age: user.age,
prefecture: user.prefecture,
bio: user.bio,
thumbnail: user.thumbnail,
},
stats: {
like_count: likes.count,
unread_message_count: messages.count,
},
});
});
3️⃣ Next.js(フロント)― お客さん
Next.jsは「お客さん」です。ホール係(BFF)に注文するだけです。
技術的に言うと、BFFしか知りません。Railsの存在を知らなくていいです。
// frontend/app/profile/[userId]/page.tsx
type ProfileData = {
user: {
id: number;
nickname: string;
age: number;
prefecture: string;
bio: string;
thumbnail: string;
};
stats: {
like_count: number;
unread_message_count: number;
};
};
async function getProfile(userId: string): Promise<ProfileData> {
// BFFを1回叩くだけ!
const res = await fetch(
`${process.env.BFF_URL}/bff/profiles/${userId}`
);
return res.json();
}
export default async function ProfilePage({
params,
}: {
params: { userId: string };
}) {
const { user, stats } = await getProfile(params.userId);
return (
<div>
<img src={user.thumbnail} alt={user.nickname} />
<h1>{user.nickname}({user.age}歳・{user.prefecture})</h1>
<p>{user.bio}</p>
<p>もらったいいね:{stats.like_count}件</p>
<p>未読メッセージ:{stats.unread_message_count}件</p>
</div>
);
}
BFFが太りすぎるとどうなる?
BFFは「まとめて返すだけ」が役割です。
ここにビジネスロジック(アプリのルール。「自分にはいいねできない」「マッチングしていない相手にはメッセージ送れない」など)を書き始めると、「どこに何が書いてあるかわからない」状態になります。
料理屋さんで言うと、ホール係が厨房に入って料理まで作り始めるイメージです。それはシェフ(Rails)の仕事ですよね。
// ❌ これはNG:BFFにビジネスロジックを書く
app.post("/bff/likes", async (c) => {
const body = await c.req.json();
// ビジネスルールをBFFに書いてはいけない
if (body.from_user_id === body.to_user_id) {
return c.json({ error: "自分にはいいねできません" }, 400);
}
});
// ✅ これがOK:BFFはRailsに流すだけ
app.post("/bff/likes", async (c) => {
const body = await c.req.json();
const res = await fetch(`${RAILS_API}/api/v1/likes`, {
method: "POST",
body: JSON.stringify(body),
});
return c.json(await res.json(), res.status);
});
BFFのルール
- ✅ 複数APIのレスポンスをまとめる
- ✅ フロントが必要な形に整形する
- ❌ ビジネスロジックは書かない
- ❌ DBに直接アクセスしない
採用すべきでないケース
BFFは万能ではありません。状況によっては採用しない方がいいケースがあります。
🔰 APIが1〜2本しかない場合
まとめる必要がないので、BFFを挟むだけ無駄です。Next.jsから直接Railsを叩けばシンプルに保てます。
🔰 小規模・MVPで速度優先の場合
サービスの方向性が決まっていない段階でBFFを作ると、仕様変更のたびに修正箇所が増えます。まずは動くものを作ることを優先しましょう。
🔰 チームがRubyエンジニアだけの場合
TypeScriptのHonoを追加するより、RailsにBFF的な処理をまとめた方がチーム全体で管理しやすいです。
💡 例えば、金融機関の開発では、セキュリティ要件やチーム構成によってBFFをバックエンドに集約するケースもあります。BFFを採用すべきかどうかは、チームの規模・技術スタック・ドメインの複雑さで判断しましょう。
🎯 まとめ
今回はBFFについて解説しました。
| 役割 | 技術 | やること |
|---|---|---|
| 画面 | Next.js | BFFを叩くだけ |
| BFF | Hono | 複数APIをまとめて返す |
| APIサーバー | Rails | ビジネスロジック・DB操作 |
| DB | PostgreSQL | データの保存 |
BFFの2つのルール
- ✅ 複数APIのレスポンスをまとめてフロントが必要な形に返す
- ❌ ビジネスロジックは書かない・DBに直接アクセスしない
BFFを採用すべきでないケース
- APIが1〜2本しかない
- 小規模・MVPで速度優先
- チームの技術スタックに合わない
BFFは「フロントエンドのためのまとめ係」です。
料理屋さんのホール係と同じで、お客さん(Next.js)が快適に注文できるように、裏側の複雑さを隠してくれる存在です。
設計に迷ったときは「これはホール係の仕事か?厨房の仕事か?」と考えてみてください。
使用したAI
- Claude
- ChatGPT




