도입부
오늘은 FastAPI를 이용해 Customer 기능을 구현하고, Streamlit 프런트엔드와 연결해 고객 정보를 등록·조회·수정·삭제하는 전체 흐름을 실습했다.
이전에는 Product 기능을 중심으로 백엔드와 프런트엔드의 연결 구조를 익혔다면, 오늘은 그 구조를 Customer 기능에 직접 적용해 보았다. 단순히 API 하나를 호출하는 데서 끝나지 않고 스키마, 서비스, 라우터, 클라이언트, Streamlit 화면이 어떻게 연결되는지 확인할 수 있었다.
FastAPI Customer 기능 구현
Customer 기능은 역할에 따라 파일을 나누어 구성했다.
- 스키마: 요청과 응답 데이터의 형식 정의
- 서비스: 고객 데이터 처리
- 라우터: HTTP 요청을 받아 서비스 함수 호출
- main.py: Customer 라우터를 FastAPI 애플리케이션에 등록
이렇게 역할을 나누면 한 파일에 모든 코드를 작성하는 것보다 구조를 이해하고 관리하기 쉽다.
전체적인 백엔드 흐름은 다음과 같다.
클라이언트 요청
→ Customer Router
→ Customer Service
→ 처리 결과 반환
→ Pydantic 응답 스키마로 검증
Pydantic 스키마의 역할
Pydantic 스키마는 API에서 주고받는 데이터의 규칙을 정한다.
Customer 데이터에는 아이디, 비밀번호, 이름, 나이 등의 정보가 들어간다. 하지만 고객 정보를 조회하거나 등록 결과를 반환할 때는 비밀번호가 노출되면 안 된다.
따라서 요청용 스키마와 외부 공개용 응답 스키마를 구분했다.
요청 데이터
id, pwd, name, age
응답 데이터
id, name, age
이를 통해 비밀번호는 입력받아 처리하되 API 응답에는 포함되지 않도록 만들었다. 스키마가 단순히 데이터 형식을 검사하는 기능뿐만 아니라, 외부에 공개할 공개할 정보를 제한하는 역할도 한다는 것을 이해했다.
REST API와 Customer CRUD
오늘 구현한 Customer API는 HTTP 메서드에 따라 기능이 구분된다.
기능HTTP 메서드API 주소
| 고객 등록 | POST | /customers |
| 고객 전체 조회 | GET | /customers |
| 고객 한 명 조회 | GET | /customers/{customer_id} |
| 고객 정보 수정 | PUT | /customers/{customer_id} |
| 고객 삭제 | DELETE | /customers/{customer_id} |
같은 /customers 주소를 사용하더라도 HTTP 메서드에 따라 서로 다른 작업을 수행할 수 있다.
- POST: 새로운 데이터 등록
- GET: 데이터 조회
- PUT: 기존 데이터 수정
- DELETE: 데이터 삭제
특정 고객을 대상으로 작업할 때는 주소에 고객 ID를 경로 매개변수로 전달했다.
GET /customers/id01
PUT /customers/id01
DELETE /customers/id01
이를 통해 REST API에서 자원과 기능을 주소와 HTTP 메서드로 표현하는 방식을 다시 익혔다.
공통 API 요청 함수 활용
프런트엔드에서는 각 페이지에서 직접 HTTP 요청을 작성하지 않고 customer_client.py에 Customer 관련 요청 함수를 모았다.
from core.api_client import request
기존 Product 기능에서 사용하던 공통 request() 함수를 그대로 활용해 프로젝트의 작성 방식을 통일했다.
def customer_create(customer: dict):
return request("POST", "/customers", json=customer)
def customer_delete(customer_id: str):
return request("DELETE", f"/customers/{customer_id}")
def customer_update(customer_id: str, customer: dict):
return request(
"PUT",
f"/customers/{customer_id}",
json=customer,
)
def customer_select_all():
return request("GET", "/customers")
def customer_select(customer_id: str):
return request("GET", f"/customers/{customer_id}")
처음에는 HTTP 클라이언트의 post() 메서드를 직접 호출하는 형태를 생각했지만, 기존 product_client.py의 구조를 다시 확인한 뒤 공통 request() 함수를 사용하는 방식으로 수정했다.
기능은 같더라도 기존 프로젝트의 구조와 규칙을 먼저 확인해야 코드의 일관성을 유지할 수 있다는 점을 배웠다.
Streamlit 고객 등록 화면 제작
고객 등록 페이지인 07_customer_create.py를 만들었다.
Streamlit의 입력 컴포넌트를 이용해 다음 정보를 입력받았다.
- 고객 ID
- 비밀번호
- 이름
- 나이
비밀번호 입력창에는 다음 옵션을 적용했다.
customer_pwd = st.text_input(
"PASSWORD",
type="password",
)
type="password"를 지정하면 사용자가 입력한 비밀번호가 화면에 그대로 노출되지 않는다.
등록 버튼을 누르면 입력값을 딕셔너리로 만든 뒤 customer_create() 함수에 전달했다.
customer = {
"id": customer_id,
"pwd": customer_pwd,
"name": customer_name,
"age": int(customer_age),
}
이 데이터는 다음 과정을 거쳐 처리된다.
Streamlit 입력
→ customer_create()
→ 공통 request()
→ POST /customers
→ FastAPI Customer Router
→ 응답 결과 출력
등록 결과에는 비밀번호가 제외된 고객 정보만 표시되는 것도 확인했다.
고객 조회·수정·삭제 화면 제작
08_customer_select.py에서는 Customer CRUD의 나머지 기능을 하나의 화면에 구현했다.
전체 고객 조회
customers = customer_select_all()
st.dataframe(customers, use_container_width=True)
FastAPI에서 받은 고객 목록을 st.dataframe()으로 출력했다. 목록에는 고객 ID, 이름, 나이만 표시되고 비밀번호는 포함되지 않았다.
고객 한 명 조회
조회할 고객 ID를 입력받아 해당 고객의 정보를 JSON으로 출력했다.
selected_customer = customer_select(customer_id)
st.json(selected_customer)
고객 정보 수정
수정할 고객 ID와 새로운 이름, 나이를 입력받아 딕셔너리로 전달했다.
customer = {
"name": update_name,
"age": int(update_age),
}
이 데이터는 PUT /customers/{customer_id} 요청으로 백엔드에 전달된다.
고객 삭제
삭제할 고객 ID를 입력받고 customer_delete() 함수를 호출했다.
result = customer_delete(delete_customer_id)
이를 통해 Streamlit 버튼 클릭이 FastAPI의 DELETE 요청으로 연결되는 과정까지 확인했다.
Streamlit 멀티페이지 내비게이션 연결
새로 만든 고객등록 및 고객조회 페이지를 app.py에 연결했다.
customer_create_page = st.Page(
"app_pages/07_customer_create.py",
title="고객등록",
icon="📝",
)
customer_select_page = st.Page(
"app_pages/08_customer_select.py",
title="고객조회",
icon="📝",
)
페이지 객체를 로그인 상태에서 사용할 pages 목록과 사이드바에 각각 추가했다.
이 과정에서 처음에는 고객등록 페이지 경로를 다음과 같이 작성했다.
"app_pages07_customer_create.py"
app_pages와 파일명 사이의 /가 빠진 것을 확인하고 아래처럼 수정했다.
"app_pages/07_customer_create.py"
파일이 실제로 존재하더라도 경로 문자열이 정확하지 않으면 Streamlit이 페이지를 찾을 수 없다. 작은 문자 하나도 실행 결과에 영향을 줄 수 있으므로 파일 경로를 꼼꼼하게 확인해야 한다는 점을 다시 느꼈다.
최종 기능 점검
마지막으로 다음 기능을 하나씩 실행하며 연결 상태를 확인했다.
- Customer 등록
- Customer 전체 조회
- Customer 한 명 조회
- Customer 정보 수정
- Customer 삭제
- Streamlit 내비게이션 연결
- 요청 데이터와 Pydantic 스키마의 필드 일치 여부
- 프런트엔드와 백엔드의 API 주소 일치 여부
- 응답 데이터에서 비밀번호가 제외되는지 확인
오늘 완성한 프런트엔드 구조는 다음과 같다.
frontend/
├─ app.py
├─ core/
│ └─ api_client.py
├─ clients/
│ └─ customer_client.py
└─ app_pages/
├─ 07_customer_create.py
└─ 08_customer_select.py
현재 실습에서는 테스트용 데이터를 사용했기 때문에 수정이나 삭제 요청이 성공하더라도 변경 내용이 영구적으로 저장되지 않을 수 있다. 실제 데이터베이스를 연결하면 서비스 계층에서 데이터를 저장하고 수정하는 로직이 추가되어야 한다.
오늘의 회고
오늘 실습을 통해 프런트엔드와 백엔드는 각각 따로 움직이는 코드가 아니라, 정해진 데이터 형식과 API 주소를 기준으로 연결된다는 것을 이해할 수 있었다.
Streamlit에서 버튼을 한 번 누르면 내부에서는 클라이언트 함수, 공통 요청 함수, FastAPI 라우터, 서비스 함수, Pydantic 응답 스키마가 차례대로 동작한다. 이전에는 화면에 결과가 나오는 것만 확인했다면, 이제는 그 결과가 어떤 파일과 함수를 거쳐 만들어지는지 조금 더 구조적으로 볼 수 있게 되었다.
특히 기존 Product 코드의 구조를 참고해 Customer 기능을 구현하면서, 새로운 기능을 만들 때는 이미 작성된 코드의 규칙을 먼저 파악하는 것이 중요하다는 점을 배웠다.
앞으로는 존재하지 않는 ID나 빈 입력값이 전달됐을 때 사용자에게 이해하기 쉬운 메시지를 보여주는 예외 처리를 보완하고 싶다. 이후 실제 데이터베이스까지 연결한다면 오늘 만든 CRUD 구조가 어떻게 확장되는지도 함께 복습할 예정이다.
'AI 오케스트레이션 캠프 > 일차별 회고' 카테고리의 다른 글
| 17일차 회고|회원 인증·ERD와 Supabase 협업|AI 오케스트레이션 개발자 국비지원 (0) | 2026.08.03 |
|---|---|
| 16일차 회고|회원 인증·예외 처리와 상품 CRUD 통합|AI 오케스트레이션 개발자 국비지원 (0) | 2026.07.31 |
| 14일차 회고|Streamlit·FastAPI CRUD 연동|AI 오케스트레이션 개발자 국비지원 (0) | 2026.07.29 |
| 13일차 회고|Streamlit·FastAPI API 연동과 팀 프로젝트|AI 오케스트레이션 개발자 국비지원 (0) | 2026.07.28 |
| 12일차 회고|Streamlit Session State·Form·데이터 시각화|AI 오케스트레이션 개발자 국비지원 (0) | 2026.07.27 |
댓글