REST API 연동

VUE.JS BASIC
Chapter 8. REST API 연동
Vue에서 Axios로 FastAPI 서버에 요청하고,
JSON 데이터를 받아 화면에 출력하는 전체 흐름을 배웁니다.
서버와 네트워크 장비가 연결된 데이터센터
실제 웹 프로그램의 데이터는 Vue 파일이 아니라 서버와 데이터베이스에서 관리하는 경우가 많습니다.
8.1 REST API란?

지금까지 만든 Vue 예제에서는 사원 데이터를 Vue 파일 안에 직접 작성했습니다.

Vue 파일 내부 데이터
const employees = ref([
  {
    id: 1,
    name: '홍길동',
    department: '개발팀'
  },
  {
    id: 2,
    name: '김철수',
    department: '영업팀'
  }
])

연습 단계에서는 편리하지만 실제 프로그램에서는 일반적으로 사원정보를 Vue 파일에 직접 저장하지 않습니다. 실제 데이터는 데이터베이스에서 관리하는 경우가 많습니다.

Vue → 서버 → Database

이번 교재에서는 다음과 같은 구조를 생각해 볼 수 있습니다.

Vue 화면
REST API 통신
FastAPI 서버
Database 데이터
💡 핵심 구조

Vue는 화면을 담당하고, FastAPI는 서버 프로그램을 담당합니다. 데이터는 필요에 따라 SQLite, MySQL, PostgreSQL 같은 데이터베이스에 저장할 수 있습니다.

8.2 Vue와 FastAPI의 역할
구분 역할
Vue 화면 출력, 사용자 입력
FastAPI 데이터 처리
Database 데이터 저장
REST API Vue와 FastAPI 사이의 데이터 전달

사용자가 사원목록 화면을 열었을 때의 흐름을 단순하게 표현하면 다음과 같습니다.

사용자 → Vue → 사원목록 요청 → FastAPI → Database 조회
→ FastAPI → JSON 전달 → Vue → 화면 출력
8.3 JSON

Vue와 서버 사이에서는 주로 JSON 형식으로 데이터를 주고받습니다.

사원 한 명의 JSON
{
  "id": 1,
  "name": "홍길동",
  "department": "개발팀"
}

사원이 여러 명이면 배열 안에 여러 객체가 들어가는 형태가 됩니다.

여러 사원의 JSON
[
  {
    "id": 1,
    "name": "홍길동",
    "department": "개발팀"
  },
  {
    "id": 2,
    "name": "김철수",
    "department": "영업팀"
  },
  {
    "id": 3,
    "name": "이영희",
    "department": "관리팀"
  }
]
💡 Vue에서는 어떻게 사용할까?

서버에서 배열 형태의 JSON 데이터를 받으면 Vue에서는 이를 변수에 저장하고 v-for로 반복 출력할 수 있습니다.

8.4 REST API 주소

서버는 특정 URL을 통해 데이터를 제공합니다. 예를 들어 FastAPI가 다음 주소에서 실행된다고 가정하겠습니다.

FastAPI 기본 주소
http://localhost:8000
방식 주소 기능
GET /employees 전체 사원 조회
GET /employees/1 1번 사원 조회
POST /employees 사원 등록
PUT /employees/1 1번 사원 수정
DELETE /employees/1 1번 사원 삭제
📘 이번 장의 중심

이번 장에서는 먼저 GET 요청을 이용하여 서버 데이터를 가져오는 방법을 중심으로 학습합니다.

8.5 FastAPI 테스트 서버 만들기

Vue와 연결하기 전에 간단한 FastAPI 서버를 만들어 봅니다.

Terminal
pip install fastapi uvicorn
main.py
main.py
from fastapi import FastAPI

app = FastAPI()

employees = [
    {
        "id": 1,
        "name": "홍길동",
        "department": "개발팀"
    },
    {
        "id": 2,
        "name": "김철수",
        "department": "영업팀"
    },
    {
        "id": 3,
        "name": "이영희",
        "department": "관리팀"
    }
]

@app.get("/employees")
def get_employees():
    return employees

서버를 실행합니다.

