본문 바로가기

클라우드(Cloud)

[스나이퍼팩토리] 카카오클라우드 AIaaS 마스터 클래스 10주차 - Fast API 1

 

 이제 파이썬(Python)을 학습했으니 파이썬을 이용하는 FastAPI라는 것에 대해 학습해 볼 것이다. 

 

 사실 현재 개인적으로 진행하고 있는 사이드 프로젝트가 있는데 그 프로젝트에서는 Node.js를 이용한 Express를 사용해서 백엔드 기능들을 구현하고, 프론트엔드와 연동하는 작업을 진행하고 있다. FastAPI는 그와 같은 것인데 자바스크립트(JavaScript) 대신 파이썬을 사용하는 것으로 생각하면 된다. 

 

 FastAPI를 학습하려면 먼저 REST API가 무엇인지를 알아야 이해하기 좋다. FastAPI는 REST API 서버를 만들기 위한 프레임워크이므로 이 글에서도 REST API를 먼저 간단히 이해한 후에 FastAPI에 대해 자세히 알아볼 것이다. 

 


REST API란?

✅ REST(Representational State Transfer)의 정의

자원(Resource)의 표현(Representation)에 따른 상태(State) 전달(Transfer) 방식으로, 쉽게 말해, "웹에서 URL과 HTTP를 활용해 리소스를 주고받는 방식"이라고 할 수 있다. 

REST는 하나의 아키텍처 스타일이고, 웹의 특징을 최대한 활용할 수 있게 설계된 통신 원칙임. 

REST 예시

요소 REST 용어 예시 설명
메뉴판 API 문서 사용 가능한 API 목록을 확인
음식 리소스(Resource) 상품, 사용자 등 서버에 존재하는 데이터
주문서 요청(Request) 특정 리소스를 어떻게 처리해달라는 요청
음식 배달 응답(Response) 요청에 대한 처리 결과 반환
 

✅ REST의 개념 3가지

  1. 자원 (Resource)
    • 웹에서 조작할 대상
    • 보통 명사형 URI로 표현됨 (예: /products/123)
  2. 표현 (Representation)
    • 클라이언트가 받는 데이터의 형식
    • JSON, XML, HTML 등 다양함
  3. 상태 전이 (State Transfer)
    • 리소스의 상태가 클라이언트의 요청을 통해 변경됨
    • 예: 상품 상세 → 장바구니 추가 → 결제 전환

 

✅ REST의 6가지 원칙

원칙 설명
클라이언트-서버 구조 UI와 데이터 처리 분리 → 독립적 진화 가능
무상태성 (Stateless) 각 요청은 독립적이며 서버는 상태 저장 X
캐시 가능 (Cacheable) 응답에 캐싱 여부 명시 → 성능 향상
계층화 구조 (Layered System) 로드 밸런서, 프록시 서버 등의 중간 계층 허용
일관된 인터페이스 (Uniform Interface) HTTP 메서드와 URI 규칙 등 명확한 표준 제공
HATEOAS 응답에 링크 포함 → 다음 상태 전이 유도
 

✅ HTTP 메서드와 REST 매핑

메서드 설명 예시
GET 리소스 조회 /books/1
POST 리소스 생성 /books
PUT 리소스 전체 수정 /books/1
PATCH 리소스 부분 수정 /books/1
DELETE 리소스 삭제 /books/1
 

✅ 상태 코드(Status Code)

코드 의미 설명
200 OK 성공적인 요청
201 Created 새 리소스 생성 성공
204 No Content 응답 본문 없음 (삭제 등)
400 Bad Request 잘못된 요청
401 Unauthorized 인증 필요
403 Forbidden 권한 없음
404 Not Found 리소스 없음
500 Internal Server Error 서버 내부 오류
 

✅ RESTful URI 설계 규칙

규칙 예시
명사 사용 /users, /products
복수형 사용 /users (O), /user (X)
계층 표현 /users/123/orders
소문자 사용 /products (O), /Products (X)
하이픈 사용 /product-categories (O), _ 지양
확장자 미포함 /users (O), /users.json (X)
 

