CRUD 프로그램
사원 등록·조회·수정·삭제 기능을 하나의 프로그램으로 완성합니다.
CRUD는 프로그램에서 데이터를 관리할 때 사용하는 가장 기본적인 네 가지 기능을 의미합니다.
POST
GET
PUT
DELETE
| 기능 | 의미 | HTTP Method |
|---|---|---|
| Create | 데이터 등록 | POST |
| Read | 데이터 조회 | GET |
| Update | 데이터 수정 | PUT |
| Delete | 데이터 삭제 | DELETE |
- 사원 등록 → Create
- 사원 조회 → Read
- 사원 수정 → Update
- 사원 삭제 → Delete
데이터베이스를 연결하기 전에 Python 리스트 형태의 간단한 사원 데이터를 이용하여 Vue와 FastAPI 사이에서 CRUD가 어떻게 처리되는지 전체 흐름을 익힙니다.
- 사원 목록 출력
- 사원 정보 입력
- 등록 버튼 처리
- 수정 버튼 처리
- 삭제 버튼 처리
- 화면 이동
- 사원 목록 반환
- 사원 한 명 반환
- 새로운 사원 등록
- 기존 사원 수정
- 사원 삭제
먼저 FastAPI와 Uvicorn을 설치합니다.
pip install fastapi uvicorn
프로젝트 폴더는 다음과 같이 준비합니다.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def home():
return {
"message": "사원관리 API"
}
서버를 실행합니다.
uvicorn main:app --reload
브라우저에서 다음 주소를 확인합니다.
http://localhost:8000
http://localhost:8000/docs
FastAPI가 제공하는 API 문서 화면에서 GET, POST, PUT, DELETE 요청을 직접 테스트할 수 있습니다.
이번 장에서는 SQLite를 사용하기 전에 Python 리스트에 사원 데이터를 저장하여 CRUD 동작부터 확인합니다.
employees = [
{
"employee_id": 1,
"name": "홍길동",
"phone": "010-1111-1111",
"department": "개발팀"
},
{
"employee_id": 2,
"name": "김철수",
"phone": "010-2222-2222",
"department": "영업팀"
},
{
"employee_id": 3,
"name": "이영희",
"phone": "010-3333-3333",
"department": "관리팀"
}
]
| 번호 | 이름 | 전화번호 | 부서 |
|---|---|---|---|
| 1 | 홍길동 | 010-1111-1111 | 개발팀 |
| 2 | 김철수 | 010-2222-2222 | 영업팀 |
| 3 | 이영희 | 010-3333-3333 | 관리팀 |
Python 리스트에 저장한 값이므로 서버를 종료하면 변경된 데이터는 사라집니다. 이번 장의 목적은 데이터 영구 저장이 아니라 Vue와 FastAPI를 이용한 CRUD 처리 방법을 이해하는 것입니다.
전체 사원을 반환하는 API를 작성합니다.
@app.get("/employees")
def get_employees():
return employees
http://localhost:8000/employees
사원번호를 이용하여 특정 사원 한 명의 정보를 조회합니다.
from fastapi import FastAPI, HTTPException
@app.get("/employees/{employee_id}")
def get_employee(employee_id: int):
for employee in employees:
if employee["employee_id"] == employee_id:
return employee
raise HTTPException(
status_code=404,
detail="사원을 찾을 수 없습니다."
)
GET /employees/1
/employees/2 → employee_id = 2
/employees/3 → employee_id = 3
POST와 PUT에서는 Vue가 JSON 데이터를 FastAPI로 전달합니다.
{
"name": "박민수",
"phone": "010-4444-4444",
"department": "개발팀"
}
FastAPI에서 이 데이터를 받을 수 있도록 Pydantic 모델을 작성합니다.
from pydantic import BaseModel
class Employee(BaseModel):
name: str
phone: str
department: str
이제 다음과 같이 전달받은 값을 사용할 수 있습니다.
employee.name employee.phone employee.department
새로운 사원을 등록하는 API를 작성합니다.
@app.post("/employees")
def create_employee(employee: Employee):
new_id = max(
[e["employee_id"] for e in employees],
default=0
) + 1
new_employee = {
"employee_id": new_id,
"name": employee.name,
"phone": employee.phone,
"department": employee.department
}
employees.append(
new_employee
)
return new_employee
Vue가 다음 데이터를 보낸다고 가정합니다.
{
"name": "박민수",
"phone": "010-4444-4444",
"department": "개발팀"
}
기존 사원 정보를 수정합니다.
@app.put("/employees/{employee_id}")
def update_employee(
employee_id: int,
employee: Employee
):
for item in employees:
if item["employee_id"] == employee_id:
item["name"] = employee.name
item["phone"] = employee.phone
item["department"] = employee.department
return item
raise HTTPException(
status_code=404,
detail="사원을 찾을 수 없습니다."
)
PUT /employees/1
{
"name": "홍길동",
"phone": "010-9999-9999",
"department": "영업팀"
}
→ 이름 수정 → 전화번호 수정 → 부서 수정
→ 수정된 데이터 반환
선택한 사원을 목록에서 삭제합니다.
@app.delete("/employees/{employee_id}")
def delete_employee(employee_id: int):
for employee in employees:
if employee["employee_id"] == employee_id:
employees.remove(
employee
)
return {
"message": "삭제되었습니다."
}
raise HTTPException(
status_code=404,
detail="사원을 찾을 수 없습니다."
)
DELETE /employees/1
| 기능 | Method | URL |
|---|---|---|
| 전체 조회 | GET | /employees |
| 상세 조회 | GET | /employees/{id} |
| 등록 | POST | /employees |
| 수정 | PUT | /employees/{id} |
| 삭제 | DELETE | /employees/{id} |
REST API에서는 같은 /employees 주소라도 HTTP Method에 따라 수행하는 작업을 구분할 수 있습니다.
POST /employees → 등록
PUT /employees/1 → 수정
DELETE /employees/1 → 삭제
Vue와 FastAPI는 서로 다른 주소에서 실행됩니다.
| 프로그램 | 주소 |
|---|---|
| Vue | http://localhost:5173 |
| FastAPI | http://localhost:8000 |
Vue에서 FastAPI를 호출할 수 있도록 CORS 설정을 추가합니다.
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:5173"
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
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=["*"]
)
employees = [
{
"employee_id": 1,
"name": "홍길동",
"phone": "010-1111-1111",
"department": "개발팀"
},
{
"employee_id": 2,
"name": "김철수",
"phone": "010-2222-2222",
"department": "영업팀"
},
{
"employee_id": 3,
"name": "이영희",
"phone": "010-3333-3333",
"department": "관리팀"
}
]
class Employee(BaseModel):
name: str
phone: str
department: str
@app.get("/employees")
def get_employees():
return employees
@app.get("/employees/{employee_id}")
def get_employee(employee_id: int):
for employee in employees:
if employee["employee_id"] == employee_id:
return employee
raise HTTPException(
status_code=404,
detail="사원을 찾을 수 없습니다."
)
@app.post("/employees")
def create_employee(employee: Employee):
new_id = max(
[e["employee_id"] for e in employees],
default=0
) + 1
new_employee = {
"employee_id": new_id,
"name": employee.name,
"phone": employee.phone,
"department": employee.department
}
employees.append(
new_employee
)
return new_employee
@app.put("/employees/{employee_id}")
def update_employee(
employee_id: int,
employee: Employee
):
for item in employees:
if item["employee_id"] == employee_id:
item["name"] = employee.name
item["phone"] = employee.phone
item["department"] = employee.department
return item
raise HTTPException(
status_code=404,
detail="사원을 찾을 수 없습니다."
)
@app.delete("/employees/{employee_id}")
def delete_employee(employee_id: int):
for employee in employees:
if employee["employee_id"] == employee_id:
employees.remove(
employee
)
return {
"message": "삭제되었습니다."
}
raise HTTPException(
status_code=404,
detail="사원을 찾을 수 없습니다."
)
서버를 실행합니다.
uvicorn main:app --reload
먼저 FastAPI 문서에서 CRUD API가 정상적으로 동작하는지 확인합니다.
http://localhost:8000/docs
기존 Vue 프로젝트에서 Axios와 Vue Router를 사용합니다. 설치되어 있지 않다면 다음 명령을 실행합니다.
npm install axios vue-router
서버 통신 코드를 화면 파일마다 반복하지 않도록 employeeApi.js 파일로 분리합니다.
import axios from 'axios'
const API_URL =
'http://localhost:8000/employees'
export function getEmployees() {
return axios.get(
API_URL
)
}
export function getEmployee(id) {
return axios.get(
API_URL + '/' + id
)
}
export function createEmployee(employee) {
return axios.post(
API_URL,
employee
)
}
export function updateEmployee(
id,
employee
) {
return axios.put(
API_URL + '/' + id,
employee
)
}
export function deleteEmployee(id) {
return axios.delete(
API_URL + '/' + id
)
}
| 함수 | 기능 |
|---|---|
| getEmployees() | 전체 조회 |
| getEmployee(id) | 상세 조회 |
| createEmployee() | 등록 |
| updateEmployee() | 수정 |
| deleteEmployee() | 삭제 |
<script setup>
import {
ref,
onMounted
} from 'vue'
import {
getEmployees,
deleteEmployee
} from '../api/employeeApi'
const employees = ref([])
async function loadEmployees() {
const response =
await getEmployees()
employees.value =
response.data
}
async function removeEmployee(id) {
const result = confirm(
'삭제하시겠습니까?'
)
if (!result) {
return
}
await deleteEmployee(id)
await loadEmployees()
}
onMounted(
loadEmployees
)
</script>
<template>
<div>
<h2>사원 목록</h2>
<router-link
to="/employees/new"
>
사원 등록
</router-link>
<table 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>
→ employees.value → v-for → 목록 출력
deleteEmployee()가 끝난 뒤 loadEmployees()를 다시 호출하면 서버에 남아 있는 최신 사원목록을 받아 화면에 표시할 수 있습니다.
<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() {
await createEmployee(
employee
)
alert(
'등록되었습니다.'
)
router.push(
'/employees'
)
}
</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>
→ POST /employees → FastAPI → 등록
<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() {
const response =
await getEmployee(
route.params.id
)
employee.value =
response.data
}
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>
</div>
</div>
</template>
예를 들어 현재 주소가 다음과 같다고 가정합니다.
/employees/2
수정 화면에서는 먼저 기존 사원 데이터를 가져와 입력창에 표시한 후, 사용자가 값을 변경하면 PUT 요청으로 서버에 전달합니다.
<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() {
await updateEmployee(
route.params.id,
employee
)
alert(
'수정되었습니다.'
)
router.push(
'/employees'
)
}
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>
↓
사용자가 데이터 수정
↓
PUT /employees/2 → FastAPI → 데이터 수정
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
| URL | 화면 |
|---|---|
| /employees | 사원 목록 |
| /employees/new | 사원 등록 |
| /employees/1 | 사원 상세 |
| /employees/1/edit | 사원 수정 |
import {
createApp
} from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
.use(router)가 있어야 Vue 애플리케이션에서 router-link, router-view, useRoute(), useRouter() 등을 사용할 수 있습니다.
<template>
<div>
<h1>
사원관리 프로그램
</h1>
<nav>
<router-link
to="/employees"
>
사원 목록
</router-link>
|
<router-link
to="/employees/new"
>
사원 등록
</router-link>
</nav>
<hr>
<router-view />
</div>
</template>
router-view 위치에 현재 URL과 연결된 화면이 표시됩니다.
/employees/new → EmployeeCreateView
/employees/1 → EmployeeDetailView
/employees/1/edit → EmployeeEditView
uvicorn main:app --reload
npm run dev
사용자가 사용하는 화면은 Vue이므로 브라우저에서는 http://localhost:5173으로 접속합니다.
사용자가 다음 사원을 등록한다고 가정해 봅니다.
전화번호 : 010-4444-4444
부서 : 개발팀
Vue의 v-model에 의해 다음 데이터가 만들어집니다.
{
name: '박민수',
phone: '010-4444-4444',
department: '개발팀'
}
등록 버튼을 누르면 다음 순서로 처리됩니다.
↓
createEmployee()
↓
Axios POST
↓
POST /employees
↓
FastAPI
↓
employees.append()
↓
JSON 반환
↓
Vue 목록 화면 이동
목록 화면이 다시 열리면 onMounted()에 의해 GET 요청이 다시 실행됩니다.
GET /employees
등록이 끝난 뒤 목록 화면으로 이동하면 최신 사원목록을 서버에서 다시 가져오기 때문에 새로 등록한 사원이 화면에 표시됩니다.
→ 데이터 처리 → JSON → Vue 화면
| 기능 | Vue | FastAPI |
|---|---|---|
| 목록 조회 | getEmployees() | GET /employees |
| 상세 조회 | getEmployee(id) | GET /employees/{id} |
| 등록 | createEmployee() | POST /employees |
| 수정 | updateEmployee() | PUT /employees/{id} |
| 삭제 | deleteEmployee() | DELETE /employees/{id} |
Vue에서는 사용자의 입력과 화면을 관리하고, Axios를 이용해 서버에 요청합니다.
FastAPI에서는 요청받은 데이터를 이용해 실제 조회·등록·수정·삭제 작업을 처리한 뒤 결과를 JSON으로 반환합니다.
- CRUD는 Create, Read, Update, Delete의 약자입니다.
- Create는 POST, Read는 GET, Update는 PUT, Delete는 DELETE와 연결할 수 있습니다.
- 이번 프로그램의 기본 구조는 Vue → Axios → REST API → FastAPI입니다.
- FastAPI에서는 Python 리스트를 이용하여 CRUD의 전체 동작을 연습했습니다.
- GET /employees는 전체 사원을 조회합니다.
- GET /employees/{id}는 특정 사원 한 명을 조회합니다.
- POST /employees는 새로운 사원을 등록합니다.
- PUT /employees/{id}는 기존 사원정보를 수정합니다.
- DELETE /employees/{id}는 사원을 삭제합니다.
- POST와 PUT으로 들어오는 JSON 데이터는 Pydantic 모델을 이용해 받을 수 있습니다.
- 존재하지 않는 사원을 조회하거나 수정·삭제할 때 HTTPException을 이용해 404 오류를 처리합니다.
- Vue와 FastAPI의 주소가 다르므로 Vue에서 API를 호출할 수 있도록 CORS를 설정합니다.
- Vue에서는 API 호출 코드를 employeeApi.js로 분리하여 관리할 수 있습니다.
- EmployeeListView는 목록 조회와 삭제를 담당합니다.
- EmployeeCreateView는 사원 등록을 담당합니다.
- EmployeeDetailView는 특정 사원의 상세정보를 보여줍니다.
- EmployeeEditView는 기존 데이터를 조회한 뒤 수정합니다.
- Vue Router는 목록·등록·상세·수정 화면을 URL과 연결합니다.
- 등록이나 수정이 끝난 뒤 router.push('/employees')로 목록 화면으로 이동할 수 있습니다.
- 삭제 후 loadEmployees()를 다시 호출하면 최신 목록을 서버에서 다시 가져올 수 있습니다.
- CRUD 프로그램의 전체 흐름은 사용자 입력 → Vue → Axios → FastAPI → 데이터 처리 → JSON → Vue 화면 갱신입니다.