Terminal
uvicorn main:app --reload

브라우저에서 다음 주소에 접속합니다.

Browser
http://localhost:8000/employees
실행 결과
[ { "id": 1, "name": "홍길동", "department": "개발팀" }, { "id": 2, "name": "김철수", "department": "영업팀" }, { "id": 3, "name": "이영희", "department": "관리팀" } ]

이제 FastAPI가 Vue에게 데이터를 제공할 준비가 되었습니다.

8.6 Axios란?

Vue에서 서버로 HTTP 요청을 보내는 방법에는 fetch()Axios 등이 있습니다.

💡 Axios

Axios는 JavaScript에서 HTTP 요청을 편리하게 처리할 수 있도록 도와주는 라이브러리입니다. 이번 교재에서는 Axios를 사용합니다.

Vue 프로젝트 폴더의 터미널에서 설치합니다.

Terminal
npm install axios
8.7 Axios 기본 사용법

먼저 Axios를 불러옵니다.

JavaScript
import axios from 'axios'

GET 요청의 기본 형태는 다음과 같습니다.

GET 요청
axios.get(
  'http://localhost:8000/employees'
)

서버에서 응답한 결과를 변수에 저장할 수도 있습니다.

응답 데이터 사용
const response = await axios.get(
  'http://localhost:8000/employees'
)

console.log(response.data)
💡 실제 데이터 위치

Axios로 받은 응답에서 서버가 전달한 실제 데이터는 response.data에 들어 있습니다.

8.8 async와 await

서버에 요청을 보낸다고 해서 결과가 즉시 들어오는 것은 아닙니다.

Vue → 서버 요청 → 서버 처리 → 응답

따라서 응답이 올 때까지 기다리는 과정이 필요합니다. 이때 asyncawait를 사용할 수 있습니다.

async / await
async function loadEmployees() {
  const response = await axios.get(
    'http://localhost:8000/employees'
  )
}
💡 초보자용 이해

await는 여기서는 쉽게 "서버의 응답이 올 때까지 기다린다"라고 이해하면 됩니다.

await를 사용하는 함수 앞에는 async를 붙입니다.

노트북을 이용해 웹 프로그래밍을 하는 모습
Vue는 서버에 데이터를 요청하고, 응답으로 받은 값을 반응형 데이터에 저장해 화면을 자동으로 갱신합니다.
8.9 Vue에서 서버 데이터 가져오기

이제 FastAPI의 사원 데이터를 Vue에서 직접 가져와 보겠습니다.

Vue 예제
<script setup>
import { ref } from 'vue'
import axios from 'axios'

const employees = ref([])

async function loadEmployees() {
  const response = await axios.get(
    'http://localhost:8000/employees'
  )

  employees.value = response.data
}
</script>

<template>
  <h1>사원목록</h1>

  <button @click="loadEmployees">
    사원 불러오기
  </button>

  <ul>
    <li
      v-for="emp in employees"
      :key="emp.id"
    >
      {{ emp.id }}
      {{ emp.name }}
      {{ emp.department }}
    </li>
  </ul>
</template>
처음 화면
사원목록
[사원 불러오기]
버튼 클릭 후
1 홍길동 개발팀
2 김철수 영업팀
3 이영희 관리팀
8.10 데이터 처리 흐름

처음에는 빈 배열을 준비합니다.

빈 배열
const employees = ref([])

Axios로 서버에 데이터를 요청합니다.

서버 요청
const response = await axios.get(
  'http://localhost:8000/employees'
)

받은 데이터를 Vue의 반응형 데이터에 저장합니다.

Vue 데이터 변경
employees.value = response.data
FastAPI → JSON → Axios → response.data
→ employees.value → v-for → 화면 출력
💡 Vue의 반응형 처리

employees가 변경되면 Vue가 변경을 감지하고 화면을 자동으로 다시 출력합니다.

8.11 JavaScript DOM 방식과 비교

일반 JavaScript에서는 서버 데이터를 받아 HTML 문자열을 직접 만들고 DOM을 수정하는 방식을 사용할 수 있습니다.

