-
[To-Do 앱]Vue.js/Node.js 앱 에서 API Call 하기 (Axios)웹 어플리케이션 2019. 2. 26. 17:16
이전 포스트에서 Vue.js와 Node.js를 이용해 프론트엔드 웹 서버의 뼈대를 구현 해 보았다. 이번 포스트에서는 진짜로 To Do 리스트를 만들기 위해 Vue.js/Node.js 프론트엔드 서버에서 Spring Boot RESTful API서버로 API콜을 해 보도록 하겠다. 들어가기에 앞서 vue.js는 우리가 추가한 eslint패키지 때문에 들여쓰기(indention)에 매우 민감하다. 따라서 들여쓰기에 주의를 하거나 package.json에서 "lint": "eslint --ext .js,.vue src" 이 부분을 지우고 다시 실행시켜야 할 것이다. 일단 실습을 위해 프로젝트를 실행시켜라. npm run dev
예상독자
- IntelliJ, Webstorm, Atom 등 자바스크립트 IDE중 하나를 설치했다.
- 자바스크립트를 좀 안다.
- 백엔드는 알아서 구현할 수 있거나 아래의 튜토리얼들을 마쳤다.
- [To-Do 앱]스프링부트(SpringBoot) 웹 어플리케이션)
- [To-Do 앱] 스프링 부트(Spring Boot) RESTful API - GET
- [To-Do 앱] 스프링 부트(Spring Boot) RESTful API - POST
- vue.js와 node.js를 이용해 웹 앱 만들기
- 도커에 스프링 앱을 올리고 싶다면: 스프링 부트 도커에 올리기 (Dockerizing Spring Boot App) 참고
목표
- 프론트엔드 프로젝트구조
- Vue.js 컴포넌트 (Component)
- Vue.js 라우터 (Router)
- Vue.js App.vue
- Axios을 이용해 API 콜 하기
- Cross Origin Resource Sharing
프론트엔드 프로젝트 구조
to-do-frontend 프로젝트 내부의 src 디렉토리의 구조를 살펴보자.중요한 몇 개의 파일과 디렉토리를 확인 해 보자. src의 디렉토리 아래에는 3개의 디렉토리 assets, components, router가 있다.
- assets: .css나 .png같은 UI관련 리소스들이 들어간다.
- components: 여기에 바로 화면 내부에 들어갈 html 템플릿과 vue.js 함수들이 들어간다.
- router: vue.js의 라우팅 정보가 들어간다.
- main.js: 이 Vue앱을 초기화 시키는 자바스크립트이다.
- App.vue: 이 앱의 기본(디폴트) 템플릿이라고 생각하면 된다.
- index.js: vue-router를 이용해 경로를 설정하는 자바스크립트이다.
- Hello.vue: 우리가 ToDoItem관련 로직을 넣을 컴포넌트이다.
Vue.js 컴포넌트 (Component)
컴포넌트는 Vue.js에서 재사용 할 수 있는 단위이다. 이 튜토리얼에서는 HTML 페이지 하나를 컴포넌트 하나라고 생각하면 쉽다. Hello.vue를 다음과 같이 수정 해 보자.<template>
<div class="hello">
오늘 해야 할 일
{{toDoItems}}
</div>
</template>
<script>
export default {
name: 'hello',
data: () => {
return {
toDoItems: ['1. 밥 먹기.', '2. 잠 자기.']
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #35495E;
}
</style>이렇게 하면, http://localhost:8080/이 아래처럼 변경 될 것이다.헤더의 To Do List는 아래서 변경 할 테니 신경쓰지 마라. 아무튼 위의 캡쳐 화면을 보면 오늘 해야 할 일 그리고 옆에 밥먹기, 잠자기 리스트가 나왔다. 이 부분의 코드를 다시 한번 살펴보자.
<template>
<div class="hello">
오늘 해야 할 일
{{toDoItems}}
</div>
</template>
<script>
export default {
name: 'hello',
data: () => {
return {
toDoItems: ['1. 밥 먹기.', '2. 잠 자기.']
}
}
}
</script><template></template> 이 사이에는 이 컴포넌트가 사용할 HTML의 템플릿이 들어간다. 코드와 UI사이의 맵핑을 확인 해 보자.
1과 2를 자세히 보자. 우리가 script안에서 export default하는 부분에 data를 정의했다. 이 데이터가 바로 template내부에서 쓰일 데이터이다. 처음 data를 정의 할 때 함수의 형태로 초기값을 오브젝트의 형태({..})로 리턴 해 줘야 한다. 그래서 함수 () => {}를 써 주고 그 안에 return값을 toDoItems가 있는 오브젝트를 리턴했다. 이렇게 리턴하면 template안에서 {{toDoItems}}처럼 두개의 꺽쇠괄호 안에 변수를 집어넣어 사용 가능하다. 지금 이 예제에서는 투두 리스트를 만들 것이므로 리스트를 넣어보았다.
Vue.js 라우터 (Router)
이제 라우터에 대해 알아보자. 라우터는 어떤 역할을 할까? 바로 경로를 설정 해 주는 역할을 한다. 브라우저에서 보면 /products, /contacts같이 슬래시를 사용해 경로를 지정 해 줄 수 있다. 우리의 앱에서는 vue-router라는 패키지를 이용해 손 쉽게 라우팅을 할 수 있다. 이 작업은 스프링부트와 비교하자면 @ReqestMapping(value ="/todo")를 해 줬던 작업과 비슷하다. 스프링에서는 해당 경로로 리퀘스트가 들어오면 어떤 함수를 실행시켰다. 마찬가지이다. 이 라우터도 해당 경로로 누군가 접근하면 그 경로에 필요한 컴포넌트를 이어 줄 것이다. 아래를 보자.router/index.jsimport Vue from 'vue' # 1. Vue를 사용 할 수 있도록 import한다.
import Router from 'vue-router' # 2. Router를 사용 할 수 있도록 import한다.
import Hello from '@/components/Hello' # 3. 경로 설정을 원하는 컴포넌트를 import 한다.
Vue.use(Router) # 4. Vue에게 Router를 사용하라고 알려준다.
# 5. 실제 경로 설정
export default new Router({
routes: [
{
path: '/', # http://localhost:8080/으로 누가 들어오면
name: 'Hello', # 이 경로에 Hello라는 이름을 붙이고
component: Hello # Hello.vue 컴포넌트를 이어줘라... 어디에? <router-view></router-view>에
}
]
})그렇자면 <router-view></router-view>는 과연 어디 있는가? 다음 섹션으로 넘어가 App.vue를 확인 해 보자.Vue.js App.vue
App.vue<template>
<div id="app">
<header>
<span>To Do List</span>
</header>
<main>
<router-view></router-view>
</main>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>그림을 통해 App.vue의 구조를 보자.App.vue의 헤더에 To Do List라고 쓰여있으므로 이를 통해 이 부분이 브라우저상의 To Do List와 맵핑된다는 것을 이해 할 수 있다. 그리고 아래 부분인 하얀 부분이 <router-view></router-view>로 매핑 된 것을 확인 할 수 있다. 바로 이 부분이 우리가 route/index.js에 명시한 경로에 맞는 컴포넌트가 출력되는 부분이다.
다시말해 vue.js가 <router-view></router-view>부분에 어떤 컴포넌트를 보여줄 지 결정하기 위해,
1. 현재 요청받은 브라우저의 경로를 본다.
2. 그 경로를 router/index.js의 라우터에서 찾는다 (path : '/' 부분)
3. 해당 경로의 컴포넌트를 (Hello)를 리턴한다.
4. 해당 컴포넌트의 스크립트를 실행하고 템플릿을 <router-view></router-view>에 넣는다.
이런 과정을 거쳐 Hello.vue가 <router-view></router-view>부분에 보여지는 것이다.
이를 확인하기 위해 새 경로인 /about을 만들고 컴포넌트인 About.vue를 components 디렉토리 아래에 만들어 이 둘을 이어보자.
About.vue
<!-- to-do-frontend/src/components/About.vue -->
<template>
<div class="about">
{{msg}}
</div>
</template>
<script>
export default {
name: 'about',
data: () => {
return {
msg: '내가 개발한 To Do 어플입니다.'
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #35495E;
}
</style>데이터에는 간단하게 msg로 '내가 개발한 To Do 어플입니다.'를 할당 해 주었다. 이제 router/index.js에 가서 경로를 추가 해 주자.router/index.jsimport Vue from 'vue'
import Router from 'vue-router'
import Hello from '@/components/Hello'
import About from '@/components/About' # component를 import한다 .vue는 안 써줘도 됨.
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Hello',
component: Hello
},
{
path: '/about', # http://localhost:8080/about 경로에 맵핑 해 준다.
name: 'About',
component: About # About.vue를 맵핑 해 준다.
}
]
})이렇게 하고 저장 후 http://localhost:8080/#/about로 들어가 보자위처럼 화면이 바뀌고 About.vue에서 추가 했던 메시지가 보이는 것을 확인 할 수 있다.
Axios을 이용해 API 콜 하기
라우터의 기능까지 확인 했으니 이제 Axios를 이용해 스프링 부트에 API콜을 해 보자.
Axios 설치
이를 위해 일단 Axios 패키지를 설치해야 한다. 실행중인 to-do-frontend앱을 종료하고 터미널을 이용해 to-do-frontend앱의 프로젝트 디렉토리로 들어간다.➜ npm install axios
+ axios@0.18.0
added 1 package from 1 contributor and audited 9253 packages in 14.731s
found 1 low severity vulnerability위처럼 npm install axios를 실행하면 npm이 알아서 axios를 to-do-frontend앱에 추가 할 것이다. 이제 다시 to-do-frontend앱을 실행 해 보자.➜ npm run dev
그리고 스프링 부트 앱도 실행 해야 한다. 잠깐, 현재 to-do-frontend앱은 8080에서 실행되고 있다. 만약 스프링 부트 앱을 그냥 실행시킨다면 8080 포트에 실행 시킬 것 이므로 port address in use라는 바인드 에러가 난다. 즉 8080포트는 이미 다른 어플리케이션이 사용중이라는 뜻이다. 따라서 우리는 스프링 부트 앱의 포트를 변경 해야 한다.src/main/resources/application.properties 에 다음과 같은 엔트리를 추가 해 주자.server.port = 5000
이제 스프링 부트 앱을 실행 시켜보자(gradle bootRun). 5000포트에서 실행 될 것이다. Docker에서 실행하는 사람들은 5000:8080으로 포트 맵핑을 해 주면 된다.Hello.vue에서 GET API Call하기
이제 다시 to-do-frontend앱으로 돌아와 Hello.vue를 다음과 같이 수정 해 보자.<template>
<div class="hello">
오늘 해야 할 일
<ul v-if="toDoItems && toDoItems.length">
<li v-for="toDoItem of toDoItems">
{{toDoItem.title}}
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios' // 아까 받은 axios 패키지를 사용하기 위해 import한다
export default {
name: 'hello',
data: () => {
return {
toDoItems: [] // toDoItems를 빈 리스트로 초기화한다.
}
},
created () { // 초기화 함수를 정의 한다.
axios.get('http://127.0.0.1:5000/todo/') // http://localhost:5000/todo/에 get call을 한다.
.then(response => {
this.toDoItems = response.data.map(r => r.data) // 반환되는 값을 toDoItems에 저장한다.})
.catch(e => {
console.log('error : ', e) // 에러가 나는 경우 콘솔에 에러를 출력한다
})
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #35495E;
}
</style>이제 template 내부를 잠깐 설명하겠다.<ul v-if="toDoItems && toDoItems.length">
<li v-for="toDoItem of toDoItems">
{{toDoItem.title}}
</li>
</ul>v-if는 vue.js에서 제공하는 html attribute이다. 이렇게 v-if="조건" 을 적어주면 조건에 따라 해당 블록을 실행하거나 실행하지 않는다. 여기서는 toDoItems && toDoItems.length 즉 toDoItems 오브젝트가 존재하거나, toDoItems.length가 존재하는 경우 아래의 <li ..> </li>를 출력한다.v-for은 뭘까? 감이 오지 않는가? 바로 for 문이다. 많은 자바스크립트 프레임워크의 장점 중 하나가 정적 문서인 HTML내에서 마치 HTML이 프로그래밍 언어 인 것처럼 사용 할 수 있게 해준다는 것이다. 위의 블락은 자바에서 아래랑 비슷한 느낌이다.for(ToDoItem todoItem : toDoItems) {
System.out.println(toDoItem.getTitle());
}아무튼 이렇게 하면 우리의 데이터가 출력되어야 한다. 한번 새로고침을 해 보자. 실행이 되었는가? 아니다. 에러가 났을 것이다. 에러가 났는지 어떻게 확인하는가? F12를 눌러 Developer Tools에 들어가 보자.아래와 같은 요상한 에러가 난 것을 확인 할 수 있다.
Cross Origin Resource Sharing
Access to XMLHttpRequest at 'http://127.0.0.1:5000/todo/' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
이는 CORS(Cross Origin Resource Sharing)이라고 보안을 위해 만들어진 정책이다. 무슨 뜻이냐 하면 스프링 부트 어플리케이션이 도메인이 다른 어플리케이션으로부터의 요청을 거부했다는 뜻이다. 즉 localhost:8080 -> 127.0.0.1:5000/todo/는 도메인(포트)가 다르기 때문에 보안상 이를 허락하지 않는다는 뜻이다. 그러면 어떻게 해야 할까? 다 방법이 있다.
스프링 부트로 돌아가 Cross Origin을 위한 Configuration을 추가 해 줘야 한다. Config패키지 안에 WebConfig.java를 만들어보자. 그리고 아래처럼 설정 해 보자.package com.fsoftwareengineer.MySpringApp.Config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://127.0.0.1:8080")
.allowedOrigins("http://localhost:8080");
}
}간단히 말해 127.0.0.1:8080이나 localhost:8080에서 들어오는 리퀘스트는 허락하라는 뜻이다.이렇게 저장 하고 스프링 부트를 다시 실행 해 보자.gradle clean build; gradle bootRun;
이제는 아래처럼 에러가 나지 않아야 한다.이제 포스트맨이나 cURL을 이용해 To Do Item을 몇 개 추가 해 보도록 하자. 포스트맨을 이용하는 법은 이 포스트(포스트맨(Postman)을 이용한 API 테스트)에서 확인 하도록 한다. 또는 아래의 화면을 참고하되 포트를 5000으로 바꾸는 것을 잊지 말자.
CURL 코드는 다음과 같다.curl -X POST \
http://localhost:5000/todo \
-H 'Content-Type: application/json' \
-H 'Postman-Token: f0b5caf1-d362-480c-adb2-9f3cadc9cc12' \
-H 'cache-control: no-cache' \
-d '{
"title" : "To Do 리스트에 CSS추가하기"
}'이렇게 하고 다시 http://localhost:8080에서 새로고침을 해 보자. 아래와 같은 화면이 나오면 성공 한 것이다.
끝
여기까지 해서 Axios를 이용해 이전에 만들었던 RESTful API에 GET메서드 콜을 하는 방법까지 살펴보았다. 지금까지 따라왔으면 느끼겠지만 만만치 않다. 웹 앱 하나를 만드는 것은 이렇게 많은 시간과, 디버깅, 노력이 필요 한 것이다. 그래도 여기까지 했으면 거의 다 한 것이다! 이 포스트에서 포트 바인딩이나 CORS에 대해 이야기 했던 것을 기억하면 좋겠다. 웹 앱을 개발하다보면 한번씩 건너야 하는 산이다. 다음 포스트에서는 boostrap-vue를 이용해 UI를 구현하고, UI상에서 POST를 이용해 To Do 아이템을 생성하는 실습을 해 보도록 하겠다.
다음 포스트 :
'웹 어플리케이션' 카테고리의 다른 글
[To-Do 앱]Vue.js/Node.js To Do Item 추가 기능 만들기 (1) 2019.02.28 [To-Do 앱]Vue.js/Node.js Bootstrap-vue를 이용한 UI 구현 (1) 2019.02.27 스프링 부트 도커에 올리기(Dockerizing Spring Boot App) (3) 2019.02.25 [To-Do 앱]Vue.js 와 Node.js를 이용해 웹 앱 만들기 (4) 2019.02.25 [To-Do 앱] 스프링 부트(Spring Boot) RESTful API - POST (9) 2019.02.24 댓글