๐Ÿ›ค๏ธ FastAPI ๋ผ์šฐํŒ… ์™„์ „ ์ •๋ณต

๐Ÿ•’ ์•ฝ 2๋ถ„ ์ฝ๋Š” ๋ฐ ์†Œ์š”๋ฉ๋‹ˆ๋‹ค

์ด์ „ ๊ธ€ ๋ณด๊ธฐ(FastAPI ๊ธฐ๋ณธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋งŒ๋“ค๊ธฐ)

โ€“ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜, ๊ทธ๋ฆฌ๊ณ  ๊ฒฝ๋กœ ์—ฐ์‚ฐ์ž

“API ์„ค๊ณ„์˜ ๋ณธ์งˆ์€ ๋ผ์šฐํŒ…์ด๋‹ค!”
FastAPI๋Š” ๋ผ์šฐํŒ…์„ ๋งค์šฐ ์ง๊ด€์ ์œผ๋กœ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋ฉฐ, Flask์™€ ๋น„์Šทํ•œ ๋ฌธ๋ฒ•์„ ์ œ๊ณตํ•˜๋ฉด์„œ๋„ ํ›จ์”ฌ ๊ฐ•๋ ฅํ•˜๊ณ  ๋ช…ํ™•ํ•œ ํƒ€์ž… ๊ธฐ๋ฐ˜ ์ฒ˜๋ฆฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜, ๊ทธ๋ฆฌ๊ณ  HTTP ๋ฉ”์„œ๋“œ๋ณ„ ๊ฒฝ๋กœ ์—ฐ์‚ฐ์ž๋ฅผ ์ž์„ธํžˆ ๋‹ค๋ค„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


โœ… ๋ผ์šฐํŒ…์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

๋ผ์šฐํŒ…(Routing)์ด๋ž€ ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•œ URL ๊ฒฝ๋กœ์— ๋”ฐ๋ผ ์„œ๋ฒ„๊ฐ€ ์–ด๋–ค ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ• ์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๊ทœ์น™์„ ๋งํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ /items/1๋กœ GET ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, FastAPI๋Š” ์ด ๊ฒฝ๋กœ๋ฅผ ํ•ธ๋“ค๋งํ•  ํ•จ์ˆ˜๋ฅผ ์ฐพ์•„ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.


1. ๐Ÿ“Œ ๊ฒฝ๋กœ ์—ฐ์‚ฐ์ž: @app.get, @app.post ๋“ฑ

FastAPI์—์„œ๋Š” ๊ฐ HTTP ์š”์ฒญ ๋ฉ”์„œ๋“œ(GET, POST, PUT, DELETE ๋“ฑ)์— ๋Œ€์‘ํ•˜๋Š” **๋ฐ์ฝ”๋ ˆ์ดํ„ฐ(Decorator)**๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์˜ˆ์‹œ:

from fastapi import FastAPI

app = FastAPI()

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

@app.post("/submit")
def submit_data():
    return {"status": "Data submitted"}
  • @app.get("/"): ๋ฃจํŠธ ๊ฒฝ๋กœ์— GET ์š”์ฒญ์„ ์ฒ˜๋ฆฌ
  • @app.post("/submit"): /submit ๊ฒฝ๋กœ์— POST ์š”์ฒญ์„ ์ฒ˜๋ฆฌ

์ด ์™ธ์—๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—ฐ์‚ฐ์ž๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

  • @app.put() โ€“ ๊ธฐ์กด ์ž์›์˜ ์ „์ฒด ์ˆ˜์ •
  • @app.patch() โ€“ ์ผ๋ถ€ ์ˆ˜์ •
  • @app.delete() โ€“ ์‚ญ์ œ ์š”์ฒญ

FastAPI๋Š” HTTP ๋ช…์„ธ๋ฅผ ์ •ํ™•ํžˆ ๋”ฐ๋ฅด๋ฏ€๋กœ, RESTful API ์„ค๊ณ„์— ๋งค์šฐ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.


2. ๐Ÿ›ฃ ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ (Path Parameters)

๊ฒฝ๋กœ ๋‚ด์— ๋ณ€์ˆ˜์ฒ˜๋Ÿผ ๊ฐ’์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, /users/5์ฒ˜๋Ÿผ URL์— ํฌํ•จ๋œ ์ˆซ์ž๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ ์˜ˆ:

@app.get("/users/{user_id}")
def read_user(user_id: int):
    return {"user_id": user_id}
  • {user_id}๋Š” ๊ฒฝ๋กœ์ƒ์˜ ๋ณ€์ˆ˜์ด๋ฉฐ,
  • user_id: int๋Š” ํ•ด๋‹น ๊ฐ’์ด ์ •์ˆ˜์ž„์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค.

ํƒ€์ž… ๊ฒ€์‚ฌ๋„ ์ž๋™

  • /users/5 โ†’ ์ •์ƒ ์ž‘๋™
  • /users/abc โ†’ 422 ์˜ค๋ฅ˜ ๋ฐœ์ƒ (์ž๋™ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ)

Pydantic์ด๋‚˜ ๋ณ„๋„์˜ ๊ฒ€์‚ฌ ์ฝ”๋“œ ์—†์ด๋„ ํƒ€์ž…์„ ์ž๋™์œผ๋กœ ์ฒดํฌํ•ด์ฃผ๋Š” FastAPI์˜ ํฐ ์žฅ์ ์ž…๋‹ˆ๋‹ค.


3. ๐Ÿ”Ž ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ (Query Parameters)

์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” URL ๋’ค์— ?key=value ํ˜•์‹์œผ๋กœ ์ถ”๊ฐ€๋˜๋Š” ๊ฐ’์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๊ฐœ๋„ ๊ฐ€๋Šฅํ•˜๊ณ , ์„ ํƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด /items/?name=keyboard&price=49.99์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ฃ .

