Vue + FastAPI + SQLite 통합 실습

FULL STACK INTEGRATION
Chapter 14. Vue + FastAPI + SQLite 통합 실습
Vue 프론트엔드와 FastAPI 백엔드, Service·Repository·SQLite를 연결하여
실제 CRUD 사원관리 웹 프로그램을 완성합니다.
Vue FastAPI SQLite 통합 웹 개발
프론트엔드부터 데이터베이스까지 하나의 데이터 흐름으로 연결해 봅니다.
14.1 통합 프로그램 만들기

이번 장에서는 지금까지 배운 Vue, Axios, FastAPI, Service, Repository, SQLite를 실제 하나의 프로그램으로 연결합니다.

Vue
Axios
FastAPI
Service
Repository
SQLite
구현할 기능
사원 목록 조회 · 사원 등록 · 사원 상세 조회 · 사원 수정 · 사원 삭제
기존 Tkinter 프로그램
Tkinter → Python 함수 → SQLite
웹 프로그램
Vue → REST API → FastAPI → Python → SQLite
💡 이번 장의 목표
각각 따로 학습했던 프론트엔드, REST API, 업무 로직, DB 처리 코드를 하나의 완성된 데이터 흐름으로 연결합니다.
14.2 프로젝트 폴더 만들기
employee_web │ ├─ backend │ │ │ ├─ main.py │ ├─ database.py │ │ │ ├─ models │ │ └─ employee.py │ │ │ ├─ routers │ │ └─ employee_router.py │ │ │ ├─ services │ │ └─ employee_service.py │ │ │ ├─ repositories │ │ └─ employee_repository.py │ │ │ └─ employee.db │ └─ frontend │ ├─ src │ │ │ ├─ api │ │ ├─ axios.js │ │ └─ employeeApi.js │ │ │ ├─ views │ │ ├─ EmployeeListView.vue │ │ ├─ EmployeeCreateView.vue │ │ ├─ EmployeeDetailView.vue │ │ └─ EmployeeEditView.vue │ │ │ ├─ router │ │ └─ index.js │ │ │ ├─ App.vue │ └─ main.js │ └─ package.json
폴더 담당
backend Python / FastAPI / SQLite
frontend Vue / Axios / Vue Router
14.3 SQLite 데이터베이스 만들기

backend 폴더에 employee.db를 만듭니다.

SQL
CREATE TABLE employee (
    employee_id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    phone TEXT,
    department TEXT
);
샘플 데이터 입력
SQL
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', '관리팀');
employee_id name phone department
1홍길동010-1111-1111개발팀
2김철수010-2222-2222영업팀
3이영희010-3333-3333관리팀
14.4 database.py 만들기
backend/database.py
import sqlite3


def get_connection():

    conn = sqlite3.connect(
        "employee.db"
    )

    conn.row_factory = sqlite3.Row

    return conn
Repository → get_connection() → employee.db

Repository는 데이터베이스를 사용할 때 get_connection() 함수를 호출합니다.

14.5 사원 데이터 모델 만들기
backend/models/employee.py
from pydantic import BaseModel


class EmployeeCreate(BaseModel):
    name: str
    phone: str = ""
    department: str = ""


class EmployeeUpdate(BaseModel):
    name: str
    phone: str = ""
    department: str = ""
Vue가 보내는 JSON
JSON
{
  "name": "홍길동",
  "phone": "010-1111-1111",
  "department": "개발팀"
}
FastAPI에서 사용할 수 있는 값
employee.name employee.phone employee.department
14.6 Repository 만들기

Repository는 SQLite를 직접 처리합니다.

backend/repositories/employee_repository.py
from database import get_connection


def find_all():

    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
    ]
