개발 환경을 구성하기 위해 프로그램 설치
Project Scaffolding(프로젝트 스캐폴딩)
프로젝트를 시작하기 위한 구조와 설정 등을 자동으로 처리해주는 툴입니다. → 애플리케이션의 구조 생성
개발자가 수동으로 설치하고 설정해야 하는 것들을 프로그램이 대신 처리해줌으로써 개발을 빠르게 시작할 수 있도록 도와줍니다.
Vue CLI(Command line interface)
Vue.js 개발을 위한 표준 도구 (빠른 Vue.js 애플리케이션 개발을 위한 시스템을 제공) (https://cli.vuejs.org/)

IDE(Integrated Development Environment, 통합 개발 환경)
VS Code + Vue-Official(extension)
문법 강조 표시, TypeScript 지원, 템플릿 표현식 및 컴포넌트 props에 대한 인텔리센스 제공

Browser Devtools
Vue.js devtools
Vue로 만든 웹의 구조를 파악하거나 데이터 파악하고 디버깅을 할 수 있게 제공

Vue CLI로 Vue 프로젝트 생성
1. Vue CLI 설치 (콘솔에서 실행합니다.)
npm install -g @vue/cli

vue --version

2. Vue CLI로 Vue 프로젝트 생성 (콘솔에서 실행합니다.)
vue create lunchapp


vue 프로젝트를 어떻게 구성할 건지 preset(프리셋 : 사전 설정)합니다. – Default (Vue 3) 선택
babel (바벨, https://babeljs.io) : javascript를 이전 버전과 호환되게 코드를 변환해주는 컴파일러 → 트랜스파일러
eslint (린트/린터, https://eslint.org) : javascript 코드에서 발견되는 문제 패턴들을 식별해주는 정적 분석 도구, 코드 규칙을 이용하 코드 스타일 유지

3. Vue 프로젝트 실행 (콘솔에서 실행합니다.)
cd lunchapp; npm run serve

4. 웹 브라우저에서 확인 (http://localhost:8080/)

5. 웹 경로와 Vue 컴포넌트를 연결하기 위해 Vue Router 설치
Vue Router –웹 경로와 Vue 컴포넌트를 연결해 줍니다.

Vue Router Plugin으로 vue-router를 설치하겠습니다.
vue에서는 vue router에 대한 일련의 작업들을 자동으로 처리해 Plugin을 제공합니다.
Vue Router Plugin 설치 (콘솔에서 실행합니다.)
vue add router

설치가 진행되는 중에 "Use history mode for router? (Requires proper server setup for index fallback in production) - 라우터에 히스토리 모드를 사용 하시겠습니까? (프로덕션에서 인덱스 폴백을 위한 적절한 서버 설정 필요합니다.)"라는 컨펌 메시지가 나타납니다. 라우터를 히스토리 모드로 사용하기 위해 "Y"을 입력하고 엔터키를 누릅니다.
Vue Router 디렉터리 구조


6. index.js에 Router(라우터) 인스턴스를 생성
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
라우터 인스턴스 생성
(https://router.vuejs.kr/guide/#Registering-the-router-plugin)

7. App.vue에 Router(라우터) 인스턴스를 use() 함수로 등록
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
use() 함수는 mount() 함수 이전에 실행되어야 합니다.
라우터 인스턴스 등록
(https://router.vuejs.kr/guide/#Registering-the-router-plugin)

8. 웹 브라우저에서 확인 (http://localhost:8080/)

9. API 서버와 연동하기 위해 Vue Axios(액시오스) 설치
npm install axios --save

Options API vs Composition API

Vue 2 지원은 2023년 12월 31일에 종료
API 스타일에 대한 설명을 참고하세요. (https://ko.vuejs.org/guide/introduction.html#api-styles)
LunchMenuListView 컴포넌트 생성
1. src\views에서 LunchMenu 디렉터리 생성, LunchMenuListView.vue 파일을 생성합니다.

<template>
<div>점심 메뉴 리스트</div>
</template>
<script>
</script>
<style>
</style>
2. src\router\index.js에서 LunchMenuListView.vue를 import하고 routes에 route를 추가합니다.

:
import LunchMenuListView from '@/views/LunchMenu/LunchMenuListView.vue'
const routes = [
:
{
path: '/lunchmenu/',
name: 'lunchmenu',
component: LunchMenuListView
}
]
:
@는 단축 경로로 "src/*" 경로를 단축한 겁니다.
vue.config.js 파일에 정의되어 있습니다.
"paths": {
"@/*": ["src/*"]
},
3. src\App.vue에서 router-link를 추가합니다.

Router 선언적 방식 (v3.x)
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/lunchmenu/">LunchMenu</router-link>
</nav>
<router-view/>
</template>
링크를 생성하기 위해서 <a>태그가 아닌 router-link 컴포넌트를 사용합니다. → router-link 컴포넌트는 <a>태그로 렌더링 됩니다. (<router-link to=""> → <a href="">)
router-view 컴포넌트는 현재 컴포넌트에서 렌더링할 위치입니다. → 현재 경로에 맞는 컴포넌트가 렌더링 됩니다.
(v4.x)
<template>
<nav>
<RouterLink to="/">Home</RouterLink> |
<RouterLink to="/about">About</RouterLink> |
<RouterLink to="/lunchmenu/">LunchMenu</RouterLink>
</nav>
<RouterView/>
</template>
4. 웹 브라우저에서 확인 (http://localhost:8080/lunchmenu)

LunchMenuListView 컴포넌트 개발
1. setup()에 점심 메뉴들이 리스트로 저장될 lunchMenuList를 선언하고 값이 반응형 상태가 되도록 ref(레프, reference) 함수를 사용합니다. 그리고 <template>에서 사용할 수 있게 리턴합니다.
<template>
<div>점심 메뉴 리스트</div>
</template>
<script>
import { ref } from 'vue';
export default {
name : 'LunchMenuListView',
setup() {
let lunchMenuList = ref([]);
return {
lunchMenuList
}
}
}
</script>
let lunchMenuList = []; ← 값이 변동되므로 let로 변수 선언
let lunchMenuList = ref([]); ← 값에 ref() 함수를 적용하여 반응형 상태가 되도록 함
Composition API에서는 반응형 상태를 선언하는 ref() 함수입니다. → 반응성
(https://ko.vuejs.org/guide/essentials/reactivity-fundamentals#ref)

2. <template>에서 lunchMenuList가 있는지 없는지에 따라 블록을 렌더링 합니다.
<template>
<div>점심 메뉴 리스트</div>
<div v-if="lunchMenuList.length == 0">
점심 메뉴가 없습니다.
</div>
<div v-else>
점심 메뉴가 {{ lunchMenuList.length }} 개 있습니다.
</div>
</template>
Directive(디렉티브) : 지시문으로 <template>에서 v- 접두사가 붙어 사용되는 속성입니다.
v-if는 조건이 true일 때 블록을 렌더링 합니다. → 조건부 렌더링
v-else는 v-if에 대한 else 블록입니다.
블록은 태그입니다. 시작 태그와 종료 태그가 한 블록입니다.
{{ }}(Mustache, 머스테치, 이중 괄호)는 데이터를 DOM에 바인딩합니다. → 보간(Interpolation)법(중간에 무언가를 끼워 넣다)
조건부로 블록을 렌더링합니다.
(https://ko.vuejs.org/guide/essentials/conditional.html)

텍스트 보간법
(https://ko.vuejs.org/guide/essentials/template-syntax#text-interpolation)

3. axios의 get() 함수를 사용하여 APIServer에서 점심 메뉴 리스트를 받아서 lunchMenuList에 적용하는 getLunchMenuList() 함수를 생성하고 컴포넌트에서 사용할 수 있게 리턴합니다.
<script>
import { ref } from 'vue';
import axios from 'axios';
export default {
name : 'LunchMenuListView',
setup() {
let lunchMenuList = ref([]);
function getLunchMenuList() {
axios.get("http://127.0.0.1:9000/api/v1/lunchmenus/").then((res) => {
lunchMenuList.value = res.data.data;
}).catch((err) => {
console.log(err);
});
}
return {
lunchMenuList, getLunchMenuList
}
}
}
</script>
setup()안에서 ref() 함수로 작용된 값에 접근할 때는 value로 속성을 사용해야 합니다.
Ref()는 반응형 ref 객체를 반환합니다. Ref 객체 안에는 단일 속성으로 value가 있습니다. value 속성에 값이 있습니다.
axios의 get() 함수는 HTTP GET 메서드로 요청합니다. ← 비동기
then() 메서드는 요청(HTTP Request)에 대한 응답(HTTP Response)이 성공하면 들어오는 메서드로 callback function를 호출합니다. 파라미터는 응답 객체입니다. "response"이나 "res"로 사용합니다.
catch() 메서드는 요청(HTTP Request)에 대한 응답(HTTP Response)이 실패하면 들어오는 메서드로 callback function를 호출합니다. 파라미터는 에러 객체입니다. "error"이나 "err"로 사용합니다.
then() 메서드와 catch() 메서드에서 function를 직접 사용해서 처리하면 됩니다.
then(function(res) {
})
또는 화살표 함수(arrow function)를 사용할 수 있습니다.
then((res) => {
})
비동기 처리
function getLunchMenuList() {
axios.get("http://127.0.0.1:9000/api/v1/lunchmenus/").then((res) => {
lunchMenuList.value = res.data.data;
}).catch((err) => {
console.log(err);
});
}
동기 처리
async function getLunchMenuList() {
try {
const result = await axios.get("http://127.0.0.1:9000/api/v1/lunchmenus/");
lunchMenuList.value = result.data.data;
} catch(err) {
console.log(err);
}
}
axios.get("http://127.0.0.1:9000/api/v1/lunchmenus/")의 결과 값
{
"data": {
"result": "success",
"code": 200,
"message": "데이터가 조회되었습니다.",
"data": [
{
"seq": 3,
"menu_name": "볶음밥",
"reg_dtm": "2025-01-17 13:37:26"
},
{
"seq": 2,
"menu_name": "짬봉",
"reg_dtm": "2025-01-17 13:37:14"
},
{
"seq": 1,
"menu_name": "자장면",
"reg_dtm": "2025-01-17 13:37:00"
}
]
},
"status": 200,
"statusText": "OK",
"headers": {
"content-length": "283",
"content-type": "application/json; charset=utf-8"
},
"config": {
"transitional": {
"transitional": {
"silentJSONParsing": true,
"forcedJSONParsing": true,
"clarifyTimeoutError": false
},
"adapter": [
"xhr",
"http",
"fetch"
],
"transformRequest": [
null
],
"transformResponse": [
null
],
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1,
"env": {},
"headers": {
"Accept": "application/json, text/plain, */*"
},
"method": "get",
"url": "http://127.0.0.1:9000/api/v1/lunchmenus/"
},
"request": {}
}
4. 컴포넌트가 로딩이 완료된 후 점심 메뉴 리스트를 가져오는 함수를 호출하기 위해 <script>에 mounted() 함수를 추가하고 setup에 있는 getLunchMenuList() 함수가 실행되게 합니다.
<script>
import { ref } from 'vue';
import axios from 'axios';
export default {
setup() {
:
},
mounted() {
this.getLunchMenuList();
}
}
</script>

컴포넌트가 마운트 되면 mounted() 훅이 실행됩니다. → 생명주기 훅
setup()에서 리턴 되는 변수는 this(컴포넌트 인스턴스)에 노출됩니다:
생명주기 훅 등록 – mounted 등록
(https://ko.vuejs.org/guide/essentials/lifecycle#registering-lifecycle-hooks)

Composition API (컴포지션 API)에서 setup() 함수를 <script setup>으로 단순화 할 수 있습니다.
<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';
let lunchMenuList = ref([]);
function getLunchMenuList() {
axios.get("http://127.0.0.1:9000/api/v1/lunchmenus/").then((res) => {
console.log(res);
lunchMenuList.value = res.data.data;
}).catch((err) => {
console.log(err);
});
}
onMounted(()=>{
getLunchMenuList();
});
</script>
setup() 밖에서 사용된 mounted를 setup()에서 사용할 수 있습니다. → onMounted()
(https://ko.vuejs.org/api/composition-api-lifecycle#onmounted)
Options API (옵션 API)로 작성할 수 있습니다.
<script>
import axios from 'axios';
export default {
name : 'LunchMenuListView',
data() {
return {
lunchMenuList : []
}
},
methods : {
async getLunchMenuList() {
try {
const result = await axios.get("http://127.0.0.1:9000/api/v1/lunchmenus/");
console.log(result);
this.lunchMenuList = result.data.data;
} catch(err) {
console.log(err);
}
}
},
mounted() {
this.getLunchMenuList();
}
}
</script>
setup()에서 사용한 변수와 함수는 Options API에서는 data와 methods로 분리됩니다.
5. lunchMenuList 수 만큼 반복하여 <li>가 생성되게 합니다.
<template>
<div>점심 메뉴 리스트</div>
<div v-if="lunchMenuList.length == 0">
점심 메뉴가 없습니다.
</div>
<div v-else>
점심 메뉴가 {{ lunchMenuList.length }} 개 있습니다.
<ul class="menu-list">
<li class="menu-item" v-for="(lunchMenuItem, index) in lunchMenuList" :key="index">
{{ lunchMenuItem.menu_name }}
</li>
</ul>
</div>
</template>
v-for는 배열을 기반으로 블록을 렌더링 합니다. → 리스트 렌더링
Vue가 각 노드의 ID를 추적하고 기존 엘리먼트를 재사용하고 재정렬할 수 있도록 힌트를 제공하려면 각 항목에 대해 특수 속성 key를 고유한 값으로 제공해야 합니다.
:key는 v-bind:key를 단축한 겁니다. key는 속성입니다. v-bind는 속성 값에 변수를 바인딩해줍니다. 속성 바인딩 (속성에서는 이중 괄호를 사용할 수 없습니다.)
배열을 리스트로 렌더링합니다.
(https://ko.vuejs.org/guide/essentials/list)

6. 스타일을 적용하기 위하여 <style>에 CSS를 추가합니다.
<style>
.menu-list {
width: 500px;
margin: 10px auto;
padding: 0;
}
.menu-item {
list-style: none;
padding: 10px 10px;
background-color: #0a4171;
color: white;
margin: 4px 0px;
cursor: pointer;
}
</style>
7. 웹 브라우저에서 확인 (http://localhost:8080/lunchmenu)

'Vue.js 3 & NodeJS > 사내교육 - 점심 메뉴' 카테고리의 다른 글
| [6] Frontend 개발 - 등록 뷰 (AddView) 생성 (0) | 2026.08.03 |
|---|---|
| [5] Backend 개발 - API Server 개발, Node.JS + Search (0) | 2026.08.03 |
| [4] Backend 개발 - API Server 개발, Node.JS + Paging (0) | 2026.08.03 |
| [3] Backend 개발 - API Server 개발, Node.JS + MyBatis3 (0) | 2026.08.03 |
| [1] Backend 개발 - API Server 개발 (0) | 2026.08.02 |