์‚ฌ์šฉ ์˜ˆ:

@app.get("/items/")
def read_item(name: str = "default", price: float = 0.0):
    return {"name": name, "price": price}
  • /items/?name=mouse&price=19.99 โ†’ { "name": "mouse", "price": 19.99 }
  • /items/ โ†’ { "name": "default", "price": 0.0 }

ํ•„์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋งŒ๋“ค๊ธฐ

๊ธฐ๋ณธ๊ฐ’ ์—†์ด ์ •์˜ํ•˜๋ฉด ํ•ด๋‹น ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.

@app.get("/search/")
def search(keyword: str):
    return {"result": f"Searching for {keyword}"}
  • /search/?keyword=python โ†’ OK
  • /search/ โ†’ 422 ์˜ค๋ฅ˜ (keyword ๋ˆ„๋ฝ)

4. ๐ŸŽฏ ๊ฒฝ๋กœ + ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ํ˜ผํ•ฉ ์‚ฌ์šฉ

FastAPI์—์„œ๋Š” ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํŠน์ • ์œ ์ €์˜ ํŠน์ • ๊ฒŒ์‹œ๊ธ€์„ ์ฟผ๋ฆฌํ•œ๋‹ค๊ณ  ํ•ด๋ณผ๊นŒ์š”?

@app.get("/users/{user_id}/posts/")
def read_user_post(user_id: int, sort_by: str = "date"):
    return {"user_id": user_id, "sort_by": sort_by}
  • /users/10/posts/?sort_by=title โ†’ { "user_id": 10, "sort_by": "title" }
  • /users/10/posts/ โ†’ { "user_id": 10, "sort_by": "date" } (๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ)

5. ๐ŸŽ› ๊ณ ๊ธ‰ ๋ผ์šฐํŒ…: ๊ฒฝ๋กœ ์šฐ์„ ์ˆœ์œ„์™€ ํƒ€์ž… ๊ตฌ๋ถ„

FastAPI๋Š” ๊ฒฝ๋กœ ์šฐ์„ ์ˆœ์œ„์™€ ํƒ€์ž…์„ ๋งค์šฐ ์—„๊ฒฉํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์ข‹์€ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

@app.get("/items/{item_id}")
def read_item_by_id(item_id: int):
    return {"item_id": item_id}

@app.get("/items/special")
def read_special_item():
    return {"item": "special item"}
  • /items/special โ†’ read_special_item ์‹คํ–‰
  • /items/42 โ†’ read_item_by_id ์‹คํ–‰

ํ•˜์ง€๋งŒ ์ˆœ์„œ๊ฐ€ ๋ฐ˜๋Œ€์˜€๋‹ค๋ฉด /items/special์ด ์ˆซ์ž๋กœ ์ธ์‹๋ผ ์˜ค๋ฅ˜๊ฐ€ ๋‚  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ • ๋ฌธ์ž์—ด ๊ฒฝ๋กœ๋Š” ์œ„์ชฝ์— ๋จผ์ € ์ •์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


6. ๐Ÿงผ ๊ฒฝ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ๊ฒ€์ฆ ์˜ต์…˜

FastAPI๋Š” Path, Query ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด ์„ธ๋ถ€ ์กฐ๊ฑด์„ ๊ฑธ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

from fastapi import Path

@app.get("/products/{product_id}")
def get_product(product_id: int = Path(..., gt=0, lt=1000)):
    return {"product_id": product_id}
  • gt=0: 0๋ณด๋‹ค ํฐ ๊ฐ’๋งŒ ํ—ˆ์šฉ
  • lt=1000: 1000 ๋ฏธ๋งŒ ๊ฐ’๋งŒ ํ—ˆ์šฉ

์ž…๋ ฅ๊ฐ’์ด ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ์•Š์œผ๋ฉด ์ž๋™์œผ๋กœ 422 ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿงพ ๋งˆ๋ฌด๋ฆฌ: FastAPI ๋ผ์šฐํŒ…์€ ๊ฐ•๋ ฅํ•˜๋ฉด์„œ๋„ ์ง๊ด€์ ์ด๋‹ค

FastAPI์˜ ๋ผ์šฐํŒ… ์‹œ์Šคํ…œ์€ ๋‹จ์ˆœํ•œ URL-ํ•จ์ˆ˜ ์—ฐ๊ฒฐ์„ ๋„˜์–ด์„œ, ํƒ€์ž… ์•ˆ์ •์„ฑ๊ณผ ์ž๋™ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑ, ๋ช…ํ™•ํ•œ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ๊นŒ์ง€ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ๋“ค์€ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ์—์„  ์ถ”๊ฐ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ถ™์—ฌ์•ผ ๊ฐ€๋Šฅํ•œ ์ผ๋“ค์ด์ง€๋งŒ, FastAPI์—์„œ๋Š” ๊ธฐ๋ณธ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ ๊ธ€์—์„œ๋Š” ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ์ •์˜ํ•˜๊ณ , ์‘๋‹ต ์Šคํ‚ค๋งˆ์™€ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ ์šฉํ•˜๋ฉด์„œ ๋” ๊ฒฌ๊ณ ํ•œ API๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. FastAPI๊ฐ€ ์™œ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ์‚ฌ๋ž‘๋ฐ›๋Š”์ง€, ๋” ๊นŠ์ด ์ฒด๊ฐํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”.

FastAPI ๊ณต์‹ ๋ฌธ์„œ : https://fastapi.tiangolo.com/ko/

fastapi-logo,fastapi-๊ฐœ๋ฐœ-ํ™˜๊ฒฝ-์„ค์ •, fastapi-์•ฑ-๋งŒ๋“ค๊ธฐ, fastapi-๋ผ์šฐํŒ