⚙️ 요약 정리

  • REST는 웹 자원을 URI로 표현하고, HTTP 메서드로 조작하는 방식
  • 핵심 원칙: 클라이언트-서버 분리, 무상태성, 캐시 가능, 계층화, 일관된 인터페이스, HATEOAS
  • 리소스는 명사형 URI로 표현하고, HTTP 상태 코드로 결과를 명확하게 표현
  • FastAPI는 이런 RESTful 원칙을 코드로 쉽게 구현하게 도와주는 도구

 


 REST API를 학습했으니 우리는 FastAPI를 학습할 준비가 되었다고 할 수 있다. 파이썬을 기반으로 하는 FastAPI는 무엇이고, 어떻게 활용이 가능한지 정리해 볼 것이다. 

 

 FastAPI는 파이썬 표준 타입에 기초한 파이썬 기반 API를 빌드하기 위한 현대적이고, 빠른 웹 프레임워크라고 본인들을 소개하고 있다. 실제로 FastAPI는 Node.js와 Go와 견주어도 손색없을 정도로 높은 성능을 보여준다. 또, 빠른 개발 속도와 직관적이고 쉽게 코드를 작성할 수 있다는 장점으로 파이썬 환경에서 API를 구현할 때, FastAPI를 많이 사용하는 것 같다. 

 

 FastAPI의 설치와 같은 것부터 다루려 했으나 이미 많은 곳에 정리되어 있기에 설치 관련 내용은 넘어가도록 하고, FastAPI의 기본 개념부터 정리하며 학습해 볼 예정이다. 그럼 FastAPI의 앱을 생성해서 직관적으로 확인하면서 학습을 진행해 보자. 

 

 

✅ FastAPI 앱 만들기

main.py 파일 생성:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello FastAPI!"}

 

✅ 서버 실행하기

uvicorn main:app --reload
  • main : 파일명 (main.py)
  • app : FastAPI 객체 변수명
  • --reload : 코드 수정 시 서버 자동 재시작 (개발용 옵션)

실행 결과: 

INFO:     Uvicorn running on http://127.0.0.1:8000
INFO:     Application startup complete.

 

✅ Swagger UI & ReDoc 접속

문서 타입 접속 경로 설명
Swagger UI http://127.0.0.1:8000/docs 기본 대화형 문서
ReDoc http://127.0.0.1:8000/redoc 시각적으로 깔끔한 문서

 


FastAPI 라우팅과 엔드포인트

✅ 라우팅(Routing)이란?

클라이언트가 특정 URL로 요청을 보냈을 때, 요청을 처리할 함수(=엔드포인트)와 연결해 주는 메커니즘

우편배달에 비유

요소 비유
URL 경로 주소
FastAPI 앱 우체국
라우터 우편배달부
핸들러 함수 수신자(주소지에 있는 사람)
 

✅ FastAPI의 라우팅 방식

FastAPI는 데코레이터 기반의 선언적 라우팅을 사용한다. 이건 Flask와 비슷하면서도 훨씬 명확하고 타입 기반의 방식이라고 할 수 있다. 

from fastapi import FastAPI

app = FastAPI()

@app.get("/")  # GET 요청 처리
def root():
    return {"message": "Hello World"}

@app.get("/items/{item_id}")  # 경로 매개변수 사용
def read_item(item_id: int):
    return {"item_id": item_id}

 

✅ HTTP 메서드별 데코레이터

HTTP 메서드 FastAPI 데코레이터 용도
GET @app.get() 조회
POST @app.post() 생성
PUT @app.put() 전체 수정
PATCH @app.patch() 일부 수정
DELETE @app.delete() 삭제
 

✅ 예시: CRUD 라우팅

from fastapi import FastAPI

app = FastAPI()

# GET 요청: 조회
@app.get("/users")
def get_users():
    return {"users": ["Alice", "Bob"]}

