목표

  • useFetch()로 서버 데이터 가져오기
  • watch()를 사용해 서버 데이터 변경 감지하기
  • $fetch 사용해 HTTP 메서드 사용하기 (GET, POST, DELETE)
  • 서버 데이터를 등록순/최신순 정렬하기

 


결과 화면

 


버전

node v.20.8.1

nuxt v.3.8.1

vue v.3.3.8

 

1. Nest.js + PSQL (백앤드 + 데이터베이스)

https://jae-study.tistory.com/80

Nest.js와 Psql을 사용해서 백앤드와 데이터베이스를 만들었다.

 

직접 백앤드와 데이터베이스를 만들어도 되고,

JSONPlaceholder 사이트를 통해 가짜 데이터를 받아와도 된다.

 

2. toDo 가져오기

  • pages/index.vue

Nuxt3에서는 axios 대신 useFetch()를 사용해 데이터 패칭을 한다.

useFetch()는 데이터를 비동기적으로 불러올 수 있고, 기존 axios 코드보다 로직을 간소화할 수 있다.

 

https://jae-study.tistory.com/127

useFetch()에 대한 자세한 설명은 위의 링크를 참고한다.

 

  1. Nuxt3는 타입스크립트 기반이기 때문에 useFetch()로 받아오는 데이터의 타입을 정의한다. (IToDo[])
  2. 서버에서 받은 데이터를 가공하기 위해 toDoData 변수를 만든다. useFetch()로 받아오는 데이터의 타입은 기본적으로 객체(object)이다. 이것을 배열로 만들어 정렬(sort 메서드)을 하고 싶어 toDoData 변수를 만들었다.
  3. toDo를 추가하고 삭제할 때마다 실시간으로 렌더링 하기 위해 watch 함수를 사용한다.
<script setup lang="ts">
interface IToDo {
  id: number
  todo: string
  created_at: Date
}

// axios.get 기능 (toDo 가져오기)
const { data, refresh } = await useFetch<IToDo[]>('http://localhost:3001/todo')
const toDoData = ref(data.value ? [...data.value] : [])

watch(() => data.value, () => {
  toDoData.value = isActive.value ? [...data.value] : [...data.value].reverse()
})

...
</script>

 

3. toDo 추가하기

  • pages/index.vue
  1. newToDo 변수를 만들어 빈 값이면 alert() 창이 나오게 하고, 값이 있으면 POST 메서드를 실행한다.
  2. POST가 완료되면 newToDo를 빈 값으로 만들고, 실시간 값 변화를 감지하기 위해 refresh()를 실행한다.
<script setup lang="ts">
const newToDo = ref('')

// axios.post 기능 (toDo 추가하기)
async function addToDo() {
  if(newToDo.value === '') {
    alert('할 일을 입력해 주세요.')
  } else {
    await $fetch('http://localhost:3001/todo', {
      method: 'POST',
      body: { todo: newToDo.value }
    })

    newToDo.value = ''
    await refresh()
  }
}

...
</script>

 

4. toDo 삭제하기

  • pages/index.vue
  1. 리스트 1개만 삭제하고 싶으면 해당하는 id 값을 매개변수로 받아 DELETE 메서드를 실행한다.
  2. 리스트 전체를 삭제하고 싶으면 api 전체 리스트에서 DELETE 메서드를 실행한다.
  3. DELETE가 완료되면 실시간 값 변화를 감지하기 위해 refresh()를 실행한다.
<script setup lang="ts">
// axios.delete 기능 (toDo 삭제하기)
async function deleteToDo(id: number) {
  await $fetch(`http://localhost:3001/todo/${id}`, {
    method: 'DELETE'
  })

  await refresh()
}

// axios.delete 기능 (toDo 전체 삭제하기)
async function clearToDo() {
  if(window.confirm('리스트를 모두 삭제하시겠습니까?')) {
    await $fetch(`http://localhost:3001/todo`, {
      method: 'DELETE'
    })

    await refresh()
  }
}

...
</script>

 