일반 JavaScript 예
fetch('/employees')
  .then(response => response.json())
  .then(data => {
    let html = ''

    data.forEach(emp => {
      html += `
        <li>
          ${emp.name}
        </li>
      `
    })

    document.getElementById(
      'employeeList'
    ).innerHTML = html
  })

Vue에서는 서버 데이터를 변수에 넣는 방식으로 처리할 수 있습니다.

Vue 데이터 변경
employees.value = response.data
template
<li
  v-for="emp in employees"
  :key="emp.id"
>
  {{ emp.name }}
</li>
방식 흐름
일반 DOM 서버 데이터 → HTML 생성 → DOM 찾기 → innerHTML 변경
Vue 서버 데이터 → employees 변경 → 화면 자동 갱신
8.12 onMounted()

실제 프로그램에서는 버튼을 누르지 않아도 화면이 열리는 순간 데이터를 자동으로 가져오는 경우가 많습니다.

import
import {
  ref,
  onMounted
} from 'vue'
기본 사용법
onMounted(() => {
  loadEmployees()
})
전체 예제
EmployeeListView.vue
<script setup>
import {
  ref,
  onMounted
} from 'vue'

import axios from 'axios'

const employees = ref([])

async function loadEmployees() {
  const response = await axios.get(
    'http://localhost:8000/employees'
  )

  employees.value = response.data
}

onMounted(() => {
  loadEmployees()
})
</script>

<template>
  <h1>사원목록</h1>

  <ul>
    <li
      v-for="emp in employees"
      :key="emp.id"
    >
      {{ emp.name }}
    </li>
  </ul>
</template>
EmployeeListView 생성 → onMounted()
→ loadEmployees() → FastAPI 요청
→ 사원 데이터 → 화면 출력
8.13 onMounted()란?

onMounted()는 컴포넌트가 화면에 만들어진 후 실행되는 함수입니다.

Vue 컴포넌트 생성 → 화면에 연결 → onMounted 실행

화면을 열자마자 서버 데이터를 불러오는 작업에 사용할 수 있습니다.

  • 게시판 화면 열기 → 게시글 조회
  • 상품목록 화면 열기 → 상품 조회
  • 사원목록 화면 열기 → 사원 조회
8.14 테이블로 서버 데이터 출력하기
EmployeeListView.vue
<script setup>
import {
  ref,
  onMounted
} from 'vue'

import axios from 'axios'

const employees = ref([])

async function loadEmployees() {
  const response = await axios.get(
    'http://localhost:8000/employees'
  )

  employees.value = response.data
}

onMounted(() => {
  loadEmployees()
})
</script>

<template>
  <h1>사원목록</h1>

  <table border="1">
    <thead>
      <tr>
        <th>번호</th>
        <th>이름</th>
        <th>부서</th>
      </tr>
    </thead>

    <tbody>
      <tr
        v-for="emp in employees"
        :key="emp.id"
      >
        <td>{{ emp.id }}</td>
        <td>{{ emp.name }}</td>
        <td>{{ emp.department }}</td>
      </tr>
    </tbody>
  </table>
</template>
💡 달라진 점

이제 사원 데이터는 Vue 파일 안에 직접 작성되어 있는 것이 아니라 FastAPI 서버에서 전달받습니다.

8.15 오류 처리

서버가 실행되지 않았거나 네트워크 문제가 발생하면 요청에 실패할 수 있습니다. 따라서 서버 통신에는 오류 처리를 추가하는 것이 좋습니다.

try ~ catch
async function loadEmployees() {
  try {
    const response = await axios.get(
      'http://localhost:8000/employees'
    )

    employees.value = response.data

  } catch (error) {
    console.log(error)

    alert(
      '사원정보를 불러오지 못했습니다.'
    )
  }
}
서버 요청
├─ 성공 → response.data
└─ 실패 → catch → 오류 처리
8.16 로딩 상태 처리

서버 응답을 기다리는 동안 사용자에게 데이터를 불러오는 중이라는 상태를 보여줄 수도 있습니다.

로딩 변수
const loading = ref(false)

요청을 시작할 때 true로 변경합니다.

