본문 바로가기
Data Science

FastAPI 이해부터 router로 백엔드 API 서버 만들고 docs 문서 확인하기

by Lora Baek 2024. 8. 7.
300x250

FastAPI는 Python 프레임워크 중 하나로, 특히 API를 구축하는 데 유용하다. 

무엇보다 /docs를 통해 자동으로 API 문서화가 가능하다는 점이 정말 좋다고 느꼈는데,

간단한 예제를 통해 FastAPI로 백엔드 API 서버를 만들어보면서 차근차근 설명해보겠다.

 

우선 맥북에는 기본으로 venv라는 가상환경 라이브러리가 설치되어 있다.

나는 내가 백엔드 API 서버 개발을 원하는 폴더로 이동한 다음, 가상환경을 먼저 생성하고 실행해주었다.

python3 -m venv 가상환경이름
source 가상환경이름/bin/activate

 

다음으로는 fastapi와 uvicorn을 설치해주자.

uvicorn은 경량 서버로, FastAPI로 만든 내용을 uvicorn으로 실행할 수 있다.

pip install fastapi uvicorn

 

이제 같은 폴더에서 main.py 파일을 하나 생성하고, 다음과 같이 내용을 입력해주자.

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

 

여기서 @app 데코레이터는 FastAPI의 엔드포인트를 정의하는 데 사용된다.

HTTP 메서드(GET, POST 등)에 해당하는 경로와 함수를 연결해준다.

 

여기서는 / 경로로 GET 요청을 보내면 "Hello World" 메시지를 반환한다는 엔드포인트를 정의했다.

 

이제 파일을 저장해준 후, 터미널에서 아래와 같이 실행해주자.

uvicorn main:app --host 0.0.0.0 --port 3000

 

만일 코드 변경 시 서버를 자동으로 재시작해주기를 원한다면 --reload 옵션을 추가해줄 수 있다.

 

--host 옵션은 서버가 바인딩할 호스트 주소를 지정하는 것으로, 0.0.0.0을 지정하면 모든 곳에서 접속을 허용하겠다는 의미다. 즉, 이 설정을 통해서 다른 장치나 외부 네트워크에서도 서버에 접근할 수 있다.

 

--port 옵션은 서버가 사용할 포트 주소를 지정해주는 것이다.

만일 나처럼 3000 포트를 지정한다면, 서버는 3000번 포트에서 요청을 수신하게 된다.

 

즉 위 코드를 실행하면 Uvicorn 서버가 0.0.0.0 주소에서 3000번 포트로 실행된다.

이제 브라우저에서 http://0.0.0.0:3000으로 접근하면 FastAPI 애플리케이션을 확인할 수 있다!

우리가 만든 hello world가 프린트될 것이다.

 

여기서 url 뒤에 /docs를 붙여보자. API 문서화가 자동으로 되어있는 것을 볼 수 있다!

router 사용해보기

이제 router를 사용해보겠다.

main.app을 열고, 코드를 다음과 같이 수정해보자.

from fastapi import FastAPI, APIRouter

app = FastAPI()

router = APIRouter()

@router.get("/users/")
def read_users():
    return [{"username": "Alice"}, {"username": "Bob"}]

@router.get("/users/{user_id}")
def read_user(user_id: int):
    return {"user_id": user_id, "username": "Alice" if user_id == 1 else "Bob"}

app.include_router(router)

 

APIRouter는 여러 라우트(경로)를 그룹화하고 모듈화하는 역할을 한다.

APIRouter를 사용하여 /users/와 /users/{user_id} 경로를 정의해준 다음,

app.include_router(router)를 사용하여 라우터를 FastAPI 앱에 포함시켜줄 수 있다.

 

/users/ 경로로 GET 요청을 보내면 {"username": "Alice"}, {"username": "Bob"}을 리턴,

/users/{user_id} 경로로 GET 요청을 보내면 {user_id}가 1이면 Alice, 다른 숫자이면 Bob을 리턴한다!

 

이제 코드를 저장하고, 터미널에서 Ctrl+c를 눌러 서버를 잠시 닫았다가,

다시 uvicorn main:app --host 0.0.0.0 --port 3000 명령어로 서버를 다시 시작해준다.

 