# POST 요청: 생성
@app.post("/users")
def create_user(name: str):
    return {"message": f"{name} created!"}

# GET 요청: 특정 사용자
@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}

# PUT 요청: 전체 수정
@app.put("/users/{user_id}")
def update_user(user_id: int, name: str):
    return {"user_id": user_id, "new_name": name}

# DELETE 요청: 삭제
@app.delete("/users/{user_id}")
def delete_user(user_id: int):
    return {"message": f"user {user_id} deleted"}

 

✅ 경로 변수의 타입 

FastAPI는 자동으로 URL에서 값을 추출하고 타입을 변환해 준다.

@app.get("/items/{item_id}")
def get_item(item_id: int):  # 자동으로 str → int 변환
    return {"item_id": item_id}

 

✅ 라우팅 설계 

  • URI는 명사형, 복수형 사용
    • /users, /products
  • 계층 구조 표현
    • /users/123/orders
  • 소문자 + 하이픈 사용
    • /product-categories
  • 동사 표현 X (예: /getUser ❌)

 

⚙️ 요약 정리

항목 설명
라우팅 URL 요청을 적절한 함수에 매핑
방식 데코레이터 기반 (@app.get(), @app.post() 등)
경로 변수 {}로 정의하며, 타입 힌트 사용 가능
URI 규칙 명사형, 소문자, 하이픈 사용, 계층 구조 표현

 


 

경로 매개변수 (Path Parameter)

✅ 정의

URL 경로 안에 포함된 동적인 값으로, 중괄호 {}로 표현하며, 함수 인자와 매칭된다. 

@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}

 

/users/7을 호출하면 user_id = 7로 함수 실행되고, int로 타입 힌트를 주면 문자열을 숫자로 자동 변환해 준다. 

 

✅ 여러 경로 매개변수

@app.get("/users/{user_id}/orders/{order_id}")
def get_order(user_id: int, order_id: int):
    return {"user_id": user_id, "order_id": order_id}

 

 /users/5/orders/88 → 두 값을 모두 인자로 받음

 

✅ 경로 매개변수 + 유효성 검사

from fastapi import Path

@app.get("/items/{item_id}")
def read_item(item_id: int = Path(..., ge=1)):
    return {"item_id": item_id}
  • ... → 필수 매개변수
  • ge=1 → 1 이상이어야 함

 

쿼리 매개변수 (Query Parameter)

✅ 정의

 

URL 뒤에? key=value 형태로 붙는 값들로, 함수 인자에 기본값을 주면 자동으로 쿼리 파라미터로 인식한다. 

@app.get("/products")
def get_products(category: str = None, min_price: float = 0.0):
    return {"category": category, "min_price": min_price}

 

/products?category=books&min_price=10.5

 

✅ 다중 값 받기 (List 타입)

from typing import List

@app.get("/products")
def get_products(tags: List[str] = Query(None)):
    return {"tags": tags}

 

/products?tags=eco&tags=new → ["eco", "new"]

 

✅ 쿼리 파라미터에 유효성 검사 추가

from fastapi import Query

@app.get("/search")
def search_items(keyword: str = Query(..., min_length=2, max_length=20)):
    return {"keyword": keyword}
  • min_length, max_length, regex로 제한이 가능하다. 

 

⚙️ Path vs Query 정리

항목 경로 매개변수 쿼리 매개변수
위치 URL 경로에 포함 (/users/123) URL ? 뒤에 (?name=tom)
필수 여부 기본적으로 필수 기본값 있으면 선택 가능
유효성 검사 Path() 사용 Query() 사용
사용 예 특정 리소스 식별 필터링, 검색, 정렬 등
 

⚙️ 요약 정리

개념 요약
경로 매개변수 URL 경로에 포함, {}로 정의
쿼리 매개변수 ?key=value, 기본값 지정 시 자동 처리
유효성 검사 Path(), Query() 사용 가능
타입 자동 변환 int, float, List[str] 등 타입 힌트 사용 가능

 