요청 시작
loading.value = true

요청이 끝나면 false로 변경합니다.

요청 종료
loading.value = false
전체 예제
EmployeeListView.vue
<script setup>
import {
  ref,
  onMounted
} from 'vue'

import axios from 'axios'

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

async function loadEmployees() {
  loading.value = true

  try {
    const response = await axios.get(
      'http://localhost:8000/employees'
    )

    employees.value = response.data

  } catch (error) {
    console.log(error)

  } finally {
    loading.value = false
  }
}

onMounted(() => {
  loadEmployees()
})
</script>

<template>
  <h1>사원목록</h1>

  <p v-if="loading">
    데이터를 불러오는 중입니다...
  </p>

  <table
    v-else
    border="1"
  >
    <tr>
      <th>번호</th>
      <th>이름</th>
      <th>부서</th>
    </tr>

    <tr
      v-for="emp in employees"
      :key="emp.id"
    >
      <td>{{ emp.id }}</td>
      <td>{{ emp.name }}</td>
      <td>{{ emp.department }}</td>
    </tr>
  </table>
</template>
요청 중 화면
사원목록
데이터를 불러오는 중입니다...
8.17 finally

try ~ catch와 함께 finally를 사용할 수 있습니다.

기본 구조
try {
  // 서버 요청

} catch (error) {
  // 오류 처리

} finally {
  // 성공/실패와 관계없이 실행
}
로딩 처리 예
loading.value = true

try {
  const response = await axios.get(
    'http://localhost:8000/employees'
  )

  employees.value = response.data

} catch (error) {
  console.log(error)

} finally {
  loading.value = false
}
💡 finally가 적합한 이유

서버 요청이 성공하든 실패하든 마지막에는 로딩 상태를 종료해야 하므로 loading.value = false를 finally에서 처리할 수 있습니다.

프로그래밍 코드가 표시된 개발자 화면
서버 통신에서는 정상 응답뿐 아니라 로딩 상태와 오류 상황까지 함께 처리하는 것이 중요합니다.
8.18 특정 사원 조회하기

전체 사원 목록뿐 아니라 특정 번호의 사원 한 명만 조회할 수도 있습니다.

FastAPI
@app.get("/employees/{employee_id}")
def get_employee(employee_id: int):
    for employee in employees:
        if employee["id"] == employee_id:
            return employee

    return None
요청 주소
http://localhost:8000/employees/1
응답 JSON
{ "id": 1, "name": "홍길동", "department": "개발팀" }
8.19 Vue Router와 API 연결하기

7장에서 배운 URL 파라미터를 API 요청과 연결할 수 있습니다.

URL 예
/employees/1

Vue Router에서 사원번호를 읽고, 그 번호를 이용해 FastAPI에 요청합니다.

EmployeeDetailView.vue
<script setup>
import {
  ref,
  onMounted
} from 'vue'

import { useRoute } from 'vue-router'
import axios from 'axios'

const route = useRoute()
const employee = ref(null)

async function loadEmployee() {
  const id = route.params.id

  const response = await axios.get(
    'http://localhost:8000/employees/' + id
  )

  employee.value = response.data
}

onMounted(() => {
  loadEmployee()
})
</script>

<template>
  <h1>사원 상세정보</h1>

  <div v-if="employee">
    <p>
      번호 : {{ employee.id }}
    </p>

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

    <p>
      부서 : {{ employee.department }}
    </p>
  </div>
</template>
/employees/2 → Vue Router → route.params.id
→ 2 → Axios → GET /employees/2
→ FastAPI → JSON → employee.value → 상세화면 출력
8.20 CORS 문제

Vue와 FastAPI를 각각 실행하면 사용하는 주소가 서로 다릅니다.

프로그램 예시 주소
Vue http://localhost:5173
FastAPI http://localhost:8000

브라우저에서 서로 다른 주소의 서버로 요청할 때 CORS 문제가 발생할 수 있습니다.

FastAPI CORS 설정
from fastapi import FastAPI
from fastapi.middleware.cors import (
    CORSMiddleware
)

app = FastAPI()

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

