프로그래머가 되는 꿈
FastAPI를 사용한 파이썬 웹 개발(CHAPTER 5) 본문
FastAPI를 사용하면 아주 쉽게 애플리케이션을 만들 수 있다. 하지만 애플리케이션 구조와 기능이 복잡해지면 애플리케이션을 적절하게 구조화해야 한다.
구조화란? 애플리케이션 컴포넌트를 형식에 맞춰 정리하는 것
여기서 형식을 모듈이라고 하며 모듈화(modular)는 애플리케이션 코드와 콘텐츠의 가독성을 높여준다. 적절히 구조화된 애플리케이션은 개발 속도와 디버깅 속도를 빠르게 하고 전체적인 생산성도 향상시킨다.

CHAPTER 5에서 다루는 내용은 다음과 같다.
- 애플리케이션 라우트와 모델 구조화
- 플래너 API용 모델 구현
5.1 FastAPI 애플리케이션 구조화
*이제부터 이벤트 플래너를 만들어볼 것이다. 다음과 같은 구조로 이벤트 플래너 애플리케이션을 설계한다.*
<이벤트 플래너 만들기>
(설계)
planner/
main.py
database/
__init__.py
connection.py
routes/
__init__.py
events.py
users.py
models/
__init__.py
events.py
users.py
첫 번째 단계는 애플리케이션용 폴더를 생성하는 것이다. 폴더의 이름은 planner로 한다.

planner 폴더 안에 초기 파일인 main.py를 만들고 database, routes, models라는 서브 폴더를 만든다.

__init__.py 파일을 모든 폴더 안에 만든다.

database 폴더에 connection.py라는 빈 파일을 만든다.
(이 파일은 데이터베이스 추상화와 설정에 사용되는 파일로, <CHAPTER 6 데이터베이스 연결>에서 쓰인다.)

routes와 models 폴더 모두에 다음과 같이 events.py와 users.py 두 개의 파일을 만든다.

(각 파일의 역할)
routes 폴더
events.py: 이벤트 생성,변경,삭제 등의 처리를 위한 라우팅
users.py: 사용자 등록 및 로그인 처리를 위한 라우팅
models 폴더
events.py: 이벤트 처리용 모델을 정의
users.py: 사용자 처리용 모델을 정의
[이벤트 플래너 애플리케이션 개발]
등록된 사용자는 이벤트를 추가, 변경, 삭제할 수 있으며 애플리케이션이 자동으로 만든 이벤트 페이지에서 생성된 이벤트를 확인할 수 있다.
등록된 사용자와 이벤트는 모두 고유한 ID를 갖는다. 따라서 사용자와 이벤트가 중복되는 것을 방지할 수 있다.
개발을 시작하기 위해 먼저 프로젝트 폴더(planner)에서 가상 환경을 활성화하자.

애플리케이션의 의존 라이브러리를 설치한다.

필요한 라이브러리를 requirements.txt 파일에 저장한다.

*필요한 라이브러리가 포함된 개발 환경이 모두 준비됐다. 이어서 애플리케이션 모델을 구현해보자.*
모델 구현
1.