FastAPI 문서화: Swagger UI & ReDoc

✅ 왜 API 문서가 중요할까?

이유 설명
🧑‍💻 개발자 협업 팀원이 API 구조를 쉽게 이해
🧪 빠른 테스트 UI에서 API 호출 가능
🛠 유지보수 코드 바뀌면 문서도 자동 업데이트
❌ 오류 방지 요청/응답 형식 미스 줄이기
 

FastAPI의 자동 문서화 흐름

  1. 타입 힌트Pydantic 모델 기반으로
  2. OpenAPI 3.0 명세를 자동 생성
  3. Swagger UI와 ReDoc으로 시각화

 

✅ 문서 접속 방법

문서 종류 경로 특징
Swagger UI /docs 기본값, 대화형 테스트 가능
ReDoc /redoc 보기 깔끔, 정적인 문서 스타일

예: http://127.0.0.1:8000/docs

 

✅ Swagger UI 구성요소

항목 설명
API 목록 라우팅된 모든 엔드포인트가 자동 나열됨
Try it out 직접 요청을 날려보고 응답 확인 가능
Response body 응답 데이터 샘플 자동 생성
Status code API 호출 결과 상태 확인

 

✅ 예시: 자동 문서 생성

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
    name: str
    age: int
    email: str

@app.post("/users")
def create_user(user: User):
    return {"message": f"{user.name}님이 등록되었습니다."}

 

 /users 엔드포인트가 /docs에 자동 표시
 요청 본문 스키마도 자동 표시됨

 

✅ 태그 (Tag) 활용

@app.get("/menu", tags=["Menu"])
def get_menu():
    return {"items": ["아메리카노", "카페라떼"]}

 

 Swagger UI에서 같은 태그끼리 그룹화되어 보기 편해짐

 

✅ 문서 설명 추가하기

@app.post("/orders", summary="주문 생성", description="고객의 새 주문을 생성합니다.")
def create_order():
    return {"message": "주문이 접수되었습니다."}
  • summary: 요약 설명
  • description: 상세 설명

 

⚙️ 요약 정리

항목 설명
문서화 코드만 작성해도 OpenAPI 기반 문서 자동 생성
문서 UI /docs (Swagger), /redoc (ReDoc)
장점 테스트, 협업, 유지보수에 강력
사용자 정의 태그, summary, description 등으로 구조화 가능

 

데이터 검증 및 변환 (Pydantic 기반)

✅ Pydantic이란?

Python의 타입 힌트를 활용해서 데이터를 자동으로 검증하고 직렬화/역직렬화해주는 라이브러리로, FastAPI는 내부적으로 Pydantic의 BaseModel을 이용해 데이터 모델을 정의하고 검증한다. 

 

✅ BaseModel 사용법

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int
    email: str

 

이제 API에서 이 모델을 요청 바디에 매핑할 수 있다. 

@app.post("/users")
def create_user(user: User):
    return user

✅ 결과

  • 자동으로 JSON Body 파싱
  • 잘못된 타입이 들어오면 422 Unprocessable Entity 오류 발생
  • /docs에서 자동으로 입력 폼 생성됨

 

✅ Field를 통한 세부 제약조건 설정

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(..., min_length=2, max_length=50)
    price: float = Field(..., gt=0)
    category: str = Field(..., regex="^(electronics|books|clothing)$")

옵션 설명
min_length, max_length 문자열 길이 제한
gt, ge, lt, le 숫자 크기 제한 (초과, 이상, 미만, 이하)
regex 정규식 조건
 

✅ 응답 데이터도 검증 가능

from pydantic import BaseModel

class ResponseUser(BaseModel):
    name: str
    email: str

@app.get("/users/{user_id}", response_model=ResponseUser)
def read_user(user_id: int):
    return {"name": "Alice", "email": "alice@example.com", "age": 30}
  • response_model은 응답 형식을 제한함
  • 반환 값에 포함된 불필요한 필드(age)는 자동 제거됨

 

✅ 타입 자동 변환