Repository의 책임
SQL을 실행하고 SQLite에서 데이터를 조회·저장·수정·삭제하는 역할에 집중합니다.
14.7 한 명 조회하기
employee_repository.py
def find_by_id(
    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()

    if row is None:
        return None

    return dict(row)
14.8 사원 등록하기
employee_repository.py
def insert(
    name,
    phone,
    department
):

    conn = get_connection()
    cursor = conn.cursor()

    cursor.execute(
        """
        INSERT INTO employee
        (
            name,
            phone,
            department
        )
        VALUES (?, ?, ?)
        """,
        (
            name,
            phone,
            department
        )
    )

    conn.commit()

    employee_id = (
        cursor.lastrowid
    )

    conn.close()

    return employee_id
14.9 사원 수정하기
employee_repository.py
def update(
    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()

    count = cursor.rowcount

    conn.close()

    return count
💡 rowcount
cursor.rowcount를 사용하면 실제로 수정된 행이 있는지 확인할 수 있습니다.
14.10 사원 삭제하기
employee_repository.py
def delete(
    employee_id
):

    conn = get_connection()
    cursor = conn.cursor()

    cursor.execute(
        """
        DELETE FROM employee
        WHERE employee_id = ?
        """,
        (
            employee_id,
        )
    )

    conn.commit()

    count = cursor.rowcount

    conn.close()

    return count
Repository CRUD 함수
find_all() find_by_id() insert() update() delete()
백엔드 서버와 데이터베이스 구조
Repository는 DB를 담당하고 Service는 업무 규칙을 담당하도록 역할을 분리합니다.
14.11 Service 만들기
backend/services/employee_service.py
from repositories import (
    employee_repository
)


def validate_name(
    name
):

    if name.strip() == "":
        return False

    return True


def validate_phone(
    phone
):

    if phone == "":
        return True

    if not phone.startswith(
        "010-"
    ):
        return False

    return True

Service에서는 DB SQL 자체보다 이름과 전화번호 검증 같은 업무 규칙을 담당합니다.

14.12 Service에서 전체 조회
employee_service.py
def get_all_employees():

    return (
        employee_repository
        .find_all()
    )
14.13 Service에서 상세 조회
employee_service.py
def get_employee(
    employee_id
):

    return (
        employee_repository
        .find_by_id(
            employee_id
        )
    )
14.14 Service에서 등록 처리
employee_service.py
def create_employee(
    employee
):

    if not validate_name(
        employee.name
    ):
        raise ValueError(
            "이름을 입력하세요."
        )

    if not validate_phone(
        employee.phone
    ):
        raise ValueError(
            "전화번호 형식이 올바르지 않습니다."
        )

    name = (
        employee.name.strip()
    )

    employee_id = (
        employee_repository
        .insert(
            name,
            employee.phone,
            employee.department
        )
    )

    return employee_id
입력 데이터 ↓ 이름 검증 ↓ 전화번호 검증 ↓ 데이터 정리 ↓ Repository ↓ INSERT
14.15 Service에서 수정 처리
employee_service.py
def update_employee(
    employee_id,
    employee
):

    if not validate_name(
        employee.name
    ):
        raise ValueError(
            "이름을 입력하세요."
        )

    if not validate_phone(
        employee.phone
    ):
        raise ValueError(
            "전화번호 형식이 올바르지 않습니다."
        )

    return (
        employee_repository
        .update(
            employee_id,
            employee.name.strip(),
            employee.phone,
            employee.department
        )
    )
14.16 Service에서 삭제 처리
employee_service.py
def delete_employee(
    employee_id
):

    return (
        employee_repository
        .delete(
            employee_id
        )
    )
14.17 FastAPI Router 만들기
backend/routers/employee_router.py
from fastapi import (
    APIRouter,
    HTTPException
)

from models.employee import (
    EmployeeCreate,
    EmployeeUpdate
)

from services import (
    employee_service
)


router = APIRouter(
    prefix="/employees"
)
💡 prefix
prefix="/employees"를 지정하면 이 Router에 작성하는 모든 API 주소 앞에 /employees가 자동으로 붙습니다.
14.18 전체 조회 API
employee_router.py
@router.get("")
def get_employees():

    return (
        employee_service
        .get_all_employees()
    )
API 주소
GET /employees
14.19 상세 조회 API
employee_router.py
@router.get(
    "/{employee_id}"
)
def get_employee(
    employee_id: int
):

    employee = (
        employee_service
        .get_employee(
            employee_id
        )
    )

    if employee is None:
        raise HTTPException(
            status_code=404,
            detail=
            "사원을 찾을 수 없습니다."
        )

    return employee
요청 예
GET /employees/1
14.20 등록 API
employee_router.py
@router.post("")
def create_employee(
    employee: EmployeeCreate
):

    try:

        employee_id = (
            employee_service
            .create_employee(
                employee
            )
        )

        return {
            "employee_id":
                employee_id,
            "message":
                "등록되었습니다."
        }

    except ValueError as e:

        raise HTTPException(
            status_code=400,
            detail=str(e)
        )
14.21 수정 API
employee_router.py
@router.put(
    "/{employee_id}"
)
def update_employee(
    employee_id: int,
    employee: EmployeeUpdate
):

    try:

        count = (
            employee_service
            .update_employee(
                employee_id,
                employee
            )
        )

        if count == 0:
            raise HTTPException(
                status_code=404,
                detail=
                "사원을 찾을 수 없습니다."
            )

        return {
            "message":
                "수정되었습니다."
        }

    except ValueError as e:

        raise HTTPException(
            status_code=400,
            detail=str(e)
        )
14.22 삭제 API
employee_router.py
@router.delete(
    "/{employee_id}"
)
def delete_employee(
    employee_id: int
):

    count = (
        employee_service
        .delete_employee(
            employee_id
        )
    )

    if count == 0:
        raise HTTPException(
            status_code=404,
            detail=
            "사원을 찾을 수 없습니다."
        )

    return {
        "message":
            "삭제되었습니다."
    }
GET
/employees
GET
/employees/{id}
POST
/employees
PUT
/employees/{id}
DELETE
/employees/{id}
14.23 main.py 만들기
backend/main.py
from fastapi import FastAPI

from fastapi.middleware.cors import (
    CORSMiddleware
)

from routers.employee_router import (
    router as employee_router
)


app = FastAPI()


app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:5173"
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)


app.include_router(
    employee_router
)
main.py ↓ employee_router ↓ employee_service ↓ employee_repository ↓ SQLite
14.24 FastAPI 실행하기

backend 폴더에서 실행합니다.

Terminal
uvicorn main:app --reload
서버 주소
http://localhost:8000
API 문서
http://localhost:8000/docs
14.25 API 테스트

Swagger 문서 화면에서 다음 API를 테스트합니다.

API
GET     /employees
GET     /employees/1
POST    /employees
PUT     /employees/1
DELETE  /employees/1
⚠ Vue보다 API를 먼저 확인
Vue 프로그램을 연결하기 전에 FastAPI API가 정상적으로 동작하는지 먼저 확인하는 것이 좋습니다.
14.26 Vue 프로젝트 만들기
Terminal
npm create vite@latest frontend
선택 항목
Framework → Vue Variant → JavaScript
Terminal
cd frontend
npm install
npm install vue-router axios
14.27 Axios 설정하기
frontend/src/api/axios.js
import axios from 'axios'


const api = axios.create({
  baseURL:
    'http://localhost:8000'
})


export default api
기존 방식
JavaScript
axios.get(
  'http://localhost:8000/employees'
)
Axios 인스턴스 사용
JavaScript
api.get(
  '/employees'
)
💡 baseURL의 장점
API를 호출할 때마다 http://localhost:8000을 반복해서 작성하지 않아도 됩니다.
14.28 employeeApi.js 만들기
frontend/src/api/employeeApi.js
import api from './axios'


export function getEmployees() {

  return api.get(
    '/employees'
  )

}


export function getEmployee(id) {

  return api.get(
    '/employees/' + id
  )

}


export function createEmployee(
  employee
) {

  return api.post(
    '/employees',
    employee
  )

}


export function updateEmployee(
  id,
  employee
) {

  return api.put(
    '/employees/' + id,
    employee
  )

}


export function deleteEmployee(
  id
) {

  return api.delete(
    '/employees/' + id
  )

}

Vue 화면에서는 Axios를 직접 호출하지 않고 employeeApi.js에 정의한 함수들을 사용합니다.

14.29 Vue Router 만들기
frontend/src/router/index.js
import {
  createRouter,
  createWebHistory
} from 'vue-router'

import EmployeeListView
  from '../views/EmployeeListView.vue'

import EmployeeCreateView
  from '../views/EmployeeCreateView.vue'

import EmployeeDetailView
  from '../views/EmployeeDetailView.vue'

import EmployeeEditView
  from '../views/EmployeeEditView.vue'


const routes = [
  {
    path: '/',
    redirect: '/employees'
  },
  {
    path: '/employees',
    component:
      EmployeeListView
  },
  {
    path: '/employees/new',
    component:
      EmployeeCreateView
  },
  {
    path: '/employees/:id',
    component:
      EmployeeDetailView
  },
  {
    path: '/employees/:id/edit',
    component:
      EmployeeEditView
  }
]


const router = createRouter({
  history:
    createWebHistory(),
  routes
})


export default router
14.30 main.js 설정하기
frontend/src/main.js
import {
  createApp
} from 'vue'

import App
  from './App.vue'

import router
  from './router'


createApp(App)
  .use(router)
  .mount('#app')
14.31 App.vue 만들기
frontend/src/App.vue
<template>

  <div>

    <h1>
      사원관리 프로그램
    </h1>

    <nav>

      <router-link
        to="/employees"
      >
        사원목록
      </router-link>

      |

      <router-link
        to="/employees/new"
      >
        사원등록
      </router-link>

    </nav>

    <hr>

    <router-view />

  </div>

</template>
Vue 프론트엔드 개발 화면
이제 FastAPI 백엔드 위에 Vue 목록·등록·상세·수정 화면을 연결합니다.
14.32 사원 목록 화면 만들기
frontend/src/views/EmployeeListView.vue
<script setup>

import {
  ref,
  onMounted
} from 'vue'

import {
  getEmployees,
  deleteEmployee
} from '../api/employeeApi'


const employees = ref([])
const loading = ref(false)


async function loadEmployees() {

  loading.value = true

  try {

    const response =
      await getEmployees()

    employees.value =
      response.data

  } catch (error) {

    alert(
      '목록 조회 중 오류가 발생했습니다.'
    )

  } finally {

    loading.value = false

  }

}


async function removeEmployee(
  id
) {

  const result = confirm(
    '삭제하시겠습니까?'
  )

  if (!result) {
    return
  }

  try {

    await deleteEmployee(id)

    await loadEmployees()

  } catch (error) {

    alert(
      '삭제 중 오류가 발생했습니다.'
    )

  }

}


onMounted(
  loadEmployees
)

</script>


<template>

  <div>

    <h2>
      사원 목록
    </h2>

    <p v-if="loading">
      조회 중입니다.
    </p>

    <table
      v-else
      border="1"
    >

      <thead>
        <tr>
          <th>번호</th>
          <th>이름</th>
          <th>전화번호</th>
          <th>부서</th>
          <th>관리</th>
        </tr>
      </thead>

      <tbody>

        <tr
          v-for="emp in employees"
          :key="emp.employee_id"
        >

          <td>
            {{ emp.employee_id }}
          </td>

          <td>
            <router-link
              :to="
                '/employees/'
                + emp.employee_id
              "
            >
              {{ emp.name }}
            </router-link>
          </td>

          <td>
            {{ emp.phone }}
          </td>

          <td>
            {{ emp.department }}
          </td>

          <td>

            <router-link
              :to="
                '/employees/'
                + emp.employee_id
                + '/edit'
              "
            >
              수정
            </router-link>

            <button
              @click="
                removeEmployee(
                  emp.employee_id
                )
              "
            >
              삭제
            </button>

          </td>

        </tr>

      </tbody>

    </table>

  </div>

</template>
14.33 목록 조회 동작
화면이 열릴 때
onMounted(
  loadEmployees
)
API 호출
const response =
  await getEmployees()
EmployeeListView ↓ getEmployees() ↓ Axios ↓ GET /employees ↓ FastAPI Router ↓ Service ↓ Repository ↓ SELECT ↓ SQLite ↓ JSON ↓ employees.value ↓ v-for ↓ table
14.34 사원 등록 화면 만들기
EmployeeCreateView.vue
<script setup>

import {
  reactive
} from 'vue'

import {
  useRouter
} from 'vue-router'

import {
  createEmployee
} from '../api/employeeApi'


const router = useRouter()


const employee = reactive({
  name: '',
  phone: '',
  department:
    '개발팀'
})


async function save() {

  try {

    await createEmployee(
      employee
    )

    alert(
      '등록되었습니다.'
    )

    router.push(
      '/employees'
    )

  } catch (error) {

    if (
      error.response
      &&
      error.response.data
    ) {

      alert(
        error.response
          .data.detail
      )

    } else {

      alert(
        '등록 중 오류가 발생했습니다.'
      )

    }

  }

}

</script>


<template>

  <div>

    <h2>
      사원 등록
    </h2>

    <p>
      이름

      <input
        v-model="employee.name"
      >
    </p>

    <p>
      전화번호

      <input
        v-model="employee.phone"
      >
    </p>

    <p>
      부서

      <select
        v-model="
          employee.department
        "
      >
        <option value="개발팀">
          개발팀
        </option>
        <option value="영업팀">
          영업팀
        </option>
        <option value="관리팀">
          관리팀
        </option>
      </select>
    </p>

    <button @click="save">
      등록
    </button>

  </div>

</template>
14.35 등록 처리 동작

사용자가 이름, 전화번호, 부서를 입력하면 v-model을 통해 employee 객체에 저장됩니다.

등록 API 호출
await createEmployee(
  employee
)
input ↓ v-model ↓ employee ↓ POST /employees ↓ FastAPI ↓ Service 검증 ↓ Repository ↓ INSERT ↓ SQLite
14.36 상세 조회 화면 만들기
EmployeeDetailView.vue
<script setup>

import {
  ref,
  onMounted
} from 'vue'

import {
  useRoute
} from 'vue-router'

import {
  getEmployee
} from '../api/employeeApi'


const route = useRoute()

const employee = ref(null)


async function loadEmployee() {

  try {

    const response =
      await getEmployee(
        route.params.id
      )

    employee.value =
      response.data

  } catch (error) {

    alert(
      '사원 조회 중 오류가 발생했습니다.'
    )

  }

}


onMounted(
  loadEmployee
)

</script>


<template>

  <div>

    <h2>
      사원 상세정보
    </h2>

    <div v-if="employee">

      <p>
        번호 :
        {{ employee.employee_id }}
      </p>

      <p>
        이름 :
        {{ employee.name }}
      </p>

      <p>
        전화번호 :
        {{ employee.phone }}
      </p>

      <p>
        부서 :
        {{ employee.department }}
      </p>

      <router-link
        :to="
          '/employees/'
          + employee.employee_id
          + '/edit'
        "
      >
        수정
      </router-link>

    </div>

  </div>

</template>
14.37 URL에서 사원번호 가져오기
주소 예
/employees/3
Router 설정
{
  path:
    '/employees/:id'
}
Vue
const route = useRoute()

route.params.id
결과
3
API 호출
getEmployee(
  route.params.id
)
14.38 사원 수정 화면 만들기
EmployeeEditView.vue
<script setup>

import {
  reactive,
  onMounted
} from 'vue'

import {
  useRoute,
  useRouter
} from 'vue-router'

import {
  getEmployee,
  updateEmployee
} from '../api/employeeApi'


const route = useRoute()
const router = useRouter()


const employee = reactive({
  name: '',
  phone: '',
  department: ''
})


async function loadEmployee() {

  const response =
    await getEmployee(
      route.params.id
    )

  employee.name =
    response.data.name

  employee.phone =
    response.data.phone

  employee.department =
    response.data.department
}


async function save() {

  try {

    await updateEmployee(
      route.params.id,
      employee
    )

    alert(
      '수정되었습니다.'
    )

    router.push(
      '/employees'
    )

  } catch (error) {

    if (
      error.response
      &&
      error.response.data
    ) {

      alert(
        error.response
          .data.detail
      )

    }

  }

}


onMounted(
  loadEmployee
)

</script>


<template>

  <div>

    <h2>
      사원 수정
    </h2>

    <p>
      이름

      <input
        v-model="employee.name"
      >
    </p>

    <p>
      전화번호

      <input
        v-model="employee.phone"
      >
    </p>

    <p>
      부서

      <select
        v-model="
          employee.department
        "
      >
        <option value="개발팀">
          개발팀
        </option>
        <option value="영업팀">
          영업팀
        </option>
        <option value="관리팀">
          관리팀
        </option>
      </select>
    </p>

    <button @click="save">
      수정
    </button>

  </div>

</template>
14.39 수정 처리 흐름
수정 화면 진입
/employees/1/edit ↓ GET /employees/1 ↓ 기존 데이터 조회 ↓ input에 표시
수정 버튼 클릭
수정된 employee ↓ PUT /employees/1 ↓ FastAPI ↓ Service ↓ Repository ↓ UPDATE ↓ SQLite
14.40 삭제 처리
삭제 버튼
<button
  @click="
    removeEmployee(
      emp.employee_id
    )
  "
>
  삭제
</button>
삭제 함수
async function removeEmployee(
  id
) {

  const result = confirm(
    '삭제하시겠습니까?'
  )

  if (!result) {
    return
  }

  await deleteEmployee(id)

  await loadEmployees()

}
DELETE ↓ DB 삭제 ↓ GET ↓ 새 목록 조회 ↓ 화면 갱신
14.41 프론트엔드 실행하기
frontend Terminal
npm run dev
프로그램 주소
Vue http://localhost:5173
FastAPI http://localhost:8000
터미널 1 - FastAPI
Terminal
cd backend
uvicorn main:app --reload
터미널 2 - Vue
Terminal
cd frontend
npm run dev
14.42 실제 프로그램 처리 과정
브라우저에서 서버까지
브라우저 ↓ Vue Router ↓ EmployeeListView.vue ↓ onMounted() ↓ getEmployees() ↓ Axios ↓ GET /employees
FastAPI 내부
employee_router ↓ employee_service ↓ employee_repository ↓ SQLite
조회 결과가 화면으로 돌아오는 과정
SQLite ↓ Repository ↓ Service ↓ Router ↓ JSON ↓ Axios ↓ employees.value ↓ v-for ↓ 화면
⭐ 통합 실습의 핵심 흐름
요청은 Vue에서 DB 방향으로 내려가고, 조회 결과는 SQLite에서 Vue 화면 방향으로 다시 올라옵니다.
14.43 기존 Tkinter 프로그램과 직접 비교하기
Tkinter 목록 조회
Python
def load_employees():

    rows = (
        repository.find_all()
    )

    for row in rows:

        tree.insert(
            "",
            "end",
            values=row
        )
Vue 목록 조회
Vue
async function loadEmployees() {

  const response =
    await getEmployees()

  employees.value =
    response.data

}
Tkinter Vue 웹 프로그램
화면 → Python 함수 직접 호출 → DB 화면 → HTTP → FastAPI → Python 함수 → DB
가장 중요한 차이
Vue는 Repository를 직접 호출하지 않습니다. 반드시 HTTP 요청을 통해 FastAPI 서버와 통신합니다.
14.44 기존 Python 코드를 재사용하는 위치

기존 프로그램에 다음과 같은 파일이 있었다고 가정합니다.

employee_manager.py employee_repository.py employee_service.py

화면과 독립적으로 작성된 employee_repository.pyemployee_service.py는 FastAPI 프로젝트에서도 상당 부분 그대로 활용할 수 있습니다.

기존
Tkinter → Service → Repository → SQLite
Vue → FastAPI Router → Service → Repository → SQLite
⭐ 실제 교체되는 핵심 영역
기존 Tkinter UI는 Vue로 교체하고, Vue와 기존 Python 로직 사이에 FastAPI Router를 추가합니다.
14.45 프로그램 확장하기

사원관리 프로그램을 완성했다면 같은 구조로 회원관리, 상품관리, 게시판 등을 만들 수 있습니다.

회원관리
MemberListView.vue MemberCreateView.vue MemberEditView.vue member_router.py member_service.py member_repository.py
상품관리
ProductListView.vue ProductCreateView.vue ProductEditView.vue product_router.py product_service.py product_repository.py
게시판
BoardListView.vue BoardWriteView.vue BoardDetailView.vue board_router.py board_service.py board_repository.py
Vue View ↓ API ↓ FastAPI Router ↓ Service ↓ Repository ↓ Database
14.46 실습 문제
🎯 실습 목표
기존 사원관리 프로그램에 position(직급) 컬럼을 추가해 보세요.
SQL
ALTER TABLE employee
ADD COLUMN position TEXT;

다음 부분도 함께 수정해야 합니다.

1
EmployeeCreate 모델
2
EmployeeUpdate 모델
3
Repository의 SELECT / INSERT / UPDATE
4
Service
5
Vue 등록 화면
6
Vue 수정 화면
7
Vue 목록 화면
Vue 직급 선택
Vue
<select
  v-model="employee.position"
>

  <option value="사원">
    사원
  </option>

  <option value="대리">
    대리
  </option>

  <option value="과장">
    과장
  </option>

  <option value="차장">
    차장
  </option>

  <option value="부장">
    부장
  </option>

</select>
DB ↓ Repository ↓ Service ↓ API ↓ Vue
💡 실습에서 확인해야 할 것
하나의 필드를 추가했을 때 데이터베이스부터 Vue 화면까지 모든 계층이 하나의 데이터 흐름으로 연결되어 있다는 점을 확인해 보세요.
📌 Chapter 14 핵심 정리
  • 전체 애플리케이션의 기본 구조는 Vue → Axios → FastAPI → Service → Repository → SQLite입니다.
  • Vue는 화면 표시와 사용자 입력을 담당합니다.
  • Axios는 Vue와 FastAPI 사이의 HTTP 통신을 담당합니다.
  • FastAPI Router는 GET, POST, PUT, DELETE 요청을 받습니다.
  • Service는 이름·전화번호 검증과 같은 업무 규칙을 담당합니다.
  • Repository는 SQLite의 SELECT, INSERT, UPDATE, DELETE를 담당합니다.
  • 목록 화면은 onMounted()에서 GET API를 호출하고 결과를 employees.value에 저장합니다.
  • 등록 화면은 v-model로 입력값을 객체에 저장하고 POST 요청으로 서버에 전달합니다.
  • 상세 화면은 route.params.id를 이용하여 URL의 사원번호를 가져옵니다.
  • 수정 화면은 기존 정보를 GET으로 조회한 뒤 변경된 데이터를 PUT 요청으로 전달합니다.
  • 삭제 후 다시 GET 요청을 실행하면 최신 목록으로 화면을 갱신할 수 있습니다.
  • 기존 Tkinter 프로그램과 웹 프로그램의 가장 큰 차이는 화면과 Python 코드 사이에 HTTP와 FastAPI가 추가된다는 것입니다.
  • 기존 Service와 Repository가 화면과 독립적으로 작성되어 있다면 FastAPI 프로젝트에서도 상당 부분 재사용할 수 있습니다.
  • 회원관리, 상품관리, 게시판도 같은 View → API → Router → Service → Repository → Database 구조로 확장할 수 있습니다.
  • 새로운 필드를 하나 추가할 때도 DB → Repository → Service → API → Vue 전체 흐름을 함께 수정해야 합니다.

프로그램·홈페이지·강의가 필요하신가요?

프로그램 판매, 무료 다운로드, 홈페이지 제작, IT 강의 상담을 도와드립니다.

상담 신청하기