*각 사용자는 Events 필드를 가지며 여러 개의 이벤트를 저장할 수 있다.*
2. 이벤트 모델(Event)을 models 폴더의 events.py에 정의한다.
from pydantic import BaseModel
from typing import List
class Event(BaseModel):
id: int
title: str
image: str
description: str
tags: List[str]
location: str
이벤트 모델(6개 필드)
- id: 자동 생성되는 고유 식별자
- title: 이벤트 타이틀
- image: 이벤트 이미지 배너의 링크
- description: 이벤트 설명
- tags: 그룹화를 위한 이벤트 태그
- location: 이벤트 위치
3. Event 클래스 안에 Config 서브 클래스를 추가한다. (이 클래스는 문서화할 때 샘플 데이터를 보여주기 위한 용도이다.)
class Config:
schema_extra = {
"example": {
"title": "FastAPI Book Launch",
"image": "https://linktomyimage.com/image.png",
"description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
"tags": ["python", "fastapi", "book", "launch"],
"location": "Google Meet"
}
}
*이 코드는 이벤트의 샘플 데이터를 정의한다.*
4. 사용자 모델(User)을 models 폴더의 users.py 파일에 저장한다.
from pydantic import BaseModel, EmailStr
from typing import Optional, List
from models.events import Event
class User(BaseModel):
email: EmailStr
password: str
events: Optional[List[Event]]
사용자 모델(3개 필드)
- email: 사용자 이메일
- password: 사용자 패스워드
- events: 해당 사용자가 생성한 이벤트. 처음에는 비어 있다.
5. 데이터를 어떻게 저장하고 설정하는지 보여주는 샘플 데이터를 만든다. 이 코드는 User 클래스 안에 추가한다.
{ models 폴더의 users.py 파일 }
class Config:
schema_extra = {
"example": {
"email": "fastapi@packt.com",
"password": "strong!!!",
"events": []
}
}
6. 사용자 로그인 모델(UserSignIn)을 만든다. { models 폴더의 users.py 파일 }
class UserSignIn(BaseModel):
email: EmailStr
password: str
class Config:
schema_extra = {
"example": {
"email": "fastapi@packt.com",
"password": "strong!!!",
"events": []
}
}
이것으로 모델을 모두 완성했다. 다음으로 라우트를 구현해보자.
라우트 구현
사용자 라우트는 로그인, 로그아웃, 등록으로 구성된다.
인증을 완료한 사용자는 이벤트를 생성,변경,삭제할 수 있으며,
인증을 거치지 않은 사용자는 생성된 이벤트를 확인하는 것만 가능하다.

/signup: 등록(≒회원가입)
/signin: 로그인
/signout: 로그아웃
<사용자 라우트>
다음 단계를 따라 routes 폴더의 users.py에 사용자 라우트를 정의해보자.
1. 등록(/signup) 라우트를 다음과 같이 정의한다.
from fastapi import APIRouter, HTTPException, status
from models.users import User, UserSignIn
user_router = APIRouter(
tags=["User"],
)
users = {}
@user_router.post("/signup")
async def sign_new_user(data: User) -> dict:
if data.email in users:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User with supplied username exists"
)
users[data.email] = data
return {
"message": "User successfully registered!"
}
등록 라우트에서는 애플리케이션에 내장된 데이터베이스를 사용한다.
이 라우트는 사용자를 등록하기 전 데이터베이스에 비슷한 이메일이 존재하는지 확인한다.
2. 로그인(/signin) 라우트를 다음과 같이 정의한다. { routes 폴더의 users.py }
@user_router.post("/signin")
async def sign_user_in(user: UserSignIn) -> dict:
if user.email not in users:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User does not exist"
)
if users[user.email].password != user.password:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Wrong credential passed"
)
return {
"message": "User signed in successfully"
}
이 라우트는 로그인하려는 사용자가 데이터베이스에 존재하는지를 먼저 확인하고, 없으면 예외를 발생시킨다. 사용자가 존재하면 패스워드가 일치하는지 확인해서 성공 또는 실패 메시지를 반환한다.
여기서는 설명을 위해 패스워드를 암호화하지 않고 일반 텍스트로 저장했지만 소프트웨어 개발 관점에서는 잘못된 방식이다. 이후에 애플리케이션 내장 데이터베이스를 독립된 데이터베이스로 옮기는 과정을 다룰 때 암호화를 사용한 패스워드 저장 방식을 설명한다.
3. 사용자 처리용 라우트를 정의했으니 main.py에 라우트를 등록하고 애플리케이션을 실행해보자. 라이브러리와 사용자 라우트 정의를 임포트한다.
from fastapi import FastAPI
from routes.users import user_router
import uvicorn
4. FastAPI() 인스턴스를 만들고 앞서 정의한 라우트를 등록한다. {main.py}
app = FastAPI()
# 라우트 등록
app.include_router(user_router, prefix="/user")
5. uvicorn.run() 메서드를 사용해 8000번 포트에서 애플리케이션을 실행하도록 설정한다.
if __name__ == '__main__':
uvicorn.run("main:app", host="127.0.0.1",port=8000, reload=True)
준비가 다 됐으면 애플리케이션을 실행해보자. [ (venv)$ python main.py ]

6. 애플리케이션이 실행됐으니 사용자 라우트를 테스트해보자. 사용자 등록부터 테스트한다.