Pydantic은 아래처럼 자동으로 형변환 기능도 포함하고 있다. 

class Item(BaseModel):
    price: float

# JSON: {"price": "123.45"} → float 변환 OK!

 

✅ 복잡한 모델도 가능 (중첩 모델)

class Address(BaseModel):
    city: str
    zipcode: str

class Customer(BaseModel):
    name: str
    address: Address
  • JSON 구조가 중첩되어 있어도 문제없이 검증됨
  • 응답 스키마도 자동으로 생성됨

 

✅ @field_validator 사용 (복잡한 조건)

from pydantic import field_validator

class User(BaseModel):
    password: str

    @field_validator("password")
    @classmethod
    def check_password(cls, v):
        if "123" in v:
            raise ValueError("너무 쉬운 비밀번호입니다!")
        return v

 

복잡한 비즈니스 로직도 추가가 가능하다. 

 

⚙️ 요약 정리

기능 설명
BaseModel 입력 데이터 구조 정의
Field() 제약 조건 설정
response_model 응답 형태 제한
중첩 모델 복잡한 JSON도 대응 가능
자동 형변환 "123" → 123으로 자동 변환
검증 실패 422 에러 + 어떤 필드에서 실패했는지 명확히 표시

 


요청(Request)과 응답(Response)의 구조

✅ HTTP 기본 구조

구분 내용
요청(Request) 클라이언트 → 서버
응답(Response) 서버 → 클라이언트
 

🔹 요청 예시

GET /api/users HTTP/1.1
Host: example.com
Content-Type: application/json

🔹 응답 예시

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 85

{"users": [{"id": 1, "name": "홍길동"}]}

 

✅ 요청(Request)의 구성 요소

구성 설명
요청 라인 GET /users HTTP/1.1
요청 헤더 Content-Type, Authorization, 등
요청 본문 주로 POST, PUT에서 JSON 등 데이터를 포함
 

✅ 응답(Response)의 구성 요소

구성 설명
상태 라인 HTTP/1.1 200 OK
응답 헤더 Content-Type, Content-Length 등
응답 본문 실제 데이터(JSON, HTML, 파일 등)

 

✅ FastAPI 응답 유형 예시

1. JSON 응답 (기본)

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

2. 문자열 응답

from fastapi.responses import PlainTextResponse

@app.get("/text", response_class=PlainTextResponse)
def get_text():
    return "단순 텍스트 응답"

3. HTML 응답

from fastapi.responses import HTMLResponse

@app.get("/html", response_class=HTMLResponse)
def get_html():
    return "<h1>HTML 응답</h1>"

 

✅ 파일 업로드 (Request Body가 파일인 경우)

from fastapi import File, UploadFile

@app.post("/upload")
def upload_file(file: UploadFile = File(...)):
    return {"filename": file.filename}
  • 파일 이름, 타입, 바이트 등 메타데이터 접근 가능
  • Postman에서 테스트 시, form-data > type을 File로 설정

 

✅ 응답 커스터마이징 (상태코드 & 헤더)

from fastapi import Response, status

@app.get("/custom")
def custom_response():
    return Response(content="OK", media_type="text/plain", status_code=status.HTTP_202_ACCEPTED)

 

⚠️ 상태 코드 정리

코드 의미
200 OK
201 Created
204 No Content
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error
 

✅ 요청 헤더 받기

from fastapi import Header

@app.get("/headers")
def get_headers(user_agent: str = Header(...)):
    return {"User-Agent": user_agent}

 

✅ 응답 헤더 설정

from fastapi.responses import JSONResponse

@app.get("/with-headers")
def response_with_headers():
    return JSONResponse(
        content={"msg": "헤더 포함 응답"},
        headers={"X-Custom-Header": "HeaderValue"}
    )

 

⚙️ 요약 정리

