SDK 생성하기
SDK 생성하기 (Generating SDKs)
FastAPI는 OpenAPI 명세에 기반하기 때문에, 그 API를 많은 도구가 이해하는 표준 형식으로 기술할 수 있어요.
이 덕분에 최신 상태를 유지하는 문서, 여러 언어의 클라이언트 라이브러리(SDKs), 그리고 코드와 동기화 상태를 유지하는 테스트나 자동화 워크플로우를 쉽게 생성할 수 있어요.
이 가이드에서는 FastAPI 백엔드용 TypeScript SDK를 생성하는 법을 배워요.
출처: 공식문서
오픈소스 SDK 생성기
다재다능한 옵션으로는 OpenAPI Generator가 있어요. 많은 프로그래밍 언어를 지원하고 OpenAPI 명세에서 SDK를 생성할 수 있죠.
TypeScript 클라이언트에는 Hey API가 목적에 맞게 설계된 솔루션이에요. TypeScript 생태계에 최적화된 경험을 제공해요.
OpenAPI.Tools에서 더 많은 SDK 생성기를 찾을 수 있어요.
팁
FastAPI는 자동으로 OpenAPI 3.1 명세를 생성해요. 그래서 사용하는 어떤 도구든 이 버전을 지원해야 해요.
TypeScript SDK 만들기
간단한 FastAPI 애플리케이션부터 시작해 볼게요.
Python 3.10+
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
class ResponseMessage(BaseModel):
message: str
@app.post("/items/", response_model=ResponseMessage)
async def create_item(item: Item):
return {"message": "item received"}
@app.get("/items/", response_model=list[Item])
async def get_items():
return [
{"name": "Plumbus", "price": 3},
{"name": "Portal Gun", "price": 9001},
]
path operations이 Item과 ResponseMessage 모델을 사용해서 요청 페이로드와 응답 페이로드에 쓰는 모델들을 정의하고 있다는 점에 주목하세요.
API 문서
/docs로 가면 요청에 보내고 응답으로 받을 데이터에 대한 스키마가 있다는 걸 볼 수 있어요.
그 스키마들을 볼 수 있는 이유는 앱의 모델들로 선언됐기 때문이에요.
그 정보는 앱의 OpenAPI 스키마에 있고, API 문서에 표시돼요.
OpenAPI에 포함된 모델들의 그 정보는 바로 클라이언트 코드를 생성하는 데 사용될 수 있는 것이에요.
Hey API
모델이 있는 FastAPI 앱이 준비되면, Hey API로 TypeScript 클라이언트를 생성할 수 있어요. 가장 빠른 방법은 npx를 쓰는 거예요.
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client
이렇게 하면 ./src/client에 TypeScript SDK가 생성돼요.
@hey-api/openapi-ts 설치 방법과 생성된 출력에 대해서는 그들의 웹사이트에서 배울 수 있어요.
SDK 사용하기
이제 클라이언트 코드를 임포트해서 사용할 수 있어요. 이렇게 생겼을 수 있는데, 메서드에 대한 자동 완성(autocompletion)을 받는다는 점에 주목하세요.
보낼 페이로드에 대한 자동 완성도 받아요.
팁
name과price에 대한 자동 완성에 주목하세요. FastAPI 애플리케이션에서Item모델에 정의된 것들이에요.
보내는 데이터에 대한 인라인 오류(inline errors)도 생겨요.
응답 객체에도 자동 완성이 있어요.
태그가 있는 FastAPI 앱
여러 경우에 FastAPI 앱은 더 커지고, path operations의 다른 그룹들을 나누기 위해 태그를 사용하게 될 거예요.
예를 들어 items 섹션과 users 섹션이 있고, 태그로 나눠질 수 있어요.
Python 3.10+
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
class ResponseMessage(BaseModel):
message: str
class User(BaseModel):
username: str
email: str
@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
return {"message": "Item received"}
@app.get("/items/", response_model=list[Item], tags=["items"])
async def get_items():
return [
{"name": "Plumbus", "price": 3},
{"name": "Portal Gun", "price": 9001},
]
@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
return {"message": "User received"}
태그가 있는 TypeScript 클라이언트 생성하기
태그를 사용하는 FastAPI 앱의 클라이언트를 생성하면, 보통 클라이언트 코드도 태그에 기반해 나눠져요.
이렇게 하면 클라이언트 코드에서 항목들을 올바르게 정렬하고 그룹화할 수 있어요.
이 경우 다음을 갖게 돼요.
ItemsServiceUsersService
클라이언트 메서드 이름
지금은 createItemItemsPost 같은 생성된 메서드 이름이 그리 깔끔해 보이지 않아요.
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
...이건 클라이언트 생성기가 각 path operation 에 대해 OpenAPI 내부 operation ID를 사용하기 때문이에요.
OpenAPI는 각 operation ID가 모든 path operations 에서 유일(unique)해야 한다고 요구해요. 그래서 FastAPI는 함수 이름, 경로, HTTP 메서드/오퍼레이션을 사용해서 그 operation ID를 생성해요. 그렇게 해야 operation ID가 유일함을 보장할 수 있으니까요.
하지만 다음에 그걸 개선하는 법을 보여드릴게요. 🤓
커스텀 Operation ID와 더 나은 메서드 이름
이 operation ID가 생성되는 방식을 수정해서 더 단순하게 만들고 클라이언트에서 더 단순한 메서드 이름을 갖게 할 수 있어요.
이 경우 각 operation ID가 다른 방식으로 유일함을 보장해야 해요.
예를 들어 각 path operation 이 태그를 갖도록 하고, 태그와 path operation 이름(함수 이름)에 기반해 operation ID를 생성할 수 있어요.
커스텀 유일 ID 함수
FastAPI는 각 path operation 에 대해 유일 ID를 사용해요. 이 ID는 operation ID와 요청이나 응답에 필요한 커스텀 모델들의 이름에도 사용돼요.
그 함수를 커스터마이즈할 수 있어요. APIRoute를 받아 문자열을 출력해요.
예를 들어 여기서는 첫 번째 태그(아마 하나만 있을 테니)와 path operation 이름(함수 이름)을 사용하고 있어요.
그런 다음 그 커스텀 함수를 generate_unique_id_function 파라미터로 FastAPI에 전달할 수 있어요.
Python 3.10+
from fastapi import FastAPI
from fastapi.routing import APIRoute
from pydantic import BaseModel
def custom_generate_unique_id(route: APIRoute):
return f"{route.tags[0]}-{route.name}"
app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
class Item(BaseModel):
name: str
price: float
class ResponseMessage(BaseModel):
message: str
class User(BaseModel):
username: str
email: str
@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
return {"message": "Item received"}
@app.get("/items/", response_model=list[Item], tags=["items"])
async def get_items():
return [
{"name": "Plumbus", "price": 3},
{"name": "Portal Gun", "price": 9001},
]
@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
return {"message": "User received"}
커스텀 Operation ID를 가진 TypeScript 클라이언트 생성하기
이제 클라이언트를 다시 생성하면, 개선된 메서드 이름이 있다는 걸 볼 수 있어요.
보시다시피 메서드 이름 이제 태그 다음에 함수 이름을 갖고 있어요. URL 경로와 HTTP 오퍼레이션의 정보는 더 이상 포함하지 않아요.
클라이언트 생성기를 위한 OpenAPI 명세 전처리하기
생성된 코드에는 여전히 약간의 중복 정보가 있어요.
이 메서드가 items와 관련 있다는 건 이미 알고 있어요. 그 단어가 ItemsService(태그에서 가져온)에 있으니까요. 하지만 여전히 메서드 이름 앞에도 태그 이름이 붙어 있어요. 😕
일반적으로 OpenAPI를 위해서는 그걸 유지하고 싶을 거예요. operation ID가 유일함을 보장해 주니까요.
하지만 생성된 클라이언트를 위해서는, 클라이언트를 생성하기 직전에 OpenAPI operation ID를 수정해서 메서드 이름을 더 보기 좋고 깔끔하게 만들 수 있어요.
OpenAPI JSON을 openapi.json 파일로 다운로드하고, 이 스크립트처럼 그 태그 접두사를 제거할 수 있어요.
Python 3.10+
import json
from pathlib import Path
file_path = Path("./openapi.json")
openapi_content = json.loads(file_path.read_text())
for path_data in openapi_content["paths"].values():
for operation in path_data.values():
tag = operation["tags"][0]
operation_id = operation["operationId"]
to_remove = f"{tag}-"
new_operation_id = operation_id[len(to_remove) :]
operation["operationId"] = new_operation_id
file_path.write_text(json.dumps(openapi_content))
이렇게 하면 operation ID가 items-get_items 같은 것에서 그냥 get_items로 바뀌어서, 클라이언트 생성기가 더 단순한 메서드 이름을 생성할 수 있어요.
전처리된 OpenAPI로 TypeScript 클라이언트 생성하기
최종 결과물이 이제 openapi.json 파일에 있으니, 입력 위치를 업데이트해야 해요.
npx @hey-api/openapi-ts -i ./openapi.json -o src/client
새 클라이언트를 생성한 후에는 이제 깔끔한 메서드 이름을 갖게 되고, 자동 완성, 인라인 오류 등을 모두 받아요.
장점 (Benefits)
자동 생성된 클라이언트들을 사용하면 다음에 대해 자동 완성을 받아요.
- 메서드.
- 본문의 요청 페이로드, 쿼리 파라미터 등.
- 응답 페이로드.
모든 것에 대해 인라인 오류도 받아요.
그리고 백엔드 코드를 업데이트할 때마다, 프론트엔드를 재생성하면 새 path operations이 메서드로 제공되고, 옛것들은 제거되며, 다른 변경사항도 생성된 코드에 반영돼요. 🤓
이것은 또한 뭔가가 바뀌면 그것이 클라이언트 코드에 자동으로 반영된다는 뜻이에요. 그리고 클라이언트를 빌드하면, 사용된 데이터에 불일치(mismatch) 가 있으면 오류가 나요.
그래서 최종 사용자에게 프로덕션에서 오류로 나타나길 기다렸다가 그 문제가 어디인지 디버깅하는 대신, 개발 주기 훨씬 초기에 많은 오류를 감지할 수 있어요. ✨
더 알아보기 (Learn more)
이 문서는 FastAPI 공식 문서 - Generating SDKs를 한국어로 정리한 번역이에요. 원문에서 최신 내용과 더 다양한 예시를 확인하세요.