Python DB 처리를 FastAPI REST API로 변환
Vue에서 호출할 수 있는 FastAPI REST API 구조로 변경합니다.
11장에서는 Tkinter로 만든 화면을 Vue 화면으로 변경하는 방법을 배웠습니다.
하지만 기존 Python 프로그램에는 화면 코드뿐 아니라 SQLite를 처리하는 데이터베이스 코드도 들어 있습니다.
cursor.execute(
"SELECT * FROM employee"
)
cursor.execute(
"""
INSERT INTO employee
(name, phone, department)
VALUES (?, ?, ?)
""",
(
name,
phone,
department
)
)
기존 Python DB 코드를 모두 버리는 것이 아닙니다. 기존 DB 처리 코드 앞에 FastAPI REST API를 추가하여 Vue에서 호출할 수 있도록 만드는 것이 핵심입니다.
기존 사원관리 프로그램에 다음과 같은 DB 함수가 있다고 가정해 봅니다.
import sqlite3
def get_employees():
conn = sqlite3.connect(
"employee.db"
)
cursor = conn.cursor()
cursor.execute(
"""
SELECT
employee_id,
name,
phone,
department
FROM employee
ORDER BY employee_id
"""
)
rows = cursor.fetchall()
conn.close()
return rows
Tkinter에서는 이 함수를 직접 호출할 수 있습니다.
employees = get_employees()
for employee in employees:
print(employee)
브라우저에서 실행되는 Vue는 Python의
get_employees() 함수를 직접 호출할 수 없습니다.
따라서 중간에 REST API가 필요합니다.
기존 Python DB 함수 앞에 웹에서 호출할 수 있는 주소를 만들어 준다고 생각하면 됩니다.
GET /employees
Vue가 API 주소를 호출하면 FastAPI가 요청을 받고, 기존 Python DB 코드를 실행하여 SQLite 데이터를 처리합니다.
FastAPI와 실행 서버인 Uvicorn을 설치합니다.
pip install fastapi uvicorn
main.py 파일을 만듭니다.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def home():
return {
"message": "서버 실행"
}
서버를 실행합니다.
uvicorn main:app --reload
브라우저에서 다음 주소를 엽니다.
http://localhost:8000
FastAPI는 Vue와 Python 사이에서 요청을 받아 처리합니다.
↓ GET /employees
FastAPI
↓ Python 함수 실행
SQLite
기존 Python 기능을 HTTP 요청을 통해 웹에서 사용할 수 있도록 만들어 주는 역할을 합니다.
| 기능 | HTTP | API |
|---|---|---|
| 전체 조회 | GET | /employees |
| 한 명 조회 | GET | /employees/{id} |
| 등록 | POST | /employees |
| 수정 | PUT | /employees/{id} |
| 삭제 | DELETE | /employees/{id} |
get_employee(id) → GET /employees/{id}
insert_employee() → POST /employees
update_employee() → PUT /employees/{id}
delete_employee() → DELETE /employees/{id}
기존 Python 함수의 역할이 없어지는 것이 아니라 웹에서는 각각의 기능을 HTTP 메서드와 URL로 표현합니다.
이번 장에서는 다음 employee 테이블을 사용합니다.
CREATE TABLE employee (
employee_id INTEGER
PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT,
department TEXT
);
INSERT INTO employee
(name, phone, department)
VALUES
('홍길동', '010-1111-1111', '개발팀');
INSERT INTO employee
(name, phone, department)
VALUES
('김철수', '010-2222-2222', '영업팀');
INSERT INTO employee
(name, phone, department)
VALUES
('이영희', '010-3333-3333', '관리팀');
import sqlite3
def get_connection():
conn = sqlite3.connect(
"employee.db"
)
conn.row_factory = sqlite3.Row
return conn
sqlite3.Row를 사용하면 조회 결과를
컬럼 이름을 기준으로 처리하기 편리합니다.
row["name"]
def get_employees():
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
SELECT
employee_id,
name,
phone,
department
FROM employee
ORDER BY employee_id
"""
)
rows = cursor.fetchall()
conn.close()
return rows
Tkinter에서는 다음처럼 직접 호출했습니다.
employees = get_employees()
@app.get("/employees")
def get_employees():
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
SELECT
employee_id,
name,
phone,
department
FROM employee
ORDER BY employee_id
"""
)
rows = cursor.fetchall()
employees = [
dict(row)
for row in rows
]
conn.close()
return employees
def get_employees()
함수 위에 @app.get("/employees")를 추가하여
웹에서 호출할 수 있는 API로 만드는 것이 핵심입니다.
↓
GET /employees
↓
FastAPI
↓
get_employees()
↓
SELECT
↓
SQLite
↓
조회 결과
↓
FastAPI
↓
JSON
↓
Vue
FastAPI가 반환한 Python 데이터는 JSON 형태로 Vue에 전달됩니다.
def get_employee(employee_id):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
SELECT
employee_id,
name,
phone,
department
FROM employee
WHERE employee_id = ?
""",
(employee_id,)
)
row = cursor.fetchone()
conn.close()
return row
from fastapi import HTTPException
@app.get(
"/employees/{employee_id}"
)
def get_employee(
employee_id: int
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
SELECT
employee_id,
name,
phone,
department
FROM employee
WHERE employee_id = ?
""",
(employee_id,)
)
row = cursor.fetchone()
conn.close()
if row is None:
raise HTTPException(
status_code=404,
detail="사원을 찾을 수 없습니다."
)
return dict(row)
GET /employees/2
위 요청을 보내면 employee_id에는
숫자 2가 전달됩니다.
@app.get(
"/employees/{employee_id}"
)
중괄호 안의 {employee_id}는
URL을 통해 전달받는 값입니다.
기존 Python 코드로 생각하면 다음과 비슷합니다.
get_employee(10)
def insert_employee(
name,
phone,
department
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
INSERT INTO employee
(
name,
phone,
department
)
VALUES (?, ?, ?)
""",
(
name,
phone,
department
)
)
conn.commit()
conn.close()
Tkinter에서는 다음과 같이 직접 호출할 수 있습니다.
insert_employee(
"홍길동",
"010-1111-1111",
"개발팀"
)
웹 프로그램에서는 Vue가 등록 데이터를 JSON 형태로 FastAPI에 전달합니다.
{
"name": "홍길동",
"phone": "010-1111-1111",
"department": "개발팀"
}
FastAPI에서는 이 JSON을 Python 객체로 받기 위해 Pydantic 모델을 사용합니다.
from pydantic import BaseModel
class EmployeeCreate(BaseModel):
name: str
phone: str = ""
department: str = ""
@app.post("/employees")
def create_employee(
employee: EmployeeCreate
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
INSERT INTO employee
(
name,
phone,
department
)
VALUES (?, ?, ?)
""",
(
employee.name,
employee.phone,
employee.department
)
)
conn.commit()
employee_id = cursor.lastrowid
conn.close()
return {
"employee_id":
employee_id,
"message":
"등록되었습니다."
}
↓
POST /employees + JSON
웹 프로그램으로 변경하더라도 SQL의 INSERT 부분은 거의 그대로 사용할 수 있습니다.
↓
v-model
↓
employee 데이터
↓
POST /employees
↓
JSON
↓
FastAPI
↓
EmployeeCreate
↓
INSERT
↓
SQLite
Vue는 사용자의 데이터를 입력받고, FastAPI가 실제 SQLite 저장을 담당합니다.
def update_employee(
employee_id,
name,
phone,
department
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
UPDATE employee
SET
name = ?,
phone = ?,
department = ?
WHERE employee_id = ?
""",
(
name,
phone,
department,
employee_id
)
)
conn.commit()
conn.close()
@app.put(
"/employees/{employee_id}"
)
def update_employee(
employee_id: int,
employee: EmployeeCreate
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
UPDATE employee
SET
name = ?,
phone = ?,
department = ?
WHERE employee_id = ?
""",
(
employee.name,
employee.phone,
employee.department,
employee_id
)
)
conn.commit()
conn.close()
return {
"message":
"수정되었습니다."
}
{
"name": "홍길동",
"phone": "010-9999-9999",
"department": "관리팀"
}
PUT /employees/1
이 값들이 SQL의 UPDATE 문에 전달됩니다.
def delete_employee(
employee_id
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
DELETE FROM employee
WHERE employee_id = ?
""",
(employee_id,)
)
conn.commit()
conn.close()
@app.delete(
"/employees/{employee_id}"
)
def delete_employee(
employee_id: int
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
DELETE FROM employee
WHERE employee_id = ?
""",
(employee_id,)
)
conn.commit()
conn.close()
return {
"message":
"삭제되었습니다."
}
DELETE /employees/1
| Python DB 함수 | REST API | SQL |
|---|---|---|
| get_employees() | GET /employees | SELECT |
| get_employee(id) | GET /employees/{id} | SELECT |
| insert_employee() | POST /employees | INSERT |
| update_employee() | PUT /employees/{id} | UPDATE |
| delete_employee() | DELETE /employees/{id} | DELETE |
SQL은 그대로 사용하면서 외부에서 호출할 수 있도록 REST API 구조를 추가하는 것입니다.
개발 중 Vue와 FastAPI는 서로 다른 주소에서 실행됩니다.
FastAPI : http://localhost:8000
FastAPI에 CORS 설정을 추가합니다.
from fastapi.middleware.cors import (
CORSMiddleware
)
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:5173"
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
개발 중 localhost:5173에서 실행되는 Vue가
localhost:8000의 FastAPI를 호출할 수 있도록 허용합니다.
지금까지 작성한 내용을 main.py 하나로 합쳐 봅니다.
import sqlite3
from fastapi import (
FastAPI,
HTTPException
)
from fastapi.middleware.cors import (
CORSMiddleware
)
from pydantic import BaseModel
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:5173"
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class EmployeeCreate(BaseModel):
name: str
phone: str = ""
department: str = ""
def get_connection():
conn = sqlite3.connect(
"employee.db"
)
conn.row_factory = sqlite3.Row
return conn
@app.get("/employees")
def get_employees():
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
SELECT
employee_id,
name,
phone,
department
FROM employee
ORDER BY employee_id
"""
)
rows = cursor.fetchall()
conn.close()
return [
dict(row)
for row in rows
]
@app.get(
"/employees/{employee_id}"
)
def get_employee(
employee_id: int
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
SELECT
employee_id,
name,
phone,
department
FROM employee
WHERE employee_id = ?
""",
(employee_id,)
)
row = cursor.fetchone()
conn.close()
if row is None:
raise HTTPException(
status_code=404,
detail="사원을 찾을 수 없습니다."
)
return dict(row)
@app.post("/employees")
def create_employee(
employee: EmployeeCreate
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
INSERT INTO employee
(
name,
phone,
department
)
VALUES (?, ?, ?)
""",
(
employee.name,
employee.phone,
employee.department
)
)
conn.commit()
employee_id = cursor.lastrowid
conn.close()
return {
"employee_id":
employee_id,
"message":
"등록되었습니다."
}
@app.put(
"/employees/{employee_id}"
)
def update_employee(
employee_id: int,
employee: EmployeeCreate
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
UPDATE employee
SET
name = ?,
phone = ?,
department = ?
WHERE employee_id = ?
""",
(
employee.name,
employee.phone,
employee.department,
employee_id
)
)
conn.commit()
if cursor.rowcount == 0:
conn.close()
raise HTTPException(
status_code=404,
detail="사원을 찾을 수 없습니다."
)
conn.close()
return {
"message":
"수정되었습니다."
}
@app.delete(
"/employees/{employee_id}"
)
def delete_employee(
employee_id: int
):
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
DELETE FROM employee
WHERE employee_id = ?
""",
(employee_id,)
)
conn.commit()
if cursor.rowcount == 0:
conn.close()
raise HTTPException(
status_code=404,
detail="사원을 찾을 수 없습니다."
)
conn.close()
return {
"message":
"삭제되었습니다."
}
FastAPI 서버를 실행합니다.
uvicorn main:app --reload
FastAPI가 제공하는 API 문서 화면을 이용하면 Vue를 연결하기 전에 REST API를 직접 테스트할 수 있습니다.
http://localhost:8000/docs
Vue를 연결하기 전에 FastAPI API가 정상적으로 동작하는지 먼저 확인하는 것이 좋습니다.
import axios from 'axios'
async function loadEmployees() {
const response =
await axios.get(
'http://localhost:8000/employees'
)
console.log(
response.data
)
}
async function saveEmployee() {
const employee = {
name: '홍길동',
phone:
'010-1111-1111',
department:
'개발팀'
}
await axios.post(
'http://localhost:8000/employees',
employee
)
}
두 번째 인자인 employee 객체가
JSON 데이터로 서버에 전달됩니다.
async function updateEmployee() {
const employee = {
name: '홍길동',
phone:
'010-9999-9999',
department:
'관리팀'
}
await axios.put(
'http://localhost:8000/employees/1',
employee
)
}
async function removeEmployee(id) {
await axios.delete(
'http://localhost:8000/employees/'
+ id
)
}
DELETE /employees/3
DELETE FROM employee WHERE employee_id = 3;
기존 프로그램과 비교했을 때 가장 큰 차이는 Vue와 Python 사이에 REST API가 추가되었다는 것입니다.
기존 프로그램의 모든 코드를 새로 작성해야 하는 것은 아닙니다.
def get_employees():
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM employee"
)
rows = cursor.fetchall()
conn.close()
return rows
DB 연결과 SELECT 처리 부분은 거의 그대로 사용할 수 있습니다. 핵심 변경은 API 주소를 연결하는 것입니다.
@app.get("/employees")
def get_employees():
작은 실습에서는 API와 SQL을 같은 함수에 작성할 수 있지만, 프로그램 규모가 커지면 DB 함수와 API 함수를 분리하는 것이 좋습니다.
작은 프로그램에서는 다음처럼 한 파일에 작성할 수 있습니다.
프로그램이 커지면 역할에 따라 파일을 분리할 수 있습니다.
| 파일 | 역할 |
|---|---|
| employee.py | REST API 처리 |
| employee_repository.py | SQL / DB 처리 |
이렇게 구성하면 기존 Python의 DB 처리 코드를 Repository 부분에서 재사용하기 쉬워집니다.
def find_all_employees():
conn = get_connection()
cursor = conn.cursor()
cursor.execute(
"""
SELECT *
FROM employee
ORDER BY employee_id
"""
)
rows = cursor.fetchall()
conn.close()
return [
dict(row)
for row in rows
]
@app.get("/employees")
def get_employees():
return find_all_employees()
API 처리와 DB 처리를 나누면 기존 Python 프로그램의 DB 코드를 활용하기 쉬워지고 프로그램 구조도 명확해집니다.
기존 Python 코드에 이미 DB 처리 로직이 있다면 활용할 수 있는 부분까지 모두 새로 작성할 필요는 없습니다.
Vue가 데이터베이스에 직접 접근하도록 구성하지 않습니다.
화면은 Vue로 이동하고, Python의 DB 처리 로직은 FastAPI 뒤에서 활용한다. 이 구조를 이해하면 기존 Python 데스크톱 프로그램을 웹 프로그램으로 변경하는 과정이 훨씬 명확해집니다.
- Tkinter에서는 Python 프로그램이 SQLite에 직접 접근할 수 있습니다.
- Vue 웹 프로그램에서는 Vue → REST API → FastAPI → SQLite 구조를 사용합니다.
- 기존 Python DB 함수 앞에 FastAPI의 API 주소를 연결한다고 이해하면 쉽습니다.
- 전체 조회는 GET /employees로 변환합니다.
- 한 명 조회는 GET /employees/{id}로 변환합니다.
- 등록은 POST /employees로 변환합니다.
- 수정은 PUT /employees/{id}로 변환합니다.
- 삭제는 DELETE /employees/{id}로 변환합니다.
- Vue가 보내는 JSON 데이터는 FastAPI에서 Pydantic 모델로 받을 수 있습니다.
- SQLite 조회 결과는 dict(row) 형태로 변환하여 반환할 수 있습니다.
- Vue와 FastAPI가 서로 다른 개발 주소에서 실행되므로 CORS 설정이 필요합니다.
- Vue에서는 Axios를 이용하여 GET, POST, PUT, DELETE 요청을 보냅니다.
- 기존 SQL의 SELECT, INSERT, UPDATE, DELETE 코드는 대부분 그대로 활용할 수 있습니다.
- 기존 Python 코드를 모두 새로 작성하는 것이 아니라 재사용 가능한 DB 로직을 FastAPI에서 활용하는 것이 중요합니다.
- 프로그램이 커지면 Router와 Repository처럼 API 처리와 DB 처리를 분리할 수 있습니다.
- 가장 중요한 전체 구조는 Vue → REST API → FastAPI → Python DB 처리 → SQLite입니다.