항목 설명
요청 본문 POST, PUT 등에서 JSON, 파일 등 포함
응답 본문 기본은 JSON, 필요시 HTML, 텍스트, 파일 가능
커스터마이징 상태코드, 헤더, 미디어 타입 설정 가능
파일 업로드 UploadFile, File() 사용
문서 자동 반영 모든 응답 형태가 /docs에 자동 적용됨

 


의존성 주입(Dependency Injection)

어떤 함수(또는 클래스)가 필요로 하는 객체나 값을 외부에서 제공받도록 하는 설계 방식이다. 

바리스타 비유

  • 바리스타가 커피를 만들기 위해 매번 원두, 물, 컵을 직접 준비하지 않고, 이미 준비된 재료(=의존성)를 받아서 커피만 만드는 것

 

✅ FastAPI의 의존성 주입 방식

FastAPI에서는 Depends()를 통해 의존성을 선언한다. 그리고 공통 로직, 설정, 인증, DB 연결 등을 분리할 수 있다. 

 

기본 예제

from fastapi import Depends, FastAPI

app = FastAPI()

def get_token():
    return "secret-token"

@app.get("/secure-data")
def read_data(token: str = Depends(get_token)):
    return {"token": token}
  • Depends(get_token)은 token 자리에 함수를 실행해 반환값을 자동 주입함
  • 내부적으로 get_token()이 먼저 실행되고 그 결과가 token으로 들어가게 됨

 

요청 범위 내 캐싱

def get_value():
    print("함수 실행됨")
    return "cached"

@app.get("/test1")
def test1(data=Depends(get_value)):
    return {"value": data}

@app.get("/test2")
def test2(
    data1=Depends(get_value),
    data2=Depends(get_value)
):
    return {"d1": data1, "d2": data2}
  • test1: 요청마다 get_value()가 실행됨
  • test2: 한 요청 내에서는 1회만 실행되고 재사용됨

 

예: 인증 & 권한 분리

from fastapi import HTTPException, status

def get_current_user(token: str = Depends(get_token)):
    if token != "admin":
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
    return {"username": "admin"}

@app.get("/admin")
def read_admin(user=Depends(get_current_user)):
    return {"msg": "관리자 전용"}

 

✅ 의존성의 의존성

def get_db_settings():
    return {"url": "db-url"}

def get_db(settings = Depends(get_db_settings)):
    return f"Connected to {settings['url']}"

@app.get("/db")
def db_test(conn=Depends(get_db)):
    return {"conn": conn}
  • 의존성 안에 또 다른 의존성을 쓸 수 있음 → 계층 구조 지원

 

설정을 전역에서 한 번만 로딩 (@lru_cache())

from functools import lru_cache

@lru_cache()
def get_settings():
    return {"mode": "production"}
  • 설정 같은 불변값은 캐싱해서 매 요청마다 새로 계산하지 않도록 할 수 있음

 

⚙️ 요약 정리

개념 설명
Depends() 의존성 함수 주입
캐싱 범위 요청 범위 내 1회 실행, 요청마다 초기화
사용 용도 인증, 설정, DB 연결, 로깅 등
중첩 지원 의존성 안에 의존성 사용 가능
성능 향상 불변 값은 @lru_cache()로 캐싱 가능

 


 

 이번엔 FastAPI에 대해 정리하며 학습해 보았다. 사실 FastAPI와 같은 것들은 이론으로 이해하는 것보다 직접 사용해 보고 코드를 작성해 보면서 어떻게 사용하는지를 익히고, 정리하는 것이 좋은 방법이라고 생각된다. 

 

 이번 글에서 FastAPI에 대해 정리하면서 학습해 보았으니, 다음에는 예제 코드들을 통해 어떻게 사용되고, 어떻게 기능들이 작동하는지를 알아보면 좋을 것 같다. 그리고 해당 과정의 학습 내용이 아니라 개인적으로 하고 있는 프로젝트(..?)에서든 FastAPI가 아니라 Node.js를 사용한 Express를 사용하여서 백엔드를 구축하고 있어서 서로 비교해 보고, 어떤 점이 다른지 익히기에 좋을 것 같다는 생각이다. 

 


본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.