이론으로만 공부했던 BFF를 실제로 해보자
왜냐면 인생은 실전이니까
이론만 공부해서 되는 건 읎어.
BFF란?
이 블로그의 다른 글: BFF API란? https://developer-dreamer.tistory.com/163
[공부] BFF API란?
BFF APIBFF: Backend-For-Frontend프론트엔드의 요구에 최적화된 백엔드의 레이어 멀티플랫폼을 지원하는 서비스에서 한 백엔드를 사용한다면, 한 백엔드가 여러 프론트엔드의 API 호출을 대응함백엔드
developer-dreamer.tistory.com
한줄요약: 백엔드 서버에서 온 날것의 API를 받아서, 프론트엔드가 먹기 좋게 잘 가공해서 보내주는 미들웨어 API
내가 구현하면서 느낀건 프론트엔드용으로 작은 서버를 만들어서, 백엔드 API를 한 번 걸러서 다시 API로 쏴주는 느낌
무슨 일이 일어나고 있나요?
이걸 그저께(14일)... 몇시더라... 한 오후 3시인가 4시인가에 시작했던 것 같아요
지금이 16일 오전 1시니까... 대충 1.5일이라 치면... 그 사이에 많은 일이 있었어요
Next.js 프로젝트 만듦 -> React에 익숙해져 있다가 오랜만에 해서 헤맴 -> 주제 고민하다 부동산 공공데이터 API 고름
-> Fastify로 BFF API 서버 구축함(이것도 참 많은 일이 있었어요) -> Swagger 안붙어서 개큰난항 겪음
-> GPT랑 Claude 유료 친구비 받으면서 일 제대로 안해서 버리고 Stack Overflow 정독 -> Swagger 붙임 끼얏호~~
-> 백엔드 API는 만들어 봤는데, DB 가져오는 API 위주로 구현했어서 API 안에서 또 API 쏘는 건 처음해봄
-> 써드파티API란뭘까......하다가 axios로 구현해냄(이것도 참 많은 일이 있었어요....) -> API에 API를 싸서 먹기 성공!!
-> 부동산 API가 1종류 만으로는 다양성이 부족해서 4개로 늘림 -> 이걸 동시에 호출하는데 Promise.all을 써봄
-> 이론으로 공부했던 Promise.all 써봐서 신남! 근데 이걸 프엔에서 어떻게 보여주지 -> 프엔 탭구현 삽질
-> 대부분은 데이터 타입 문제였던 것으로 밝혀져... -> 이제 Docker랑 CICD를 해보고 싶음!!!! -> Docker의 개큰 배신
-> 안돼서 집에 못가고 계속 카페에 처박혀 있다가 결국 집에 와서 샤워 한판 때리고 다시 고침
-> 로컬에서 Docker 이미지로 Next.js랑 Fastify 서버 동시에 띄우기 성공~~
와! 정말 많은 일이 있었죠?
요약하려고 했는데 결국 길어진 개조식 정리 버전
1. Next.js 구축 삽질
2. Fastify 서버 구축
3. Fastify 서버 구조화(server.ts 원툴에서 좀 그럴싸한 구조화로...)
4. 부동산 공공 데이터 API 독스(라고 하기에도 좀 그러한 .hwp 파일)의 오류 파티
5. 분명 비슷한 데이터이지만 속성이 모두 제각각인 부동산 API 4개 타입 공통 구조화하고 정리하기
6. API 안에서 외부 API 호출 (Third Party API with Axios)
7. 그냥 Fastify 서버 API가 될 뻔했으나 BFF API 취지에 맞게 응답 데이터 구조 뜯어고치기
8. 한 개의 로컬 API 안에서 4개의 로컬 API를 동시에 부르기
9. BFF API 응답 데이터 화면에 잘 매핑하기
10. Docker 구축하기 (+ 외않되?오류 해결하기)
이제 저는 1번부터 여러분들에게 이야기를 해볼게요...휴!
무슨 프로젝트인가요?
프로젝트 이름은 jip(집)-finder(찾기)-SSR(서버사이드랜더링)-BFF(백엔드포프론트엔드)에요
제목: 내집구함
부제: 이 넓은 서울 땅에 내 집 하나는 있겠지...
왜 집 찾기를 골랐냐면... 저 ㄹㅇ 집 찾아야 되거든요... 이번 달 안에 지금 집에서 나가야 돼요...
BFF API 만들어보고 싶어서 공공데이터 API 중에 관련된 거 있나 했는데
전월세 실거래가 정보를 보여주는 API가 있더라구요!
세상물정도 좀 알아가고... 시세 보는 눈도 좀 키우고....
일단 시세를 알아야 적당한 가격에 계약을 할 수 있으니까요!
부동산 API 쓸거면 정보 뜯어봐야 해서 부동산 공부도 해야하고
완전 럭키비키🍀일 거 같아서 이걸로 했어요!
(진짜로..? 진짜로...ㅇㅇ 저 지금 진지함 궁서체임)
Next.js 프로젝트 시작하기
yarn create next-app jip-fnder-ssr-bff
"근본" 키워드로 프로젝트 만들어줍니다
이걸로 설치하면 너무 친절하게도 얘가 기본 세팅을 yes/no만 고르면 다 해줘요
저는 tailwind 쓰겠다고 대답해서 tailwind도 같이 깔렸습니다
tailwind를 고른 이유는?
React 쓸 때는 Styled-Component가 좋은데, 저번에 Next.js에서 써보려고 시도했다가 오류 파티났음
왜냐면 Styled-Component는 CSR에 최적화 되어있어서... SSR 중심인 Next.js에서 쓰려고 하면
서버에서 렌더링 하는 과정에서 스타일 적용이 튕기는 경우도 있고...
글고 Next.js에서 쓰려면 추가 라이브러리 깔고 설정도 해줘야 하는데, 이렇게 해도 오류가 다 잡히진 않았었어유
Next.js는 tailwind 쓰세요..ㅠㅠ
왜 React 말고 Next.js 쓰나요?
React가 제 주요 스택인데 이번에 Next.js를 고른 이유는
요즘 SEO 때문에 SSR을 중요하게 여기는 기업들이 많아져서
SSR의 정수를 좀 경험해볼 필요성이 있다고 느껴서 고르게 되었습니다
(CSR vs SSR이 뭐죠? 이 블로그의 다른 글: https://developer-dreamer.tistory.com/133 )
그리고 이 프로젝트 주제 특성상
서치가 좀 잘 걸려서 유입이 되고 다른 사람들도 정보를 확인해야 유의미한 프로젝트가 될 것이라 생각해서
SEO를 신경쓰고자 했던 것도 있습니다
yarn을 쓴 이유는?
yarn은 병렬적으로 패키지를 설치하고, npm은 순차적으로 설치합니다.
그래서 yarn이 훨씬 빨라서 사용해요!
(개발 처음 배울 때 npm 썼었는데 지금 yarn 쓰는거랑 비교하면 속도 체감이 정말 느껴짐)
글고 npm은 package-lock.json이 있기 한데 패키지 충돌이 좀 종종 일어났었고,
yarn은 yarn.lock이 좀 더 확실하게 관리해줘서 그런지 패키지 충돌이 거의 없어서 경험적인...판단도
yarn add package를 하면 자동으로 package.json과 yarn.lock을 업데이트합니다
(yarn은 처음부터 yarn.lock이 있었고, npm은 좀 뒤에 package-lock.json을 도입했다고 하네여)
그리고 이건 찾아보면서 알게 된건데
yarn은 Workspaces 기능을 제공해서 하나의 repo에서 여러 패키지를 관리할 수 있대요
저는 지금 Next.js 안에 Fastify 서버가 생겨서 유사 모노레포가 되어버렸는데 저 기능을 써봐야겠어요
그리고 그 외에 다른 키워드도 있는데
yarn why package: 특정 패키지가 왜 설치되었는지 확인
yarn global add: 전역 패키지를 안전하게 관리
yarn install --check-files: 패키지 무결성 확인(보안 개선)
why 키워드 써봤는데
C:\github\jip-finder-ssr-bff>yarn why fastify
yarn why v1.22.22
[1/4] Why do we have the module "fastify"...?
[2/4] Initialising dependency graph...
[3/4] Finding dependency...
[4/4] Calculating file sizes...
=> Found "fastify@5.2.1"
info Has been hoisted to "fastify"
info This module exists because it's specified in "dependencies".
info Disk size without dependencies: "3.27MB"
info Disk size with unique dependencies: "7.16MB"
info Disk size with transitive dependencies: "11.73MB"
info Number of shared dependencies: 21
Done in 0.51s.
걍 의존성 땜에 존재한다는 키워드가 나오긴 하는데, 정보를 자동으로 다 긁어와서 편하긴 하네여
Next.js 폴더 구조
│ .env
| .env.secret
| .gitignore
| .eslint.config.mjs
| .next-env.d.ts
| .next.config.ts
| package.json
| postcss.config.mjs
| README.md
| tailwind.config.ts
| tsconfig.json
| yarn.lock
|
|
├─public
│ │ file.svg
│ │ globe.svg
│ │ next.svg
│ │ vercel.svg
│ │ window.svg
│ │
│ └─fonts
│ PretendardVariable.woff2
│
└─src
└─app
│ docker-compose.yml
│ Dockerfile
│ favicon.ico
│ globals.css
│ layout.tsx
│ page.tsx
│
├─bff // BFF API용 Fastify 서버 폴더
│
├─node_modules
└─rent
│ page.tsx
│
├─modules
│ CommonRentItem.tsx
│ SpecificRentDetails.tsx
│
└─[id]
page.tsx
사실 Fastify 서버 구축이 주 목적이라 Next.js 프로젝트 관점에서는 굉장히 폴더 구조가 간단합니다
심지어 아직 막 만드는 중이라 머가 없음
이것도 바뀔 수 있음
프로젝트가 끝난 후 회고하는 정리글이 아니라 진행 과정을 정리하는 글이라서 그렇습니다
next.js는 파일 경로를 엔드포인트 삼아서 페이지 접속 경로가 결정된답니다
app/page.tsx 파일에 메인 페이지를 만들고
app/rent/page.tsx 파일에 BFF API로 데이터를 불러와서 보여줄 부동산 실거래 목록 페이지를 만들었어요
그리고 기본으로 적용된 글씨체가 너무 못생겨서 Pretendard를 쓰고 싶었구
로컬 폰트 파일을 활용하게 하려구 폰트 파일을 다운받아서 public 폴더에 넣었어여
UI는 이런 너낌쓰~~
자세한 코드가 궁금하다면
아직 마구 뜯어고치고 있지만...
https://github.com/Shimsuyeon/jip-finder-ssr-bff
GitHub - Shimsuyeon/jip-finder-ssr-bff: 이 넓은 서울땅에 내 집 하나는 있겠지...
이 넓은 서울땅에 내 집 하나는 있겠지... Contribute to Shimsuyeon/jip-finder-ssr-bff development by creating an account on GitHub.
github.com
저는 Fastify 이야기 할게 많아서 Next.js는 이만 줄이구
BFF API 서버, 무엇을 선택할까?
외부 서버 API의 응답을 한 번 가공해서 프론트엔드에게 주기 위해 작은 서버를 프론트엔드 안에 만들거에여
Fastify를 고른 이유는! BFF API 서버 선택에 몇 가지 요소를 고려해봤는데요
기준 | Fastify | Koa | Next.js API Routes |
속도 | 매우 빠름 Express*4배 빠름 |
빠름 Express보다 가벼움 |
상대적으로 느림 |
미들웨어 시스템 | 플러그인 방식 확장 | Koa 미들웨어 활용 | Next.js 내부에서 가능 |
API 개발 편의성 | JSON Schema + zod 활용 |
미들웨어 구조로 유연 | 간단한 API에 적합 |
배포 용이 | Docker+Kubernetes | Docker | 서버리스에 적합 |
이런 기준을 바탕으로 고민하다가 Fastify를 고르게 되었습니다!
그리고 실제 빅테크 기업에서 어떤 걸 BFF API로 쓰고 있는지 찾아봤는데 Fastify or Koa를 쓰고 있었어요
둘 중에서 Fastify가 Express보다 4배 빠르다고 해서 이걸로 고르기로 결정했습니다
그리고 배포 스택에 Docker+Kubernetes가 있어서 둘 다 용이하게 적용되는 걸 고른 것도 있습니다
Fastify 서버 시작하기
yarn add fastify cors dotenv
fastify: fastify를 서버로 사용하기 위해 패키지 설치
cors: 다른 도메인 서버와 통신할 때 cors 에러 해결하는데 유용한 패키지. 어떤 origin을 허용할 지 정할 수 있음
fastify.register(cors, {
origin: '*', // 모든 도메인 허용 (보안 고려 필요)
});
dotenv: 환경 변수를 .env 파일에서 로드함
공공데이터 api는 개인이 api 신청을 한 후 개인 인증키를 넣어야 해서 이걸 레포에 올리면 대참사가 납니다!!!
그래서 .env에 넣고 이를 불러오는 방식을 쓰는데 이때 process.env.변수명 이런식으로 쉽게 불러올 수 있어용
가끔 레포에 open ai 인증키 그대로 올린 경우들이 있다고 하는데
크롤러한테 잘못 걸리면 요금 폭탄이 나오는 안타까운 사례들이 발생할 수 있으요...
저는 src/app 폴더 아래 bff 폴더를 만들었고
이 안에 서버 세팅을 시작했어요
맨 처음에는 server.ts 파일에 모든 걸 때려 넣었습니다
마치 제가 Node.js로 백엔드라는 걸 처음 해볼 때 index.js에 모든 걸 때려넣던 때가 생각나는군요
하지만 곧 구조화를 진행했습니다!!! 저 구조화 안하는 그런 사람 아닙니다
근데 그 전에 스웨거부터 붙였어요
일단 이게 있어야 테스트 하기가 편해서요
postman도 같이 쓰긴 하는디 그래도 swagger 있는 게 나중에 로그인 토큰 필요한 api 만들 때 편함요
🥹Fastify 서버에 Swagger부터 붙이자... 그런데?
하... 참 많은 일이 있었어요...
fastify가 스웨거 패키지만 깔면 따로 어노테이션 안붙여도 swagger를 자동으로 지원한다는 거에요
그때까진 참 좋았어요... 얘도 fastAPI 같은 애구나 했죠...
근데... 근데 내가 추가한 api를 얘가 인식을 못해...왜...??
그래서 저는 일단 제 유료 친구들 GPT와 Claude에게 물어봤죠
근데 얘네도 영 감을 못잡는겨1!!!🥲
근데 저는 얼마 전의 그 일을 떠올렸어요
이 AI 친구들이 Suspense, Error boundary 처리를 위한 react-query 사용법을
최신 버전(23년 10월 출시, v5)이 아니라 구버전(22년 7월 출시, v4)으로 답변했었다는 걸.............
딱히 AI 웹 검색을 킨다고 달라지지 않앗음...
(무슨 일이 있었나요? @tanstack/react-query v4 vs v5 https://developer-dreamer.tistory.com/170)
결국 AI를 버리고 stack overflow를 찾아보는 회귀를 선택해서 해결했읍니다
아날로그가 최고?여? 덕?분에 v4 v5 차이도 알게 되고 럭?키비키🍀
이 문제에 극대노를 하다가도
음 역시 아직 ai는 인간을 못따라잡았다 라고 생각했다가도
내가 ai를 너무 믿었나? 라고 생각했다가도
아무튼 온탕 냉탕 혼자 별 생각을 다 하고 있을 때쯤 긱 뉴스가 올라왔어요
Geeknews(2025.02.15): AI가 신기술 채택을 저해함.
https://news.hada.io/topic?id=19236&utm_source=slack&utm_medium=bot&utm_campaign=T7LPJUB8R
뭔가 그런 생각을 할 때 이런 긱뉴스가 올라와서 신기했음
아무튼 저 글쓴이도 좀 편향된 생각을 가지고 있기는 한데,
아무튼 gpt는 막 처음 나왔을 때 그때까지 데이터를 많이 흡수하고 배운 상태로 출시된 모델이라
23~24년 사이에서 지식 cutoff가 존재한다는 거였음
저 글을 그대로 수용하자는 건 아닌데
AI가 지식 cutoff가 어느정도 존재하고, 내가 최신 라이브러리를 쓰고자 할때 자연스럽게 구버전 솔루션을 제시할 수 있음
그래서 그걸 무조건적으로 수용하면 남들 다 최신 쓸 때 구버전만 아는 개발자가 되어버릴 수 있다...라는 건
어느정도 팩트인 것 같아유
평소에도 gpt 코드를 100% 신뢰하는 건 아니었지만, 이번을 계기로 좀 더 신경을 쓰게 되었습니다!
Fastify에 Swagger를 붙이자!
왜!!! 나는!!!! Fastify에 Swagger를!!!! 연결 할 수 엄써!!!
뭐 그런 저런 생각도 있었지만
어쨌든 저는 늘 그랬듯 해결책을 찾았습니다
yarn add @fastify/swagger @fastify/swagger-ui
fastify에 swagger를 붙이기 위해 두 패키지를 깔아줍니다
swagger: swagger ui 없이 순수한 openAPI 문서(json)을 제공함
swagger-ui: 브라우저에서 ui로 swagger를 볼 수 있도록 지원함
그래서 뭐가 문제엿냐믄
API를 붙여도 인식을 못한다는 겁니다
// 안되는 코드임!!!!
import Fastify from 'fastify';
import swagger from '@fastify/swagger';
import swaggerUI from '@fastify/swagger-ui';
const fastify = Fastify();
// Swagger 문서 생성
fastify.register(swagger, {
swagger: {
info: {
title: 'My API',
description: 'API documentation',
version: '1.0.0',
},
},
});
// Swagger UI 추가
fastify.register(swaggerUI, {
routePrefix: '/docs', // API 문서를 '/docs' 경로에서 제공
uiConfig: {
docExpansion: 'list', // 기본적으로 모든 API 접기
deepLinking: false,
},
});
fastify.get('/hello', {
schema: {
description: 'Returns a greeting',
response: {
200: {
description: 'Successful response',
type: 'object',
properties: {
message: { type: 'string' },
},
},
},
},
}, async (request, reply) => {
return { message: 'Hello, world!' };
});
fastify.listen({ port: 5000 }, () => {
console.log('Server running on http://localhost:3000/docs');
});
맨 처음 만들었던 서버 구조가 대충 이랬는데
/hello api가 화면에 안떠!!
ai가 큰 도움이 못되면... 손수 찾아야지
휴! 역시 킹 황 stackoverflow는 답을 알고 있다
How to setup @fastify/swagger and @fastify/swagger-ui with TypeScript?
I new to Fastify and I am trying to setup swagger documentation with it. I am using TypeScript and all the examples that I found were using JavaScript and require syntax. I tried to follow the exam...
stackoverflow.com
올해 최고의 글(2월 밖에 안됐지만)로 추천합니다
위 글을 참고해서 아까 안된다고 했던 서버 코드를 뚝딱뚝딱 뜯어봤습니다
위에 swagger나 swagger-ui에 들어가는 option은 전혀 상관이 없고
API를 등록하는 부분이 문제였습니다
fastify.get()을 루트 레벨에서 직접 호출했기 때문에 Swagger UI가 얘를 감지 못하는 거였어요
Swagger UI는 Fastify가 시작될 떄 등록된 엔드포인트 목록을 기반으로 문서를 생성합니다
fastify.register() 안에서 API를 등록하고, 내부에서 API를 정의해야
Fastify가 플러그인으로 API를 먼저 로드하고, Swagger를 정상적으로 읽을 수 있는 것이죠
하지만 fastify.get()로 루트 레벨에서 냅다 호출하면
비동기 코드 실행 순서에 따라 Swagger가 API를 인식하지 못할 수 있어요
그래서 Swagger에 register로 모든 API를 등록해둬야 인식하는거였어요
fastify.register((fastify, options, done) => {
fastify.get(
"/hello",
{
schema: {
description: "Returns a greeting",
response: {
200: {
description: "Successful response",
type: "object",
properties: {
message: { type: "string" },
},
},
},
},
},
async (request, reply) => {
return { message: "Hello, world!" };
}
);
done();
});
이렇게 내가 구현해야 하는 API를 register 안에 감싸서 만들어주면 Swagger가 인식하게 됩니다
다 만들고 done();으로 꼭 끝을 알려줘야 해요
Fastify 플러그인에서 비동기 등록을 완료했다고 알려주는 함수입니다
Fastify.register() 안에서 비동기 작업을 진행할 때
비동기 작업이 끝나면 done()을 호출해서 이 플러그인의 로딩이 끝났다구 알려줘야 합니당
나 이제 라우트 등록 끝났으니까 서버 시작하렴~~
(* Fastify 플러그인이란?: 라우트, DB 연결, 인증, Swagger 설정 등을 추가하는 방식을 말함)
fastify.register(async (fastify, options) => {
fastify.get(
"/hello",
{
schema: {
description: "Returns a greeting",
response: {
200: {
description: "Successful response",
type: "object",
properties: {
message: { type: "string" },
},
},
},
},
},
async (request, reply) => {
return { message: "Hello, world!" };
}
);
});
아니면 이렇게 async를 먹여서 done()으로 끝을 명시하지 않고 비동기 처리를 할 수도 있어요
<작동하도록 수정한 전체 코드>
import Fastify from "fastify";
import swagger from "@fastify/swagger";
import swaggerUI from "@fastify/swagger-ui";
const fastify = Fastify();
// Swagger 문서 생성
fastify.register(swagger, {
swagger: {
info: {
title: "My API",
description: "API documentation",
version: "1.0.0",
},
},
});
// Swagger UI 추가
fastify.register(swaggerUI, {
routePrefix: "/docs", // API 문서를 '/docs' 경로에서 제공
uiConfig: {
docExpansion: "list", // 기본적으로 모든 API 접기
deepLinking: false,
},
});
fastify.register(async (fastify, options) => {
fastify.get(
"/hello",
{
schema: {
description: "Returns a greeting",
response: {
200: {
description: "Successful response",
type: "object",
properties: {
message: { type: "string" },
},
},
},
},
},
async (request, reply) => {
return { message: "Hello, world!" };
}
);
});
fastify.listen({ port: 5000 }, () => {
console.log("Server running on http://localhost:3000/docs");
});
이건 위의 잘못된 코드를 작동하도록 수정한거고,
How to setup @fastify/swagger and @fastify/swagger-ui with TypeScript?
I new to Fastify and I am trying to setup swagger documentation with it. I am using TypeScript and all the examples that I found were using JavaScript and require syntax. I tried to follow the exam...
stackoverflow.com
아까 위에서 언급한 이 링크 속 답변 코드가 좀 더 구조화가 잘 되어 있어서
이 코드를 참고해도 좋을 것 같아용
저 답변에 이 답글이 달려있는데 너무 웃김ㅋㅋㅋㅋㅋㅋ
GPT4o는 반성해라...
그래도 이런 게 있어서 한 번씩 정신 차리고 코딩하는 것 같아요 ^^ 럭키비키🍀
이 다음에는 Fastify 구조화 어떻게 했는지랑 Docker 어떻게 했는지 등등 이어서 설명할게용
작성: 2025.02.16. 오후 1:39
'개발자 강화 > 프론트엔드' 카테고리의 다른 글
[개발][BFF 도전기] Docker 이미지 빌드 - ECR로 push - EC2 pull해서 실행 (0) | 2025.02.18 |
---|---|
[개발][BFF 도전기] Fastify 서버 - 폴더 구조화 (0) | 2025.02.18 |
🌟[매일메일] 이벤트 전파(Event Propagation)란? (FE.250106) (0) | 2025.02.14 |
[개발, 매일메일] @tanstack/react-query v4/v5 차이...(suspense, error-boundary 처리) (0) | 2025.02.14 |
[매일메일] 민감한 데이터는 어디에 저장해야 할까? (FE.250211) (0) | 2025.02.11 |