[Vue&Spring] Vue와 SpringBoot REST API 연동

young-gue Park·2023년 11월 9일
0

Vue.js

목록 보기
10/10
post-thumbnail

⚡ Vue와 SpringBoot REST API 연동


📌 Spring Boot REST API

🔷 이전에 만든 Spring Boot 게시판을 조금 바꿔서 사용한다. 수정된 게시판은 SSAFY에서 제공해준 코드이므로 이곳에는 올리지 않는다.

velog Spring Boot 게시물

SpringBoot 게시판 Git Repogitory

@RequestBody import시 swagger의 @RequestBody가 아닌 스프링 프레임워크의 것으로 import가 될 수 있도록 주의한다. 이름이 겹치기 때문에 잘못된 것이 import될 수 있다.


📌 Vue

🔷 이전에 만들었던 Axios 실습 자료에서 추가한다. (youtube 관련 컴포넌트와 뷰는 무시한다.)

Axios 실습

프로젝트 구조


router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import YoutubeView from '../views/YoutubeView.vue'
import BoardView from '../views/BoardView.vue'
import BoardList from '@/components/board/BoardList.vue'
import BoardCreate from '@/components/board/BoardCreate.vue'
import BoardDetail from '@/components/board/BoardDetail.vue'
import BoardUpdate from '@/components/board/BoardUpdate.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/youtube',
      name: 'youtube',
      component: YoutubeView
    },
    {
      path: '/board',
      name: 'board',
      component: BoardView,
      children: [
        {
          path: '',
          name: 'boardList',
          component: BoardList
        },
        {
          path: 'create',
          name: 'boardCreate',
          component: BoardCreate
        },
        {
          path: ':id',
          name: 'boardDetail',
          component: BoardDetail
        },
        {
          path: 'update',
          name: 'boardUpdate',
          component: BoardUpdate
        },
      ]
    },
  ]
})

export default router

BoardView를 추가하면서 list, create, detail, update 컴포넌트들을 children으로 넣었다.


stores/board.js

import { ref } from 'vue'
import { defineStore } from 'pinia'
import router from '@/router'
import axios from 'axios'

const REST_BOARD_API = "http://localhost:8080/api/board";

export const useBoardStore = defineStore('board', () => {
    const boardList = ref([]);

    const getBoardList = () => {
        axios.get(REST_BOARD_API)
            .then((res) => {
                boardList.value = res.data;
            })
            .catch((err) => {
                console.log(err);
            });
    };
  
    const board = ref({})

    const getBoard = (id) => {
        axios.get(`${REST_BOARD_API}/${id}`)
            .then((res) => {
                board.value = res.data;
            })
    };

    const createBoard = (board) => {
        axios({
            url: REST_BOARD_API,
            method: 'POST',
            headers: {
                "Content-Type": "application/json"
            },
            data: board
        })
            .then(() => {
                router.push({name:'boardList'})
            })
            .catch((err) => {
                console.log(err);
            });
    };

    const updateBoard = () => {
        axios.put(REST_BOARD_API, board.value)
            .then(() => {
                router.push({ name: 'boardList' });
            });
    }

    const searchBoardList = (searchCondition) => {
        axios.get(REST_BOARD_API, {
            params: searchCondition
        })
            .then((res) => {
                boardList.value = res.data;
            });
    }

    return { boardList, getBoardList, board, getBoard, createBoard, updateBoard, searchBoardList };
})

REST_BOARD_API를 호출해 DB에 저장되어 있는 데이터를 가져와 조회, 생성, 수정, 삭제가 가능하도록 한다. 이 때, 게시물의 호출이 반복되므로 prop drilling을 막기 위해 piniastore 기능을 사용한다.


components

⚙ 1. common/TheHeaderNav.vue

<template>
    <div>
        <header>
            <nav>
                <RouterLink to="/">Home</RouterLink>  |
                <RouterLink to="/youtube">Youtube</RouterLink>  |
                <RouterLink :to="{name: 'boardList'}">BoardList</RouterLink>  |
                <RouterLink :to="{name: 'boardCreate'}">BoardCreate</RouterLink>
            </nav>
        </header>
    </div>
</template>

<script setup>

</script>

<style scoped>
    nav {
        padding: 30px;
    }

    nav a {
        text-decoration: none;
        font-weight: bold;
        color: black;
    }

    nav a.router-link-exact-active {
        color: rgb(26, 207, 26);
    }
</style>

기존에 App.vue에 있던 nav를 Header로 분리하였다. 고유한 컴포넌트이므로 원칙에 따라 이름 앞에 The를 붙였다.

⚙ 2. board/BoardList.vue

<template>
    <div>
        <h4>게시글 목록</h4>
        <hr>
        <table>
            <tr>
                <th>번호</th>
                <th>제목</th>
                <th>글쓴이</th>
                <th>조회수</th>
                <th>등록</th>
            </tr>
            <tr v-for="board in store.boardList" :key="board.id">
                <td>{{ board.id }}</td>
                <td>
                    <RouterLink :to="`/board/${board.id}`">{{ board.title }}</RouterLink>
                </td>
                <td>{{ board.writer }}</td>
                <td>{{ board.viewCnt }}</td>
                <td>{{ board.regDate }}</td>
            </tr>
        </table>

        <BoardSearchInput />
    </div>
</template>

<script setup>
import { useBoardStore } from "@/stores/board";
import { onMounted } from "vue";
import BoardSearchInput from "./BoardSearchInput.vue";
const store = useBoardStore()