개발 연습 단계에서 모든 출처를 허용하는 설정을 사용하는 경우도 있지만, 실제 운영 환경에서는 허용할 주소를 명확하게 지정하는 것이 좋습니다.

8.21 Vue와 FastAPI 실행 구조

개발 중에는 Vue와 FastAPI를 각각 실행하는 경우가 많습니다.

첫 번째 터미널 — FastAPI
Terminal 1
uvicorn main:app --reload
FastAPI 주소
http://localhost:8000
두 번째 터미널 — Vue
Terminal 2
npm run dev
Vue 주소
http://localhost:5173
Browser

Vue : localhost:5173
↓ Axios
FastAPI : localhost:8000

Database
8.22 API 주소를 변수로 관리하기

같은 API 기본 주소를 여러 파일에서 반복해서 작성하면 나중에 주소가 변경될 때 수정하기 번거로울 수 있습니다.

기본 주소 변수
const API_URL =
  'http://localhost:8000'
사용 예
const response = await axios.get(
  API_URL + '/employees'
)
8.23 Axios 인스턴스 만들기

프로그램이 커지면 Axios의 기본 설정을 별도 파일로 분리하면 편리합니다.

src │ ├─ api │ └─ axios.js │ ├─ views ├─ components ├─ App.vue └─ main.js
src/api/axios.js
src/api/axios.js
import axios from 'axios'

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

export default api

다른 Vue 파일에서는 다음처럼 불러옵니다.

import
import api from '../api/axios'

API 요청은 짧게 작성할 수 있습니다.

사용 예
const response = await api.get(
  '/employees'
)
💡 장점

매번 http://localhost:8000 전체 주소를 반복해서 작성할 필요가 없습니다.

8.24 API 파일 분리하기

사원과 관련된 API 요청 자체도 별도의 파일로 분리할 수 있습니다.

src │ ├─ api │ ├─ axios.js │ └─ employeeApi.js │ ├─ views └─ components
employeeApi.js
src/api/employeeApi.js
import api from './axios'

export function getEmployees() {
  return api.get('/employees')
}

export function getEmployee(id) {
  return api.get(
    '/employees/' + id
  )
}

화면에서는 필요한 함수만 불러옵니다.

View 파일
import {
  getEmployees
} from '../api/employeeApi'
사용
const response =
  await getEmployees()

employees.value =
  response.data
💡 코드 역할 분리

이렇게 구성하면 화면 코드서버 통신 코드를 분리해서 관리할 수 있습니다.

8.25 실습 — 사원목록 완성
🧪 이번 실습에서 함께 사용하는 기능
Axios 인스턴스, onMounted(), 로딩 상태, 오류 메시지, Vue Router 상세 링크를 하나의 사원목록 화면에 적용합니다.
EmployeeListView.vue
<script setup>
import {
  ref,
  onMounted
} from 'vue'

import api from '../api/axios'

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

async function loadEmployees() {
  loading.value = true
  errorMessage.value = ''

  try {
    const response = await api.get(
      '/employees'
    )

    employees.value = response.data

  } catch (error) {
    console.log(error)

    errorMessage.value =
      '사원정보를 불러오지 못했습니다.'

  } finally {
    loading.value = false
  }
}

onMounted(() => {
  loadEmployees()
})
</script>

<template>
  <div>
    <h1>사원목록</h1>

    <button @click="loadEmployees">
      새로고침
    </button>

    <p v-if="loading">
      데이터를 불러오는 중입니다...
    </p>

    <p v-if="errorMessage">
      {{ errorMessage }}
    </p>

    <table
      v-if="!loading"
      border="1"
    >
      <thead>
        <tr>
          <th>번호</th>
          <th>이름</th>
          <th>부서</th>
        </tr>
      </thead>

      <tbody>
        <tr
          v-for="emp in employees"
          :key="emp.id"
        >
          <td>
            {{ emp.id }}
          </td>

          <td>
            <router-link
              :to="{
                name: 'employeeDetail',
                params: {
                  id: emp.id
                }
              }"
            >
              {{ emp.name }}
            </router-link>
          </td>

          <td>
            {{ emp.department }}
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
화면 열기 → onMounted()
→ API 요청 → loading = true
→ 성공 : employees 저장
→ 실패 : errorMessage 저장
→ finally : loading = false
→ 화면 자동 갱신
8.26 지금까지의 데이터 방식 비교
초기 연습 방식
Vue 내부 데이터
const employees = ref([
  { id: 1, name: '홍길동' }
])

