FastAPI 시작하기

JeongYun Lee·2024년 12월 12일
0

Developing

목록 보기
4/7
post-thumbnail

백엔드와는 거리가 먼 사람이었는데, 이번에 개발을 하면서 백, 프론트 가릴 것 없이 다 해야 하다보니... 어쩌다가 이렇게 됐다...ㅋㅋ

처음 개발할 때는 Flask를 썼고, 이후에는 Django, 그리고 마지막으로 접한게 FastAPI였는데, 파이썬 개발을 하는데 있어서 각각 나름의 장단점이 있는 것 같다. 조금씩 써보니 여전히 깊이는 몰라도 이제 대충 어떤 것들을 설정해줘야 하는지 감은 오는 것 같다.

간단하게 느낌을 말하자면, Django는 기본적으로 구조를 빌드해주고 DB까지 셋팅을 쭉 해주기 때문에 편리하긴 하다. 다만, DB를 굳이 사용하지 않거나 api를 여러개 만들어야 하는 경우에는 지나치게 하위 폴더들을 많이 만들어야 해서 너무 무겁다고 느꼈다.

반면, Flask나 FastAPI 가벼운 만큼 직접 세팅해줘야 하는 부분이 많지만 그래도 초기 사용이 간편한 것 같긴 하다. FastAPI와 뭐가 더 편한지 판단할 정도로 사용해보진 않았지만, 개인적으로는 비슷한 것 같다.

이번 프로젝트에서는 FastAPI를 주로 사용했고, router를 통해 여러개의 api를 만들어서 붙여줬다. api 하나를 만들때 py 파일 하나만 생성하고 main.py에서 연결해주는 구조로 짜서 만들기도, 유지관리도 편리하다고 느꼈다. 무엇보다 localhost:8000/docs에서 swagger 웹 UI를 통해서 테스트 해보는 게 제일 큰 장점이라고 생각했다. (Flask도 되는 걸로 안다)

이렇게 6개의 api가 각각 보인다.

개별 api에서 Try it out 버튼을 누르면, input 값을 넣어서 테스트 결과를 바로 볼 수 있다. front 쪽에 적용하기 전에 api가 제대로 만들어졌는지, front 문제인지 back 문제인지 판단할 때 아주 유용했다.


구조

FastAPI의 구조는 필요에 따라 유동적으로 만들 수 있는 것 같다. 나의 경우 여러개의 하위 api를 편리하게 관리하고 싶었기 때문에 routers라는 폴더 안에 각 api 의 py파일을 만들고 main.py에서 하위 api들을 일괄적으로 관리할 수 있도록 만들어주었다. 만든 구조는 다음과 같다.

├── back
   ├── requirements.txt 
   ├── .env
   ├── fastapi
       └── data 
       └── main.py 
       └── routers
           └── api1.py
           └── api2.py
           └── api3.py
           └── api4.py
           └── api5.py
           └── api6.py

main.py

# main.py
import uvicorn
from fastapi import FastAPI, APIRouter
from fastapi.middleware.cors import CORSMiddleware
from routers import chatgpt_imageExplainer, chatgpt_entityExtractor, coreEntity_search, chatgpt_subConclusion, subEntity_search, chatgpt_conclusion

app = FastAPI()
api_router = APIRouter(prefix="/api")

origins = [
    "http://localhost:3000",
    "http://localhost",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,  # origins 변수 사용
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],  # 구체적인 메서드 명시
    allow_headers=["*"],
    expose_headers=["*"]  # 추가
)

api_router.include_router(api1.router, prefix="/api-1")
api_router.include_router(api2.router, prefix="/api-2")
api_router.include_router(api3.router, prefix="/api-3")
api_router.include_router(chatgpt_subConclusion.router, prefix="/sub-conclusion")
api_router.include_router(subEntity_search.router, prefix="/sub-entity-search")
api_router.include_router(chatgpt_conclusion.router, prefix="/conclusion")

# 메인 앱에 api_router 포함
app.include_router(api_router)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

우선 main.py의 코드는 다음과 같다. 모든 api를 /api라는 prefix로 묶어주고 싶어서 상단에 표기했고, orgins 부분에는 연결할 front의 url을 지정한다. 개발 단계에서는 localhost를 사용했기때문에 저렇게 적었는데, 추후 디플로이를 할때는 도메인과 IP 주소도 적어줘야 한다. CORS 에러를 방지하기 위한 코드도 추가한다.

api_router 부분이 핵심인데, routers 폴더 안에 만들어준 각 api의 py 파일들과 연결해주고, url에 sub로 표시될 prefix를 지정해준다. 지금과 같이 설정하면 localhost:8000/api/api-1 과 같은 경로로 연결되는 것이다.

예시 api1.py

from fastapi import FastAPI, File, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi import APIRouter
from os import environ
from dotenv import load_dotenv
load_dotenv(override=True)

router = APIRouter()

def function1():
	# 각자의 함수
  

@router.post("/")
async def execute(
    img_path: UploadFile = File(...),
    var1: str = Form(...),
    var2: str = Form(...),
):
	try:
    	# 각자의 코드
        
    except:
    	# 각자의 코드

fastapi에서 통신하는 부분만 남기고 나머지 코드들은 지운 상태이다. front에서 원하는 값을 받아와서 def execute 부분에서 파일인 경우 UploadFile이라는 인자로 경로를 받아왔고, string인 경우에 Form을 통해서 받아왔다. 이렇게 설정해줘야 위에서 /docs 부분에서 테스트 할 때 입력값을 넣을 수 있는 폼이 생긴다.

front 부분도 간단하게 보여주자면, 나중에 deploy할 때 용이하게 하기 위해서 .env 파일에 경로 url(혹은 도메인)을 작성하고, nuxt.config.ts에 다음과 같이 작성해주었다.

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  compatibilityDate: '2024-04-03',
  devtools: { enabled: false },
  modules: ['@nuxt/ui', '@nuxtjs/tailwindcss'],
  runtimeConfig: {
    public: {
      apiUrl: process.env.NUXT_PUBLIC_API_URL
    }
  }
})

.env 파일에 설정해준 변수명이 NUXT_PUBLIC_API_URL이다.

이후 백엔드와 통신하는 컴포넌트의 script에서는 다음과 같이 baseURL을 설정해서 사용하면 추후 연결 url을 바꾸거나 도메인을 달아도 .env 파일에서만 수정해주면 된다.

const baseURL = config.public.apiUrl

const search = async () => {

  try {
    const FormData = new FormData();
    FormData.append('explain', imageExplanation.value);
    FormData.append('facilities', JSON.stringify(entityResults.value));

    const coreEntityResponse = await axios.post(`${baseURL}/core-entity-search/`, coreEntityFormData);
    searchResults.value = coreEntityResponse.data.result;
    emit('search-results', searchResults.value);

  } catch (error) {
    console.error('Error Details:', {
      message: error.message,
      response: error.response?.data,
      status: error.response?.status
    });
    alert(error.message || '오류가 발생했습니다.');
  } finally {
    isCoreEntityLoading.value = false;
    currentProcessMessage.value = '';
  }
};

.
.
.
이렇게 개발을 마쳐도 dockerizing을 하고 nginx설정해서 deploy하는 고난의 길이 남았다는 거~~ㅋㅋㅋ 재밌다

profile
궁금한 건 많지만, 천천히 알아가는 중입니다

0개의 댓글