BFF (Backend For Frontend) 란?
프론트엔드를 위한 전용 백엔드 서버다.
일반적으로 프론트엔드는 백엔드 API를 직접 호출하지만, 프로젝트가 커지면 몇 가지 문제가 생긴다.
- 하나의 화면을 그리기 위해 API를 3~4개씩 호출해야 한다
- 백엔드 응답에 프론트가 쓰지 않는 필드가 잔뜩 들어있다
- CORS, 인증 토큰 처리를 프론트에서 직접 해야 한다
BFF는 이 사이에 끼어서 프론트가 편하게 쓸 수 있는 API를 만들어주는 중간 서버다.
# 일반 구조
브라우저 → 백엔드 API
# BFF 구조
브라우저 → BFF 서버 → 백엔드 API
BFF가 하는 일
| 역할 | 설명 |
|---|---|
| API 조합 | 여러 백엔드 API를 호출해서 하나로 합쳐 내려줌 |
| 데이터 가공 | 프론트에 필요한 필드만 골라서 응답 |
| 프록시 | CORS 우회, 인증 헤더 자동 부착 |
| 인증 처리 | 로그인/토큰 갱신 등을 BFF에서 관리 |
간단 예시
프론트에서 대시보드 화면을 그리려면 유저 정보 + 알림 목록 + 통계 데이터가 필요하다고 가정하자.
BFF 없이:
// 프론트에서 3번 호출
const user = await fetch('/api/user/me')
const alerts = await fetch('/api/alerts')
const stats = await fetch('/api/dashboard/stats')
BFF 있으면:
// 프론트에서 1번 호출
const dashboard = await fetch('/bff/dashboard')
// BFF 서버 (Node.js)
app.get('/bff/dashboard', async (req, res) => {
const [user, alerts, stats] = await Promise.all([
fetchFromBackend('/api/user/me'),
fetchFromBackend('/api/alerts'),
fetchFromBackend('/api/dashboard/stats'),
])
res.json({
userName: user.name,
alertCount: alerts.length,
recentAlerts: alerts.slice(0, 5),
totalCount: stats.total,
})
})
프론트 입장에서는 호출 한 번이면 끝이고, 필요한 데이터만 깔끔하게 내려온다.
모노레포에서 BFF는 어떻게 쓰일까?
모노레포(Monorepo)는 여러 프로젝트를 하나의 저장소에서 관리하는 방식이다. 프론트엔드 앱이 여러 개 있을 때, 각 앱마다 API 호출 로직을 따로 만들면 중복이 심해진다.
일반적인 모노레포 + BFF 구조
monorepo/
├── apps/
│ ├── console/ # 메인 콘솔 앱 (Vue)
│ ├── auth/ # 인증 앱 (로그인/회원가입)
│ └── admin/ # 관리자 앱
├── packages/
│ ├── shared-ui/ # 공통 컴포넌트
│ ├── api-client/ # API 호출 함수 모음
│ └── types/ # 공통 타입 정의
├── bff/
│ └── server/ # BFF 서버 (Express/Fastify)
├── pnpm-workspace.yaml
└── package.json
API 호출 흐름
Vue 컴포넌트
→ packages/api-client의 request 함수
→ BFF 서버 (프록시)
→ 실제 백엔드 API
각 단계를 예시로 보면:
1단계: 컴포넌트에서 호출
<script setup>
import { getDeviceList } from '@packages/api-client'
const devices = ref([])
onMounted(async () => {
devices.value = await getDeviceList()
})
</script>
2단계: api-client 패키지
// packages/api-client/device.js
import { request } from './request'
export const getDeviceList = () => {
return request.get('/api/devices')
}
3단계: BFF 프록시
// bff/server/routes/devices.js
router.get('/api/devices', async (req, res) => {
const token = req.headers.authorization
const response = await axios.get(`${BACKEND_URL}/devices`, {
headers: { Authorization: token },
})
// 프론트에 필요한 필드만 가공해서 내려줌
const devices = response.data.map((d) => ({
id: d.device_id,
name: d.device_name,
status: d.is_active ? 'active' : 'inactive',
}))
res.json(devices)
})
인증 처리 흐름 (IAM 리다이렉트 방식)
모노레포에서 인증 앱이 분리되어 있는 경우, 보통 이런 흐름을 따른다:
- 사용자가 콘솔 앱 접속
- 토큰이 없으면 → 인증 앱(IAM)으로 리다이렉트
- 인증 앱에서 로그인
- 토큰 발급 후 → 콘솔 앱으로 리다이렉트 (토큰 포함)
- 콘솔 앱이 토큰 저장 → 이후 BFF 요청 시 헤더에 포함
// 간단한 인증 가드 예시
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('access_token')
if (!token && to.meta.requiresAuth) {
// 인증 앱으로 리다이렉트
window.location.href = `${IAM_URL}/login?redirect=${window.location.href}`
return
}
next()
})
pnpm vs npm — 뭐가 다를까?
모노레포를 쓰면서 pnpm을 처음 접했는데, npm과 꽤 다르다.
핵심 차이
| npm | pnpm | |
|---|---|---|
| node_modules 구조 | 플랫(flat) — 모든 패키지가 최상위에 깔림 | 심볼릭 링크 — 실제 파일은 글로벌 store에 한 벌만 저장 |
| 디스크 사용량 | 프로젝트마다 중복 설치 | 하드링크로 공유, 디스크 절약 |
| 유령 의존성 | 발생함 (직접 설치 안 한 패키지도 import 가능) | 차단됨 (직접 선언한 것만 접근 가능) |
| 모노레포 지원 | workspaces 있지만 기본적 | 네이티브 workspace 지원이 강력 |
| 속도 | 보통 | 빠름 (캐시 + 하드링크) |
유령 의존성(Phantom Dependency)이 뭔데?
npm의 가장 큰 문제 중 하나다.
// package.json에 axios만 설치했다고 가정
{
"dependencies": {
"axios": "^1.0.0"
}
}
axios가 내부적으로 follow-redirects를 사용한다. npm은 node_modules를 flat하게 깔기 때문에:
node_modules/
├── axios/
└── follow-redirects/ ← axios의 의존성인데 최상위에 깔림
그래서 내가 follow-redirects를 직접 설치하지 않았는데도 import할 수 있다. 이게 유령 의존성이다. axios가 버전업하면서 이 패키지를 안 쓰게 되면? 내 코드가 갑자기 깨진다.
pnpm은 이걸 구조적으로 막는다:
node_modules/
├── .pnpm/ ← 실제 패키지는 여기에
│ ├── axios@1.0.0/
│ └── follow-redirects@1.15.0/
└── axios -> .pnpm/axios@1.0.0 ← 심볼릭 링크 (내가 설치한 것만 접근 가능)
follow-redirects는 최상위에 링크가 없으므로 import 자체가 안 된다.
pnpm workspace 설정
모노레포에서 pnpm을 쓸 때는 루트에 pnpm-workspace.yaml을 만든다:
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- 'bff/*'
패키지 간 참조도 간단하다:
// apps/console/package.json
{
"dependencies": {
"@packages/api-client": "workspace:*",
"@packages/shared-ui": "workspace:*"
}
}
workspace:*는 "이 모노레포 안에 있는 패키지를 쓰겠다"는 뜻이다. npm registry에서 받는 게 아니라 로컬 패키지를 직접 참조한다.
정리
- BFF는 프론트 전용 백엔드. API 조합, 데이터 가공, 인증 처리를 중간에서 해준다.
- 모노레포에서 BFF를 쓰면 여러 앱이 하나의 BFF를 공유하면서 API 호출 로직 중복을 줄일 수 있다.
- pnpm은 npm의 유령 의존성 문제를 해결하고, 디스크도 아끼고, 모노레포 지원도 강력하다.
'Dev > Backend' 카테고리의 다른 글
| [BFF] BFF란? express로도 가능한거야? (0) | 2026.04.03 |
|---|---|
| EJS 템플릿 엔진이란? (0) | 2026.03.03 |