5. toDo 정렬하기

  • pages/index.vue
  1. useFetch()로 받은 데이터를 배열로 만들어 sort() 메서드를 사용해 등록순/최신순으로 정렬한다.
<script setup lang="ts">
const isActive = ref(true)

// 등록순, 최신순 정렬하기
function sortToDo(compareFn: (a: IToDo, b: IToDo) => number) {
  toDoData.value = [...data.value].sort(compareFn)
  isActive.value = !isActive.value
}

...
</script>

<template>
  <section class="w-full max-w-screen-lg min-h-screen m-auto">
    ...

    <div v-if="toDoData.length > 0">
      <div class="flex gap-2 mb-6">
        <button
          :class="{ 'underline underline-offset-4' : isActive }"
          @click="() => sortToDo((a, b) => a.id - b.id)">
          등록순
        </button>
        <i>|</i>
        <button
          :class="{ 'underline underline-offset-4' : !isActive }"
          @click="() => sortToDo((a, b) => b.id - a.id)">
          최신순
        </button>
      </div>
    </div>
    
    ...
  </section>
</template>

 

6. 마크업

  • pages/index.vue
  1. 테일윈드를 사용해 CSS를 작성했다.
  2. 각각의 버튼에 맞는 @click 이벤트를 작성한다.
<template>
  <section class="w-full max-w-screen-lg min-h-screen m-auto">
    <h1 class="mt-6 mb-12 md:mt-10 text-5xl font-bold text-center">To Do List</h1>

    <form
      class="mb-6"
      @submit.prevent="addToDo()">
      <label
        for="toDo"
        class="block mb-3 text-xl font-bold">
        New To Do
      </label>

      <div class="flex gap-3 md:gap-5">
        <input
          v-model="newToDo"
          type="text"
          id="toDo"
          class="w-3/4 px-3 py-4 rounded outline-none text-black"
          placeholder="할 일을 입력해 주세요."/>
        <button class="w-1/4 bg-teal-600 hover:bg-teal-500 transition rounded">추가하기</button>
      </div>
    </form>

    <div v-if="toDoData.length > 0">
      <div class="flex gap-2 mb-6">
        <button
          :class="{ 'underline underline-offset-4' : isActive }"
          @click="() => sortToDo((a, b) => a.id - b.id)">
          등록순
        </button>
        <i>|</i>
        <button
          :class="{ 'underline underline-offset-4' : !isActive }"
          @click="() => sortToDo((a, b) => b.id - a.id)">
          최신순
        </button>
      </div>

      <ul>
        <li
            v-for="(data, key) in toDoData"
            :key="key"
            class="flex justify-between items-center gap-3 mb-6 px-4 py-3 md:px-5 border rounded">
          <p class="flex gap-2 text-base md:text-lg">
            <span>{{ key + 1 }}.</span> {{ data.todo }}
          </p>

          <button
            class="min-w-fit px-5 py-2 bg-teal-600 hover:bg-teal-500 transition rounded"
            @click="deleteToDo(data.id)">
            삭제
          </button>
        </li>
      </ul>

      <button
        class="block w-full md:w-1/3 m-auto mt-12 px-3 py-4 bg-teal-950 hover:bg-teal-500 transition rounded"
        @click="clearToDo()">
        전체 삭제하기
      </button>
    </div>
  </section>
</template>

 


상세 코드는 깃허브 페이지를 참고한다.

 

https://github.com/heejae0811/nuxt3-todo/tree/axios

 

GitHub - heejae0811/nuxt3-todo: main: Pinia To Do List / axios: Nest.js + PSQL To Do List (useFetch)

main: Pinia To Do List / axios: Nest.js + PSQL To Do List (useFetch) - GitHub - heejae0811/nuxt3-todo: main: Pinia To Do List / axios: Nest.js + PSQL To Do List (useFetch)

github.com

 

https://github.com/heejae0811/todo-backend

 

GitHub - heejae0811/todo-backend: Nest.js + PSQL ToDo 백앤드

Nest.js + PSQL ToDo 백앤드. Contribute to heejae0811/todo-backend development by creating an account on GitHub.

github.com

+ Recent posts