onMounted(() => {
    store.getBoardList()
})

</script>

<style scoped></style>

DB에서 게시물 리스트를 꺼내오면 게시판 형식으로 출력한다.

⚙ 3. board/BoardDetail.vue

<template>
    <div>
        <h4>게시글 상세</h4>
        <hr>
        <div>{{ store.board.title }}</div>
        <div>{{ store.board.writer }}</div>
        <div>{{ store.board.regDate }}</div>
        <div>{{ store.board.viewCnt }}</div>
        <div>{{ store.board.content }}</div>

        <button @click="deleteBoard">삭제</button>
        <button @click="updateBoard">수정</button>
    </div>
</template>

<script setup>
import { useRoute, useRouter } from 'vue-router';
import { useBoardStore } from '@/stores/board';
import { onMounted } from 'vue';
import axios from 'axios';

const store = useBoardStore();

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

onMounted(() => {
    store.getBoard(route.params.id);
});

const deleteBoard = () => {
    axios.delete(`http://localhost:8080/api/board/${route.params.id}`)
        .then(() => {
            router.push({ name: 'boardList' })
        })
};

const updateBoard = () => {
    router.push({ name: 'boardUpdate' });
};

</script>

<style scoped>

</style>

게시물을 누르면 상세 페이지로 이동하는데 게시물의 정보 확인과 해당 게시물을 삭제, 수정이 가능하다.

⚙ 3. board/BoardUpdate.vue

<template>
    <div>
        <h4>게시글 수정</h4>
        <fieldset>
            <legend>수정</legend>
            <div>
                <label for="title">제목 : </label>
                <input type="text" id="title" v-model="store.board.title">
            </div>
            <div>
                <label for="writer">글쓴이 : </label>
                <input type="text" id="writer" readonly v-model="store.board.writer">
            </div>
            <div>
                <label for="content">내용 : </label>
                <textarea id="content" cols="30" rows="10" v-model="store.board.content"></textarea>
            </div>
            <div>
                <button @click="updateBoard">수정</button>
            </div>
        </fieldset>
    </div>
</template>

<script setup>
import { useBoardStore } from '@/stores/board';

const store = useBoardStore();

const updateBoard = () => {
    store.updateBoard();
}


</script>

<style scoped>

</style>

상세 게시물의 id를 받아와 해당 id와 일치하는 게시글 수정이 가능하다.

⚙ 5. board/BoardSearchInput.vue

<template>
    <div class="search">
        <div>
            <label>검색 기준 :</label>
            <select v-model="searchInfo.key">
                <option value='none'>없음</option>
                <option value="writer">글쓴이</option>
                <option value="title">제목</option>
                <option value="content">내용</option>
            </select>
        </div>
        <div>
            <label>검색 내용 :</label>
            <input type="text" v-model="searchInfo.word" />
        </div>
        <div>
            <label>정렬 기준 :</label>
            <select v-model="searchInfo.orderBy">
                <option value='none'>없음</option>
                <option value="writer">글쓴이</option>
                <option value="title">제목</option>
                <option value="view_cnt">조회수</option>
            </select>
        </div>
        <div>
            <label>정렬 방향 :</label>
            <select v-model="searchInfo.orderByDir">
                <option value="asc">오름차순</option>
                <option value="desc">내림차순</option>
            </select>
        </div>
        <div>
            <button @click="searchBoardList">검색</button>
        </div>
    </div>
</template>
  
<script setup>
import { ref } from 'vue';
import { useBoardStore } from '@/stores/board'

const store = useBoardStore()
const searchInfo = ref({
    key: 'none',
    word: '',
    orderBy: 'none',
    orderByDir: 'asc'
})
const searchBoardList = function () {
    store.searchBoardList(searchInfo.value)
}
</script>
  
<style scoped>
.search {
    display: flex;
}
</style>
  

이전에 사용했던 검색 기능이다.
api가 SearchCondition을 기반으로 검색 결과를 도출하면 이곳에서 출력한다.

⚙ 6. board/BoardCreate.vue

<template>
    <div>
        <h4>게시글 작성</h4>
        <fieldset>
            <legend>등록</legend>
            <div>
                <label for="title">제목 : </label>
                <input type="text" id="title" v-model="board.title">
            </div>
            <div>
                <label for="writer">글쓴이 : </label>
                <input type="text" id="writer" v-model="board.writer">
            </div>
            <div>
                <label for="content">내용 : </label>
                <textarea id="content" cols="30" rows="10" v-model="board.content"></textarea>
            </div>
            <div>
                <button @click="createBoard">등록</button>
            </div>
        </fieldset>
    </div>
</template>

<script setup>
import { ref } from "vue";
import { useBoardStore } from "@/stores/board";

const store = useBoardStore()
const board = ref({
    title: '',
    writer: '',
    content: ''
})

const createBoard = function () {
    store.createBoard(board.value)
}
</script>

<style scoped></style>

새로운 게시물을 생성하여 API를 통해 DB에 저장한다.


🖨 결과

분량이 커서 그런지 velog의 문제인지 더 이상 이미지가 올라가지 않으므로 목록, 상세 페이지, 수정 페이지만 이곳에 올린다.

완성된 Board git Repogitory


정신없이 지나갔지만 완성은 잘 되어서 다행이다.

profile
Hodie mihi, Cras tibi

0개의 댓글

관련 채용 정보