0. 목표
브라우저에서 아래 URL을 입력하면, 서버가 Gemini API를 호출해 받은 답변을 화면에 출력한다.
- http://localhost:5000/sendMessage?message=너에 대해서 알려줘
1. 프로젝트 구조 잡기 (프론트/백 분리)
왜 분리했나
포트폴리오(Next.js) 프론트와 Flask 백엔드는 성격이 달라서 한 폴더에 섞으면 .env, 배포, 실행 방식이 꼬이기 쉽다.
그래서 PORTFOLIO-TEST 아래에 backend/를 만들어 백엔드를 분리했다.
최종 구조
PORTFOLIO-TEST/
app/ # Next.js (프론트)
components/
public/
package.json
...
backend/ # Flask (백엔드)
app.py
.env
.gitignore
requirements.txt
.venv/

app.py
- Flask 서버 엔트리(시작점)
- /hello, /sendMessage 같은 라우트가 여기 들어감
.env
- 비밀키/환경변수 저장소
- 여기의 GEMINI_API_KEY=... 같은 값은 코드에 하드코딩하지 않기 위해 둠
- Git에 올리면 안 됨(유출 위험)
.gitignore
- 백엔드 전용 ignore
- 최소로는 .env, .venv/, __pycache__/ 같은 거 제외
requirements.txt
- 백엔드 파이썬 의존성 목록
- 다른 PC나 배포 환경에서 pip install -r requirements.txt로 동일 환경 재현
.venv/
- Python 가상환경 폴더
- 백엔드 패키지들이 시스템 파이썬이 아니라 이 폴더 안에만 설치되게 분리해줌
- Git에는 보통 올리지 않음
2. Gemini API 키 발급 & 환경변수(.env)로 관리
왜 .env로 관리하나
API Key를 코드에 하드코딩하면 GitHub에 올라가거나 캡처로 노출될 수 있다.
그래서 키는 .env에 넣고, 파이썬에서 로드해서 사용한다.
.env 예시 (backend/.env)
GEMINI_API_KEY=내_키
GEMINI_MODEL=gemini-2.5-flash
3. .gitignore로 비밀파일/로컬 파일 제외
핵심
.env와 .venv는 GitHub에 올리면 안 된다.
그래서 backend/.gitignore에 제외 규칙을 넣는다.
backend/.gitignore
.env
.venv/
__pycache__/
*.pyc
참고: __pycache__는 파이썬 실행 중 생길 수도/안 생길 수도 있는 캐시 폴더라서, 없어도 정상이다.
4. 필요한 패키지 설치 & requirements.txt 생성
설치한 것
- flask: 서버 프레임워크
- python-dotenv: .env 읽어서 환경변수로 로드
- requests: Gemini API 호출(HTTP 요청)
(backend 폴더에서)
pip install flask python-dotenv requests
requirements.txt 생성
pip freeze > requirements.txt
명령어 의미
- pip freeze: 현재 환경에 설치된 패키지+버전 목록 출력
- > requirements.txt: 그 출력을 파일로 저장
5. Flask 서버 구현: /sendMessage (GET)
과정
- /sendMessage 엔드포인트를 GET으로 만든다
- 쿼리스트링 message=로 사용자 메시지를 받는다
- 그 메시지를 Gemini API에 보내 답을 받고
- 그 답을 그대로 브라우저에 보여준다
구현 코드
backend/app.py
import os
import requests
from flask import Flask, request, Response
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
API_KEY = os.getenv("GEMINI_API_KEY")
MODEL = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")
@app.get("/sendMessage")
def send_message():
user_message = request.args.get("message", "").strip()
if not user_message:
return Response("missing query param: message", status=400, mimetype="text/plain; charset=utf-8")
if not API_KEY:
return Response("GEMINI_API_KEY not set in backend/.env", status=500, mimetype="text/plain; charset=utf-8")
url = f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent"
headers = {"Content-Type": "application/json", "x-goog-api-key": API_KEY}
payload = {"contents": [{"parts": [{"text": user_message}]}]}
r = requests.post(url, headers=headers, json=payload, timeout=30)
if r.status_code != 200:
return Response(f"Gemini API error ({r.status_code}):\n{r.text}", status=502, mimetype="text/plain; charset=utf-8")
data = r.json()
text = data["candidates"][0]["content"]["parts"][0]["text"]
return Response(text, mimetype="text/plain; charset=utf-8")
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
6. 서버 실행 & 브라우저 테스트
서버 실행
backend/ 폴더에서:
python app.py
실행되면 터미널에:
- Running on http://127.0.0.1:5000 가 찍힌다.
브라우저 테스트
- http://127.0.0.1:5000/sendMessage?message=hello
- 한글 예시:
- http://127.0.0.1:5000/sendMessage?message=너에%20대해서%20알려줘

최종결과
- /sendMessage?message=...로 요청하면
- Flask가 쿼리를 받아 Gemini API에 전달하고
- Gemini가 생성한 답변이 브라우저 화면에 출력된다.
'Study Notes' 카테고리의 다른 글
| 채팅창 입력이 AI 응답으로 돌아오기까지: 프론트엔드, 백엔드, API 호출 흐름 정리 (0) | 2026.03.15 |
|---|---|
| Flask로 HTTP 요청 이해하기: GET/POST, Query String, Content-Type (0) | 2026.02.25 |
| Git/GitHub · Vercel · DNS : ‘배포 URL’에서 ‘내 도메인’으로 (0) | 2026.02.01 |
| 포트폴리오 배포 준비 ② ㅡ Vercel 배포 & 도메인 구조 이해하기 (0) | 2026.02.01 |
| 포트폴리오 배포 준비 ① ㅡ GitHub 업로드까지 (Windows / Cursor) (0) | 2026.01.30 |