<--SyntaxHighlighter--> SyntaxHighlighter.all();

Database에서 간단히 CRUD만 구성할 일이 있었는데, Backend 와 Frontend를 분리해서 구축하려니 조금 번거로워서 찾게되었습니다. Python 프레임워크들은 Jinja2 템플릿을 사용하여 Frontend도 쉽고 빠르게 구축할 수 있도록 지원해주기 때문에, 이런 용도로는 잘 맞는 것 같습니다. FastAPI도 일반적으로는 API 형태의 backend를 구성하겠지만, Jinja2를 이용하여 Frontend 연결까지 해보도록 하겠습니다.

1. 폴더구조

아래와 같이 폴더구조를 만들겠습니다. 이전에 만들었던 main.py를 업데이트하기 위해 mainlist.py 파일을 새로 작성하였습니다. 실행도 mainlist.py로 실행하겠습니다.

2. 라이브러리 설치

<기존 설치된 라이브러리>

pip install fastapi, uvicorn
pip install mysql, sqlalchemy
pip install starlette

<추가 라이브러리>

pip install jinja2 	#jinja2 실행
pip install python-multipart	#Form 기능을 위한 라이브러리

3. 코드

<mainlist.py>

route 처리부분을 별도의 파일로 분리하여 작성하고, 해당 router를 include_router로 연결합니다.

from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from routes import index

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(index.router)

 

<db/db.py>

가장 많이 예제로 주어지는 mysql database를 연결하도록 하겠습니다. sqlalchemy를 사용하여 연결하게 되는데, DATABASE 정보를 이용하여 ENGINE을 생성하고, ENGINE을 이용하여 session을 생성합니다.

Session이란 트랜잭션이 커밋되기 전 동작들이 거쳐가는 일종의 버퍼 같은 역할을 합니다. 커밋이 안된 데이터가 다른 트랜잭션으로 흘러들어가거나, 같은 테이블의 인스턴스를 두번 불러왔을 때 다른 객체로 인식되는 등의 문제가 없도록 합니다.

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

user_name = "root"
user_pwd = ""
db_host = "127.0.0.1"
db_name = "project01"

DATABASE = 'mysql://%s:%s@%s/%s?charset=utf8' % (
    user_name,
    user_pwd,
    db_host,
    db_name,
)

ENGINE = create_engine(
    DATABASE,
    encoding="utf-8",
    echo=True
)

session = scoped_session(
    sessionmaker(
        autocommit = False,
        autoflush = False,
        bind = ENGINE
    )
)

Base = declarative_base()
Base.query = session.query_property()

 

<model/model.py>

Database 테이블의 Mapping Class를 생성합니다.

from sqlalchemy import Column, Integer, String
# from pydantic import BaseModel
from db.db import Base

class UserTable(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    age = Column(Integer)

 

<routes/index.py>

Jinja2Templates를 불러오고 html파일이 위치할 디렉토리를 지정해주면 해당 파일에서 Jinja2문법을 사용할 수 있습니다. Jinja2에서는 Post와 Get 메서드만 지원되며, put, delete 등의 다른 메서드는 지원되지 않습니다. Form 요소의 사용법에 대해서는 여기 문서를 참조하세요

from fastapi import APIRouter, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from starlette.responses import RedirectResponse

from db.db import session
from model.model import UserTable, User

router = APIRouter()
templates = Jinja2Templates(directory="templates")


# 전체 리스트
@router.get("/users", response_class=HTMLResponse)
async def read_users(request: Request):
    context={}
    users = session.query(UserTable).all()

    context["request"] = request
    context["users"] = users

    return templates.TemplateResponse("user_list.html", context)



# 특정 인원 보기
@router.get("/users/{user_id}", response_class=HTMLResponse)
async def read_user(request: Request, user_id: int):
    context={}
    user = session.query(UserTable).filter(UserTable.id == user_id).first()

    context["request"] = request
    context["name"] = user.name
    context["age"] = user.age

    return templates.TemplateResponse("user_list.html", context)

# 인원 생성
@router.post("/user")
async def create_user(
        name: str = Form(...),
        age:int = Form(...)):

    print(name, age)
    user = UserTable()
    user.name = name
    user.age = age

    session.add(user)
    session.commit()

    return RedirectResponse(url="/users", status_code=302)

# 인원 수정
@router.post("/users", response_class=HTMLResponse)
async def update_user(
        user_id: int = Form(...),
        n_name: str = Form(...),
        n_age: int = Form(...)):

    user = session.query(UserTable).filter(UserTable.id == user_id).first()
    user.name = n_name
    user.age = n_age
    print(n_name, n_age)
    session.commit()

    return RedirectResponse(url="/users", status_code=302)


# 인원 삭제
@router.post("/delete/{user_id}", response_class=HTMLResponse)
async def delete_users(request: Request, user_id: int):

    print('delete', user_id)
    session.query(UserTable).filter(UserTable.id == user_id).delete()
    session.commit()

    return RedirectResponse(url="/users", status_code=302)

 

<templates/user_list.html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MySQL연동</title>
</head>
<body>
    <div>
        <a href="/">HOME</a>
    </div>
    <form action="/user" method="post">
        <input type="text" id="username_" name="name" value="사용자" />
        <input type="text" id="userage_" name="age" value="1" />
        <input type="submit">등록</input>
    </form>
    <div id="usertable">
        <table width="100%" border="1" cellpadding="0" cellspacing="0">
            {% if users %}
                {% for user in users %}
                    <tr>
                        <form action="/users" method="post">
                            <td align="center"><input type="text"  name="user_id" value={{user.id}}></td>
                            <td align="center"><input type="text" id="username_{{user.id}}" name="n_name" value="{{user.name}}" /></td>
                            <td align="center"><input type="text" id="userage_{{user.id}}" name="n_age" value="{{user.age}}" /></td>
                            <td align="center"><input type="submit" value="수정"/></td>
                        </form>
                        <form action="/delete/{{user.id}}" method="post">
                            <td align="center">
                                <input type="submit" value="삭제"/>
                            </td>
                        </form>
                    </tr>
                {% endfor %}
            {% else %}
                <tr>
                    <td align="center" colspan="6">No user...</td>
                </tr>
            {% endif %}
        </table>
    </div>
</body>
</html>

 

<실행>

uvicorn mainlist:app --reload

<결과>

 

이상, FastAPI를 사용하여, 간단하게 Database와 연계, CRUD 실행, Frontend까지 작성해보았습니다.

 

~~끝~~

+ Recent posts