처음에는 위와 같이 오류가 나왔다. 이유가 뭘까?
아래의 routes 폴더의 users.py를 확인해보자. { routes 폴더의 users.py }
signup 부분에 가서 확인해보면 만약 email 데이터가 users안에 있으면 User with supplied username exist가 뜨고 그게 아니라면 users의 이메일 데이터가 data에 들어가서 User successfully registered, 즉 성공적으로 등록되었다는 문장이 나온다.
from fastapi import APIRouter, HTTPException, status
from models.users import User, UserSignIn
user_router = APIRouter(
tags=["User"],
)
users = {}
@user_router.post("/signup")
async def sign_new_user(data: User) -> dict:
if data.email in users:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User with supplied username exists"
)
users[data.email] = data
return {
"message": "User successfully registered!"
}
아래는 User 클래스를 정의한것이다. { models 폴더의 users.py }
from pydantic import BaseModel, EmailStr
from typing import Optional, List
from models.events import Event
class User(BaseModel):
email: EmailStr
password: str
events: Optional[List[Event]]
class Config:
schema_extra = {
"example": {
"email": "fastapi@packt.com",
"password": "strong!!!",
"events": [],
}
}


이제 어떻게 오류를 해결할 것인가?
해결책은 바로 위 코드의 events를 아래의 username으로 수정하는 것이다.
from pydantic import BaseModel, EmailStr
from typing import Optional, List
from models.events import Event
class User(BaseModel):
email: EmailStr
password: str
username : str
class Config:
schema_extra = {
"example": {
"email": "fastapi@packt.com",
"password": "strong!!!",
"username": "kim"
}
}
==(참고로)User 클래스에 email, password, username이 있으므로 이 3가지만 필요하다....!==

드디어 위와 같이 성공적으로 등록되었다!!!!!
이제 로그인을 해보자.
class UserSignIn(BaseModel):
email: EmailStr
password: str
class Config:
schema_extra = {
"example": {
"email": "fastapi@packt.com",
"password": "strong!!!",
"events": []
}
}
==(참고로)UserSignIn 클래스에 email과 password가 있으므로 이 두가지만 필요하다...!==

성공적으로 로그인 되었다는 문장이 나왔다!!!!!
+++++++++
signup (docs(swagger))

signin ((docs)swagger)

**** signup을 실행할때 자꾸 이미 등록되었다는 문장이 나오면 서버를 죽였다가 다시 살리는 방법도 있다~ ****
<이벤트 라우트>
사용자 라우트가 준비됐으니 이제 이벤트 라우트를 구현해보자.
1. routes 폴더의 events.py를 열어서 다음과 같이 의존 라이브러리를 임포트하고 이벤트 라우트를 정의한다.
from fastapi import APIRouter, Body, HTTPException, status
from models.events import Event
from typing import List
event_router = APIRouter(
tags=["Events"]
)
events = []
2. 모든 이벤트를 추출하거나 특정 ID의 이벤트만 추출하는 라우트를 정의한다. { routes 폴더의 events.py }
@event_router.get("/", response_model=List[Event])
async def retrieve_all_events() -> List[Event]:
return events
@event_router.get("/{id}", response_model=Event)
async def retrieve_event(id: int) -> Event:
for event in events:
if event.id == id:
return event
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Event with supplied ID does not exist"
)
특정 ID의 이벤트만 추출하는 라우트에서는 해당 ID의 이벤트가 없으면 HTTP_404_NOT_FOUND 예외를 발생시킨다.
3. 이벤트 생성 및 삭제 라우트를 정의한다. 첫 번째 라우트는 이벤트 생성, 두 번째는 데이터베이스에 있는 단일 이벤트 삭제, 세 번째는 전체 이벤트 삭제다. { routes 폴더의 events.py }
@event_router.post("/new")
async def create_event(body: Event = Body(...)) -> dict:
events.append(body)
return {
"message": "Event created successfully"
}
@event_router.delete("/{id}")
async def delete_event(id: int) -> dict:
for event in events:
if event.id == id:
events.remove(event)
return {
"message": "Event deleted successfully"
}
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Event with supplied ID does not exist"
)
@event_router.delete("/")
async def delete_all_events() -> dict:
events.clear()
return {
"message": "Events deleted successfully"
}
이벤트 처리용 라우트 구현이 끝났다. 변경 (UPDATE) 라우트는 이후에 실제 데이터베이스와 연동할 때 구현한다.
4. main.py의 라우트 설정을 변경해서 이벤트 라우트를 추가하자.
from fastapi import FastAPI
from routes.users import user_router
from routes.events import event_router
import uvicorn
app = FastAPI()
# 라우트 등록
app.include_router(user_router, prefix="/user")
app.include_router(event_router, prefix="/event")
if __name__ == '__main__':
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True )
*참고로 소스 코드 파일을 변경하고 저장하면 자동으로 애플리케이션이 다시 실행된다.*
5. 라우트를 테스트해보자.
GET 라우트 [이벤트 조회]