연습할 때는 편리하지만 실제 서버나 데이터베이스의 데이터는 아닙니다.

REST API 방식
초기값
const employees = ref([])
FastAPI 데이터 가져오기
const response =
  await api.get('/employees')

employees.value =
  response.data
구분 데이터 구조
초기 연습 Vue → 내부 데이터
서버 연동 Vue → REST API → FastAPI → Database
8.27 Python Tkinter 프로그램과 비교

기존 Python 데스크톱 프로그램에서는 Python 코드가 데이터베이스에 직접 접근하는 구조를 사용할 수 있습니다.

Tkinter → Python → SQLite
Python 예
cursor.execute(
    "SELECT * FROM employee"
)

하지만 이 교재에서 다루는 웹 프로그램 구조에서는 브라우저에서 실행되는 Vue가 데이터베이스에 직접 접근하지 않습니다.

Vue → FastAPI → Database

Vue는 FastAPI에 데이터를 요청합니다.

Vue
await api.get('/employees')

FastAPI가 데이터베이스를 조회합니다.

Database 조회 개념
SELECT * FROM employee

FastAPI가 조회 결과를 JSON으로 만들어 Vue에 전달하고, Vue는 받은 데이터를 화면에 출력합니다.

💡 Python 데스크톱 → 웹 프로그램

기존 Python 프로그램의 구조와 가장 크게 달라지는 부분 중 하나는 프론트엔드와 데이터베이스 사이에 서버 API가 존재한다는 점입니다.

📌 Chapter 8 핵심 정리
  • 실제 웹 프로그램에서는 데이터를 Vue 파일에 직접 저장하기보다 서버와 데이터베이스에서 관리할 수 있습니다.
  • 이번 장의 기본 구조는 Vue → REST API → FastAPI → Database입니다.
  • Vue와 FastAPI 사이에서는 주로 JSON 형식으로 데이터를 주고받습니다.
  • REST API에서는 GET, POST, PUT, DELETE 같은 HTTP 방식을 사용할 수 있으며, 이번 장에서는 주로 GET 조회를 학습했습니다.
  • Vue에서 HTTP 요청을 편리하게 처리하기 위해 Axios를 사용할 수 있습니다.
  • Axios 응답에서 서버가 전달한 실제 데이터는 response.data에 있습니다.
  • 서버 응답을 기다리는 비동기 처리에는 async와 await를 사용할 수 있습니다.
  • 서버에서 받은 데이터를 employees.value 등에 저장하면 Vue가 화면을 자동으로 갱신합니다.
  • onMounted()를 이용하면 화면이 만들어진 후 서버 데이터를 자동으로 불러올 수 있습니다.
  • 서버 통신에는 try ~ catch를 이용한 오류 처리를 적용할 수 있습니다.
  • 요청 중인 상태를 표시하려면 loading과 v-if를 사용할 수 있습니다.
  • finally는 요청 성공과 실패에 관계없이 마지막에 실행할 코드를 작성할 때 사용할 수 있습니다.
  • Vue Router의 URL 파라미터와 Axios를 연결하면 특정 사원의 상세정보를 서버에서 조회할 수 있습니다.
  • Vue와 FastAPI의 출처가 다르면 CORS 설정이 필요할 수 있습니다.
  • Axios의 baseURL을 별도 파일로 관리하면 API 주소를 반복해서 작성하지 않아도 됩니다.
  • API 요청 함수까지 별도 파일로 나누면 화면 코드와 서버 통신 코드를 분리해 관리할 수 있습니다.
  • 가장 중요한 전체 흐름은 Vue 요청 → Axios → FastAPI → JSON 응답 → Vue 데이터 변경 → 화면 자동 갱신입니다.

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

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

상담 신청하기