친구와 프로젝트를 여름 방학동안 진행할 예정이다.
전체의 프로젝트 소개 보단 백엔드의 입장에서의 개발 과정에 대해서 정리해서 올릴 예정이다.
오늘의 개발은 회원 가입이다.
회원 가입에 사용할 테이블의 구체적인 저장 요소에 대해서는 아직 미정인 부분들이 있어서 그 진행 과정과 로직에 대해서만 저장했다.
Database는 mysql을 사용할 예정이며, 백엔드 프레임워크는 FastAPI를 사용할 것이다.
테이블의 들어갈 필수적인 요소를 정리해보면
아이디, 비밀번호, 등록날짜 정도이다. 거기에 추가적으로 닉네임 정도와 이메일 정도이다.
(25.07.01 기준, 유저 테이블에 들어갈 내용은 닉네임, 자기소개, 이메일, 아이디, 비밀번호, 등록날짜 등이다)
그렇다면 백엔드의 입장에서 구현할 부분에 대해서 생각해보면
프론트가 활용할 엔드포인트를 정의하고, 프론트의 입력을 받을 라우터 부분을 정리하고 구체적인 로직이 실행될 서비스 로직을 개발할 것이다. 그리고 데이터베이스를 구성하고 테이블에 저장될 내부 로직을 만들 것이다.
(FastAPI의 기본 구성 자체는 완료되었다는 가정 하에)
일단 User를 새로 생성하는 개념이기에 REST API 기준 POST에 해당한다.
router = APIRouter(tags=["user"] ,prefix="/api/v1/user/local")
@router.post("/oauth", status_code= HTTP_201_CREATED)
따라서 라우터 등록된 엔드포인트는 다음과 같이 설정을 하였다.
(엔드포인트 URI의 경우 아직 API 명세서가 완성되지 않아서 개발 확인 차 임시로 설정해두었다.)
이제 실제 작동할 함수를 작성할 것이다.
회원가입 시의 실제 비밀번호를 저장하지 않고 해시 저장할 것이다. 따라서 해시 함수를 통하여 바꾸고 그것에 대해서 받아 저장하는 코드 작성이 필요하다.
async def create_user(
user: CreateUser,
request: Request,
db: Session = Depends(get_db)
):
hashed_password = hash_password(user.password)
user_data = {
"name": user.name,
"id": user.id,
"password": hashed_password
}
user_id = await register_user(db=db, user_data = user_data)
return {
"success": True,
"user": user_id
}
위와 같이 작성을 하였다.
user: CreateUser라 사용한 부분은 CreateUser라는 pydantic schemas를 통하여 들어온 입력에 대하여 검증을 진행한다.
class UserBase(BaseModel):
name: str = Field(..., max_length=8 ,description="회원 이름 / 본명 x여도 됨")
id: str = Field(..., max_length=20 ,description="회원 ID")
password: str = Field(..., max_length=20, description="회원 PW")
class CreateUser(UserBase):
pass
이렇게 적은 것을 기반으로 들어온 입력이 CreateUser(UserBase를 상속받음)가 지정한 형식에 맞는 지를 확인하게 된다.
(아직 모든 테이블의 내용을 반영하지는 않았다. 기본적으로 돌아가냐에 초점을 맞추고 먼저 개발하기 시작하였다.)
코드 하나하나 확인해보면,
#endpoints/oauth/user.py
hashed_password = hash_password(user.password)
#service/verify/password_hash.py
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str)->str:
return pwd_context.hash(password)
def verify_password(password: str, hashed_password: str)->bool:
return pwd_context.verify(password, hashed_password)
이렇게 라우터 부분에서 서비스 디렉토리의 내부 로직 중 하나를 호출하는 방식으로 구현하였다.
저것으로 hashed_password에 해시 알고리즘이 적용된 비밀번호를 받게된다.
이후,
user_data = {
"name": user.name,
"id": user.id,
"password": hashed_password
}
user_id = await register_user(db=db, user_data = user_data)
user_data라는 딕셔너리 자료형으로 저장한 후, service 디렉토리의 register_user로 보내게 된다.
async def register_user(
db: Session,
user_data: dict
):
try:
db_user = User(
name=user_data["name"],
id = user_data['id'],
password = user_data['password']
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user.id
except IntegrityError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User already exists"
)
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Database error occurred"
)
위의 로직에 의해서 db.add, db.commit, db.refresh 등의 과정으로 DB에 저장되게 된다.
(예외 처리에 대해서는 일단 다루지 않는다.)
위와 같은 방식으로 신규 유저에 대해서 등록하는 과정을 만들었다. 위 코드에 사용된 user 데이터베이스와 스키마를 기반으로 기본적인 CRUD를 작성하게 될 것이다.
| [백엔드 논문] Zanzibar: Google's Consistent, Global Authorization System #1 (2) | 2025.07.23 |
|---|---|
| [Serendi] 프로젝트 #4 플레이리스트가 유지하는 음악의 인덱스가 바뀌었을 경우 (1) | 2025.07.18 |
| [Serendi] 프로젝트 #3.1 DB 수정 (0) | 2025.07.13 |
| [Serendi] 프로젝트 #3 DB설계 (0) | 2025.07.03 |
| [Serendi] 프로젝트 #2 - 이메일 인증 (0) | 2025.07.01 |