All Articles

FastAPI - 회원가입

디렉토리 구조도 잡았으니 이제 본격적으로 API를 만들어본다. FastAPI 디렉토리 구조에서 언급한 것처럼, pydanticBaseModel만 request body로 인식하기 때문에, schemas/user.py에서 request body의 type을 먼저 설정한다

# schemas/user.py
from pydantic import BaseModel

class UserCreate(BaseModel):
    email: str
    password: str
    gender: str
    phone: str
    birth: str

사이드 목적상 많은 개인정보는 필요없고, 나이와 성별정도로만 데이터를 저장해보려고한다. 지역구를 저장하는게 의미가 있을 수도 있지만, 정확하게 받아올 수 있는 정보가 아니니 생략한다.

routers/user.py에 클라이언트로부터 request를 받을 인터페이스를 설정한다.

# routers/user.py

from fastapi             import APIRouter, Depends, HTTPException
from sqlalchemy.orm      import Session

from schemas   import user as user_schema

from .deps import get_db, Message

# 각 router/XXX.py에 선언한 router들을 routers.py에서 import한 후, 해당 router를 main.py에서 import한다.
router = APIRouter()

# 아래와 같이 response 종류들을 선언해줘야 swagger에 나타난다.
signup_responses = {
    201: {'model': Message, 'description': '회원가입 성공'},
    409: {'model': Message, 'description': 'email 또는 phone이 중복되는 경우'},
}


@router.post("/signup", status_code=201, responses={**signup_responses})
def signup(
    signup_info: user_schema.UserCreate,
    db: Session = Depends(get_db),
):
    # bcrypt를 사용해서 비밀번호를 단방향 hashing하는 과정을 거친다. 코드는 공식문서 참고
    # signup_info.password를 덮어쓰는 이유는 crud/user.py에서도 UserCreate schema를 사용하기 때문이다. 
    signup_info.password = get_password_hash(signup_info.password)

    # db에 저장한다.
    create_user(signup_info, db)

    # 특이하게 dicitionary로 리턴해도 정상 작동한다.
    return {'msg': 'SIGNUP_SUCCESS'}

이제 sqlalchemy를 사용해서 데이터베이스에 저장하는 과정을 살펴본다.

# crud/user.py
def create_user(
    signup_info: UserCreate,
    db: Session,
) -> User:
    db_obj = User(
                username = signup_info.username,
                password = signup_info.password,
                phone    = signup_info.phone,
                gender   = signup_info.gender,
                birth    = signup_info.birth
            )
    db.add(db_obj)
    db.commit()

    return 

db.add()를 하면 깃헙으로 따지면 스테이징이 되고, db.commit()을 해야 실제로 디비에 INSERT된다. 공식문서 예제에서는 .commit() 후에 .refresh()를 해준다. .refresh()를 하는 이유는, 다른 어딘가에서 데이터베이스에 커밋이 발생했을 때, 내가 원하는 객체가 변경될 수 있기 때문이다(회원가입에는 크게 해당하지 않지만 누군가 갑자기 사용자 정보를 바꾼다던지의 문제) 그렇다면 해당 객체가 내가 원하는 상태의 객체가 아닐 수 있기 때문에 데이터베이스를 read해서 최신화 하는 것이다. 회원가입 후 사용자 정보를 따로 클라이언트에 리턴하는 절차가 없기 때문이 refresh는 생략한다.

curl로 시도해보면 잘 작동하는 것을 볼 수 있다. signup-via-curl

서버 로그도 잘 찍히는 것을 볼 수 있다. signup-success-from-uvicorn

이제 API가 하나 만들어졌으니, 클라이언트를 만들어보도록 하겠다.

Jul 18, 2022

AI Enthusiast and a Software Engineer