POST 라우트 [이벤트 추가]

GET 라우트 [이벤트 추출]

DELETE 라우트 [이벤트 삭제]


이것으로 이벤트 플래너 애플리케이션의 라우트와 모델을 성공적으로 구현했으며 테스트를 통해 제대로 실행되는지도 확인했다.
🌱 배운 점
FastAPI 애플리케이션은 규모가 작을 때는 단일 파일로도 충분하지만, 기능과 구조가 복잡해질수록 체계적인 구조화가 필수라는 것을 이해했다.
구조화란 애플리케이션의 컴포넌트를 역할에 따라 나누고, 모듈 단위로 정리하는 과정이며 이를 통해 코드의 가독성과 유지보수성이 크게 향상된다.
라우트(routes)와 모델(models)을 분리하여 관리하면 기능별 책임이 명확해지고, 코드 수정 시 다른 부분에 미치는 영향을 최소화할 수 있다는 점을 배웠다.
특히 Pydantic 모델을 활용해 요청과 응답 데이터 구조를 명확히 정의함으로써, 데이터 검증과 API 문서화가 자동으로 이루어진다는 장점을 확인했다.
사용자(User)와 이벤트(Event)를 각각 모델과 라우트로 분리해 구현하면서, 실제 서비스와 유사한 구조의 API 흐름을 경험할 수 있었다.
또한 로그인용 모델(UserSignIn)처럼 목적에 맞는 모델을 따로 정의하는 것이 오류를 줄이고 API 안정성을 높인다는 점도 체감했다.
이벤트 조회, 생성, 삭제 기능을 구현하며 HTTP 상태 코드와 예외 처리의 중요성을 이해했고,
데이터가 존재하지 않을 경우 명확한 오류 메시지를 반환하는 것이 사용자 경험에 중요하다는 것도 알게 되었다.
✨ 정리
CHAPTER 5를 통해
FastAPI 애플리케이션을 폴더 단위로 구조화하는 방법과
라우트와 모델을 분리해 설계하는 기본적인 백엔드 아키텍처를 직접 구현해보았다.
이벤트 플래너 애플리케이션을 만들면서
단순히 “동작하는 코드”가 아니라,
확장 가능하고 유지보수가 쉬운 구조가 왜 중요한지 실습을 통해 이해할 수 있었다.
특히 애플리케이션 구조화는
이후 CHAPTER 6에서 데이터베이스를 연동하거나,
실제 서비스 규모의 프로젝트로 확장할 때 반드시 필요한 기반이라는 점에서 의미가 컸다.
이번 장에서 학습한 구조화 개념은
캡스톤 프로젝트와 실무형 웹 백엔드 개발에서도
일관성 있는 코드 작성과 협업을 위한 중요한 토대가 될 것이다.
'백엔드 (Back-end)' 카테고리의 다른 글
| Node.js 설치하기 (0) (0) | 2026.02.01 |
|---|---|
| FastAPI를 사용한 파이썬 웹 개발(CHAPTER 6) (0) | 2025.12.27 |
| FastAPI를 사용한 파이썬 웹 개발(CHAPTER 2) (1) | 2025.12.20 |
| FastAPI를 사용한 파이썬 웹 개발(CHAPTER 3) (0) | 2025.12.20 |
| FastAPI를 사용한 파이썬 웹 개발(CHAPTER 4) (0) | 2025.12.20 |