March 28, 2023
이번 글은 FastAPI 프레임워크에서 PostgreSQL을 연동하여 API를 처음 만드는 개발자를 대상으로 빠르게 구현의 목적이 있습니다. 상세 설명이 필요하시다면 다른 곳을 참조 하시는걸 추천 드립니다.
설명이 부족하여 이해의 어려움이 있다면, Github 저장소에서 소스 전체를 확인 해보시면 도움이 될 것 입니다. (이번 글의 개발 환경은 macOS에서 진행 되었습니다.)
CREATE SEQUENCE board_board_no_seq;
CREATE TABLE IF NOT EXISTS public.board
(
board_no bigint NOT NULL DEFAULT nextval('board_board_no_seq'::regclass),
title character varying(200) COLLATE pg_catalog."default" NOT NULL,
contents text COLLATE pg_catalog."default" NOT NULL,
writer character varying(50) COLLATE pg_catalog."default" NOT NULL,
view_count integer NOT NULL,
link_url character varying(200) COLLATE pg_catalog."default" NOT NULL,
create_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT board_pkey PRIMARY KEY (board_no)
)
CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.update_date = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER board_update_timestamp_trigger
BEFORE UPDATE ON public.board
FOR EACH ROW
EXECUTE PROCEDURE update_timestamp();
터미널에서 프로젝트를 생성합니다.
$ mkdir fastapi-pg
위에서 생성한 프로젝트 경로로 이동합니다.
$ cd fastapi-pg
터미널에서 “pip” 명령어로 fastapi를 설치 합니다.
$ pip install fastapi
uvicorn 웹서버를 설치 합니다.
$ pip install "uvicorn[standard]"
VS Code에서 “/main.py” 파일을 생성 후 아래 코드를 추가 합니다.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def hello():
return {"Hello": "World"}
웹 서버를 실행합니다.
$ uvicorn main:app --reload
브라우저에서 http://127.0.0.1:8000/docs 에 접속합니다.
“Hello” 클릭해서 펼친 후 “Try it out” 버튼을 클릭합니다.
“Execute” 버튼을 클릭합니다.
API를 실행 후 “Response body” 결과를 확인 합니다.
PostgreSQL 라이브러리 데이터베이스 연동 관련 라이브러리를 설치 합니다.
$ pip install SQLAlchemy
$ pip install python-dotenv
$ pip install pydantic
$ pip install psycopg2
“/config/.env” 파일을 생성 후 아래의 형식으로 데이터베이스 접속 정보를 입력합니다.
host=localhost
port=5432
user=아이디
password=비밀번호
db=데이터베이스명
dbtype=postgresql
“/db/database.py” 파일을 생성 후 아래 코드를 추가합니다.
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from dotenv import load_dotenv
# load .env
load_dotenv(dotenv_path="config/.env", verbose=True)
host = os.environ.get('host')
port = os.environ.get('port')
user = os.environ.get('user')
password = os.environ.get('password')
db = os.environ.get('db')
dbtype = os.environ.get('dbtype')
SQLALCHEMY_DATABASE_URL = f"{dbtype}://{user}:{password}@{host}:{port}/{db}"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
“/db/connection.py” 파일을 생성 후 아래 코드를 추가합니다.
from db.database import SessionLocal
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
“/models.py” 파일을 생성 후 아래 코드를 추가합니다.
from sqlalchemy import Column, String, Integer, TEXT, DATETIME
from db.database import Base
from sqlalchemy.sql import func
class Board(Base):
__tablename__ = "board"
board_no = Column(Integer, primary_key=True, index=True)
title = Column(String(200))
contents = Column(TEXT)
writer = Column(String(50))
view_count = Column(Integer)
link_url = Column(String(200))
create_date = Column(DATETIME(timezone=True), server_default=func.now())
update_date = Column(DATETIME(timezone=True), server_default=func.now())
“/schemas.py” 파일을 생성 후 아래 코드를 추가합니다.
from pydantic import BaseModel
class Item(BaseModel):
board_no: int | None = None
title: str
contents: str
writer: str | None = None
view_count: int | None = None
link_url: str | None = None
class Config:
orm_mode = True
“/main.py” 파일에 “sqlalchemy, db.connection, pydantic, models, schemas” 를 import 하고, Response 함수를 추가 합니다.
from fastapi import FastAPI
from fastapi.params import Depends
from sqlalchemy.orm import Session
from db.connection import get_db
from pydantic import ValidationError
from models import Board
import schemas
def Response(status, message, data):
return {
"status": status,
"message": message,
"data": data
}
app = FastAPI()
@app.get("/")
def hello():
return {"Hello": "World"}
“/main.py” 파일에 “create_board” 함수를 추가합니다.
from fastapi import FastAPI
from fastapi.params import Depends
from sqlalchemy.orm import Session
from db.connection import get_db
from pydantic import ValidationError
from models import Board
import schemas
def Response(status, message, data):
return {
"status": status,
"message": message,
"data": data
}
app = FastAPI()
@app.get("/")
def hello():
return {"Hello": "World"}
@app.post("/board")
def create_board(item: schemas.Item, db: Session = Depends(get_db)):
try:
board = Board()
board.title = item.title
board.contents = item.contents
board.writer = item.writer
board.view_count = item.view_count
board.link_url = item.link_url
db.add(board)
db.flush()
db.refresh(board, attribute_names=['board_no'])
data = {"board_no": board.board_no}
db.commit()
status = True
message = "Board added successfully."
except ValidationError as e:
status = False
data = None
message = e
return Response(status, message, data)
브라우저에서 http://127.0.0.1:8000/docs 에 접속합니다.
“Create Board” 를 클릭해서 펼친 후 “Try it out” 버튼을 클릭합니다.
“Request Body” 에 아래의 JSON 형식으로 값을 입력 후 “Execute” 버튼을 클릭합니다.
{
"title": "제목",
"contents": "내용",
"writer": "작성자",
"view_count": 1,
"link_url": "https://"
}
API를 실행 후 “Response body” 에서 결과를 확인 합니다.
“/main.py” 파일에 “getboardall” 함수를 추가합니다.
from fastapi import FastAPI
from fastapi.params import Depends
from sqlalchemy.orm import Session
from db.connection import get_db
from pydantic import ValidationError
from models import Board
import schemas
def Response(status, message, data):
return {
"status": status,
"message": message,
"data": data
}
app = FastAPI()
@app.get("/")
def hello():
return {"Hello": "World"}
@app.get("/board")
def get_board_all(db: Session = Depends(get_db)):
datas = db.query(Board).all()
status = True
message = "Board retrieved successfully"
if len(datas) <= 0:
status = False
message = "Board not found"
return Response(status, message, datas)
@app.post("/board")
def create_board(item: schemas.Item, db: Session = Depends(get_db)):
try:
board = Board()
board.title = item.title
board.contents = item.contents
board.writer = item.writer
board.view_count = item.view_count
board.link_url = item.link_url
db.add(board)
db.flush()
db.refresh(board, attribute_names=['board_no'])
data = {"board_no": board.board_no}
db.commit()
status = True
message = "Board added successfully."
except ValidationError as e:
status = False
data = None
message = e
return Response(status, message, data)
브라우저에서 http://127.0.0.1:8000/docs 에 접속합니다.
“Get Board All” 를 클릭해서 펼친 후 “Try it out” 버튼을 클릭합니다.
“Execute” 버튼을 클릭합니다.
API를 실행 후 “Response body” 에서 결과를 확인 합니다.
“/main.py” 파일에 “getboardbyboardno” 함수를 추가합니다.
from fastapi import FastAPI
from fastapi.params import Depends
from sqlalchemy.orm import Session
from db.connection import get_db
from pydantic import ValidationError
from models import Board
import schemas
def Response(status, message, data):
return {
"status": status,
"message": message,
"data": data
}
app = FastAPI()
@app.get("/")
def hello():
return {"Hello": "World"}
@app.get("/board")
def get_board_all(db: Session = Depends(get_db)):
datas = db.query(Board).all()
status = True
message = "Board retrieved successfully"
if len(datas) <= 0:
status = False
message = "Board not found"
return Response(status, message, datas)
@app.get("/board/{board_no}")
def get_board_by_board_no(board_no: str, db: Session = Depends(get_db)):
datas = db.query(Board).filter(Board.board_no == board_no).all()
status = True
message = "Board retrieved successfully"
if len(datas) <= 0:
status = False
message = "Board not found"
return Response(status, message, datas)
@app.post("/board")
def create_board(item: schemas.Item, db: Session = Depends(get_db)):
try:
board = Board()
board.title = item.title
board.contents = item.contents
board.writer = item.writer
board.view_count = item.view_count
board.link_url = item.link_url
db.add(board)
db.flush()
db.refresh(board, attribute_names=['board_no'])
data = {"board_no": board.board_no}
db.commit()
status = True
message = "Board added successfully."
except ValidationError as e:
status = False
data = None
message = e
return Response(status, message, data)
브라우저에서 http://127.0.0.1:8000/docs 에 접속합니다.
“Get Board By Board No” 를 클릭해서 펼친 후 “Try it out” 버튼을 클릭합니다.
“Parameters” 의 “boardno” 에 조회할 boardno를 입력 후 “Execute” 버튼을 클릭합니다.
API를 실행 후 “Response body” 에서 결과를 확인 합니다.
“/main.py” 파일에 “update_board” 함수를 추가합니다.
from fastapi import FastAPI
from fastapi.params import Depends
from sqlalchemy.orm import Session
from db.connection import get_db
from pydantic import ValidationError
from models import Board
import schemas
def Response(status, message, data):
return {
"status": status,
"message": message,
"data": data
}
app = FastAPI()
@app.get("/")
def hello():
return {"Hello": "World"}
@app.get("/board")
def get_board_all(db: Session = Depends(get_db)):
datas = db.query(Board).all()
status = True
message = "Board retrieved successfully"
if len(datas) <= 0:
status = False
message = "Board not found"
return Response(status, message, datas)
@app.get("/board/{board_no}")
def get_board_by_board_no(board_no: str, db: Session = Depends(get_db)):
datas = db.query(Board).filter(Board.board_no == board_no).all()
status = True
message = "Board retrieved successfully"
if len(datas) <= 0:
status = False
message = "Board not found"
return Response(status, message, datas)
@app.post("/board")
def create_board(item: schemas.Item, db: Session = Depends(get_db)):
try:
board = Board()
board.title = item.title
board.contents = item.contents
board.writer = item.writer
board.view_count = item.view_count
board.link_url = item.link_url
db.add(board)
db.flush()
db.refresh(board, attribute_names=['board_no'])
data = {"board_no": board.board_no}
db.commit()
status = True
message = "Board added successfully."
except ValidationError as e:
status = False
data = None
message = e
return Response(status, message, data)
@app.put("/board")
def update_board(item: schemas.Item, db: Session = Depends(get_db)):
try:
is_updated = db.query(Board).filter(Board.board_no == item.board_no).update({
Board.title: item.title,
Board.contents: item.contents,
Board.writer: item.writer,
Board.view_count: item.view_count,
Board.link_url: item.link_url
}, synchronize_session=False)
db.flush()
db.commit()
status = True
message = "Board updated successfully"
if is_updated == 1:
data = db.query(Board).filter(Board.board_no == item.board_no).one()
elif is_updated == 0:
message = "Board not updated. No product found with this board_no :" + \
str(item.board_no)
status = False
data = None
except Exception as e:
status = False
data = None
message = e
return Response(status, message, data)
브라우저에서 http://127.0.0.1:8000/docs 에 접속합니다.
“Update Board” 를 클릭해서 펼친 후 “Try it out” 버튼을 클릭합니다.
“Request Body” 에 아래의 JSON 형식으로 값을 입력 후 “Execute” 버튼을 클릭합니다.
{
"board_no": 1,
"title": "제목 수정",
"contents": "내용 수정",
"writer": "작성자 수정",
"view_count": 2,
"link_url": "https://"
}
API를 실행 후 “Response body” 에서 결과를 확인 합니다.
“/main.py” 파일에 “delete_board” 함수를 추가합니다.
from fastapi import FastAPI
from fastapi.params import Depends
from sqlalchemy.orm import Session
from db.connection import get_db
from pydantic import ValidationError
from models import Board
import schemas
def Response(status, message, data):
return {
"status": status,
"message": message,
"data": data
}
app = FastAPI()
@app.get("/")
def hello():
return {"Hello": "World"}
@app.get("/board")
def get_board_all(db: Session = Depends(get_db)):
datas = db.query(Board).all()
status = True
message = "Board retrieved successfully"
if len(datas) <= 0:
status = False
message = "Board not found"
return Response(status, message, datas)
@app.get("/board/{board_no}")
def get_board_by_board_no(board_no: str, db: Session = Depends(get_db)):
datas = db.query(Board).filter(Board.board_no == board_no).all()
status = True
message = "Board retrieved successfully"
if len(datas) <= 0:
status = False
message = "Board not found"
return Response(status, message, datas)
@app.post("/board")
def create_board(item: schemas.Item, db: Session = Depends(get_db)):
try:
board = Board()
board.title = item.title
board.contents = item.contents
board.writer = item.writer
board.view_count = item.view_count
board.link_url = item.link_url
db.add(board)
db.flush()
db.refresh(board, attribute_names=['board_no'])
data = {"board_no": board.board_no}
db.commit()
status = True
message = "Board added successfully."
except ValidationError as e:
status = False
data = None
message = e
return Response(status, message, data)
@app.put("/board")
def update_board(item: schemas.Item, db: Session = Depends(get_db)):
try:
is_updated = db.query(Board).filter(Board.board_no == item.board_no).update({
Board.title: item.title,
Board.contents: item.contents,
Board.writer: item.writer,
Board.view_count: item.view_count,
Board.link_url: item.link_url
}, synchronize_session=False)
db.flush()
db.commit()
status = True
message = "Board updated successfully"
if is_updated == 1:
data = db.query(Board).filter(Board.board_no == item.board_no).one()
elif is_updated == 0:
message = "Board not updated. No product found with this board_no :" + \
str(item.board_no)
status = False
data = None
except Exception as e:
status = False
data = None
message = e
return Response(status, message, data)
@app.delete("/board/{board_no}")
def delete_board(board_no: int, db: Session = Depends(get_db)):
data = None
try:
status = True
is_deleted = db.query(Board).filter(Board.board_no == board_no).delete()
db.commit()
if is_deleted == 1:
message = "Board deleted successfully"
else:
message = "Board not updated. No product found with this board_no :" + \
str(board_no)
except Exception as e:
status = False
message = e
return Response(status, message, data)
브라우저에서 http://127.0.0.1:8000/docs 에 접속합니다.
“Delete Board” 를 클릭해서 펼친 후 “Try it out” 버튼을 클릭합니다.
“Parameters” 의 “boardno” 에 삭제할 boardno 값을 입력 후 “Execute” 버튼을 클릭합니다.
API를 실행 후 “Response body” 에서 결과를 확인 합니다.
이상으로 FastAPI의 기본 API를 구현 해봤습니다.