http://0.0.0.0:3000/users/로 접근했을 경우

 

http://0.0.0.0:3000/users/1로 접근했을 경우

 

http://0.0.0.0:3000/users/2로 접근했을 경우

 

http://0.0.0.0:3000/docs로 접근했을 경우

 

docs에서도 Try it out을 통해 바로바로 코드를 실행해볼 수 있어 편리하다!

 

여기까지 했을 때에는 사실 왜 router가 편리한지 실감이 나지 않을 것이다.

이 router는 실제로는 router 폴더를 만들어준 다음 각각의 py 파일로 모듈을 만들어놓고,

main.py에서는 각 모듈을 import해와서 prefix 옵션과 함께 사용하는 편이다.

따라서 아래의 폴더 구조와 같이 우리의 fastAPI를 수정해보자!

main.py
├── router/
│   ├── user.py
│   ├── item.py 등등..

 

 

먼저 main.py의 위치에서 router라는 폴더를 만들고, user.py 파일을 하나 생성해준다.

이번에는 특정 HTTP 상태 코드와 메세지를 반환하는 HTTPException을 추가할 것이다.

엔드포인트로 들어온 요청을 우리 서버가 찾을 수 없을 때 Not Found를 의미하는 404 코드와 메세지를 보내는 코드를 추가해줬다. 

from fastapi import APIRouter, HTTPException

userrouter = APIRouter()

@userrouter.get("/")
def read_users():
    return [{"user_id": 1, "username": "Alice"}, {"user_id": 2, "username": "Bob"}]

@userrouter.get("/{user_id}")
def read_user(user_id: int):
    if user_id == 1:
        return {"user_id": 1, "username": "Alice"}
    elif user_id == 2:
        return {"user_id": 2, "username": "Bob"}
    else:
        raise HTTPException(status_code=404, detail="유저 ID가 존재하지 않습니다.")

 

클라이언트가 / 경로로 GET 요청을 보내면 사용자 목록을 반환한다.

/{user_id} 경로로 GET 요청을 보내면 user_id 값을 확인하고,

1이나 2이면 해당 사용자의 정보를 반환해주고 그렇지 않다면 404 오류를 발생시키겠다는 코드이다.

 

이제 파일을 저장하고 닫아준다.

router 폴더를 import해올 수 있도록 router 폴더 내에 내용이 빈 __init__.py 파일을 하나 생성해주자.

 

그 다음, main.py 파일을 열고 모든 내용을 지운 다음, 아래와 같이 적어주자.

from fastapi import FastAPI
from router.user import userrouter

app = FastAPI()

app.include_router(userrouter, prefix="/users", tags=["User"])

 

app.include_router가 FastAPI에 라우터를 추가하는 역할을 한다. 

router 폴더 내의 user 모듈에서 userrouter 객체를 가져온 다음,

prefix="users"를 통해 해당 라우터의 모든 엔드포인트에 /users 접두사를 추가해준다.

 

즉 우리는 user 모듈 안에서 /, /{user_id}로 코드를 작성하였고

main의 이 부분에서 prefix를 붙여 실제로는 users/, users/{user_id}로 경로를 설정할 수 있는 것이다!

 

만일 item 모듈을 추가하고, items라는 경로가 라우터에 기본으로 붙도록 설정하고 싶다면?

from router.item import itemrouter

app.include_router(itemrouter, prefix="/items", tags=["items"])

 

나는 설명을 위해서 user, users, userrouter 등의 다른 이름을 사용했지만 실제로는 동일하게 가져가는 것이 편한 것 같다.

 

참고로 여기서 tags는 docs를 통해 접근하는 API 문서에서 엔드포인트들을 특정 이름의 그룹으로 묶어주는 역할을 한다.

 

다시 코드를 ctrl + c로 정지하고, 재실행해보자.

Docs 문서에서 다음과 같이 User 그룹의 내용들을 확인할 수 있다.

 

http://0.0.0.0:3000/users/1 접근 시

 

http://0.0.0.0:3000/users/3 접근 시

 

우리가 지정한 에러 메세지가 표시되는 것을 확인할 수 있다! :D

댓글