본문 바로가기
내일배움 강의/강의- Node.js 입문, 숙련

입문 2주차 7 Update, Delete 구현하기

by GREEN나무 2024. 11. 24.
728x90

01. [할 일 메모 사이트] - Update, Delete


1) [할 일 메모 사이트] - Update, Delete API 정리하기

  Method URL Request Response
할일 순서, 내용 변경, 완료/해제 PATCH /api/todos/:todoId { "order": 2,
"value": "수정된 해야할 일입니다.",
"done": false }
{ }
할일 삭제 DELETE /api/todos/:todoId { } { } 

데이터 수정 :  PATCH,  put 

할일 삭제 : 삭제권한 인증, 인가는나중에

 // 코드에서 요청 반환하도록만들어서 Response필요 X

2) 할 일 순서 변경 API 만들기

Todo 데이터에서 order 값만 변경하여 할 일 순서를 바꿉니다.

여기서 주의해야할 점이 있습니다. 3번째 할 일을 2번으로 변경할 때, 만약 2번 할 일이 이미 존재한다면 어떻게 해야할까요? 3→2, 2→3 과 같이 2개의 Todo 데이터에서 order를 함께 변경해줘야합니다. 
이 과정을 거치면, 두 개의 Todo 데이터의 order 값이 서로 바뀌게 되므로, 할 일 목록의 순서도 바뀌게 되는 것이죠.



이번엔 할 일 순서를 변경하는 API를 짜볼 건데요. Todo 데이터에 order 값을 서로 바꿔서 저장하는 기능을 구현해볼거에요!
해야할 일 순서를 변경하는 것은 어떻게 구현할까요? 간단합니다. Todo 데이터에서 order 값만 변경해주면 되는 것이죠.
그런데, 여기서 주의해야할 점이 있습니다. 3번째 할 일을 2번으로 변경할 때, 만약 2번 할 일이 이미 존재한다면 어떻게 해야할까요? 3→2, 2→3 과 같이 2개의 Todo 데이터에서 order를 함께 변경해줘야합니다. 
이 과정을 거치면, 두 개의 Todo 데이터의 order 값이 서로 바뀌게 되므로, 할 일 목록의 순서도 바뀌게 되는 것이죠.
이제 위에서 설명한 내용을 바탕으로 할 일 순서를 변경하는 API를 작성해볼까요? 😎​
[코드스니펫] 할 일 순서 변경하는 API, routes/todos.router.js

/** 해야할 일 순서 변경 API**/

// /:todoId' : 수정해야 할 데이터를 알기 위해 지정.
router.patch("/todos/:todoId", async (req, res) => {
  // 변경할 '해야할 일'의 ID 값을 가져옵니다.
  const { todoId } = req.params;
  // '해야할 일'을 몇번째 순서로 설정할 지 order 값을 가져옵니다.
  const { order } = req.body;

  // 현재 나의 order가 무엇인자 알아야 한다.
  //.findById() : ()안의 ID로 데이터 찾기
  // 변경하려는 '해야할 일'을 가져옵니다. 만약, 해당 ID값을 가진 '해야할 일'이 없다면 에러를 발생시킵니다.
  const currentTodo = await Todo.findById(todoId).exec();
  if (!currentTodo) {
    return res
      .status(404) // 400은 클라이언트 잘못
      .json({ errorMessage: "존재하지 않는 해야할 일 입니다." });
  }

  // 해야할 일 순서 변경
  if (order) {
    // 변경하려는 order 값을 가지고 있는 '해야할 일'을 찾습니다.
    // findOne : 하나만 찾기(DB 컬럼 조건에 유일성을 지정해서 'order' 값이 일치하는 데이터는 하나밖에 없긴 함.)
    const targetTodo = await Todo.findOne({ order }).exec(); // {order}는  {order:order}를 의미함. 객체의 값이라 {}사용
    if (targetTodo) {
      // targetTod 값이 있다면
      // 만약, 이미 해당 order 값을 가진 '해야할 일'이 있다면, 해당 '해야할 일'의 order 값을 변경하고 저장합니다.
      targetTodo.order = currentTodo.order; // order값 바꾸기.. 4->1이면 2,3번은 그대로 있고 4, 1번만 바뀜
      await targetTodo.save();
    }
    // 변경하려는 '해야할 일'의 order 값을 변경합니니다.
    currentTodo.order = order;
  }

  // 변경된 '해야할 일'을 저장합니다.
  await currentTodo.save();

  return res.status(200).json({});
});

export default router;
더보기
// /routes/todos.router.js

import express from "express";
import Todo from "../schemas/todo.schema.js";

// 라우터 생성
const router = express.Router();

/** 할일등록 API **/

// 할일 등록은 .post메소드 사용
// URL : /todos
// DB사용하려면 async, 즉 비동기 함수를 사용해야 합니다. 데이터를 조회할 동안 프로그램이 기다리도록 만들어야 합니다.
router.post("/todos", async (req, res, next) => {
  // next는 데스트용.

  // 1. 클라이언트에게 전달받은 value 데이터를 변수에 저장합니다.
  const { value } = req.body;

  // 1-5. 데이터 유효성 검사 기능 추가
  // value가 존재하지 않을 때, 클라이언트에게 에러 메시지를 전달합니다.
  if (!value) {
    // 값이 없을 때
    return res
      .status(400) // 400 : 클라이언트 잘못으로 오류 발생
      .json({ errorMessage: "해야할 일 데이터가 존재하지 않습니다." }); // 리턴 데이터 형태: json
  }

  // 2. 해당하는 마지막 order 데이터를 조회 (MongoDB에서 'order' 값이 가장 높은 '해야할 일')
  // findOne : 한개의 데이터 조회
  // sort(컬럼) : 지정 컬럼을 기준으로 정렬
  // sort('컬럼이름') : 오름차순 정렬
  // sort('-컬럼이름') : 내림차순 정렬
  // .exec(); : 몽구스 DB에서 조회시 무조건 마지막에 붙여야 오류 안남
  // promis로 반환하는데,  .exec()가 없으면 await가 작동하지 않습니다.
  // Todo 컬렉션에서 'order'차트를 내림차순으로 맨 위의 것(제일 큰 값)을 하나 가져옴니다.

  const todoMaxOrder = await Todo.findOne().sort("-order").exec();

  // 3. 만약 존재한다면 현재 할 일에 +1, order 데이터가 존재하지 않는다면 1로 할당합니다.('order' 값이 가장 높은 도큐멘트의 1을 추가하거나 없다면, 1을 할당합니다.)
  const order = todoMaxOrder ? todoMaxOrder.order + 1 : 1;

  // 4. 해야할 일 등록
  // todo를 실제 인스턴스형식으로 만듦
  const todo = new Todo({ value, order });
  // 실제로 DB에 저장
  await todo.save();

  // 5. 해야할 일을 클라이언트에게 반환
  return res.status(201).json({ todo });
});

/** 해야할 일 목록 조회 API**/
// API등록은 router에서 합니다.
router.get("/todos", async (req, res, next) => {
  // 1. 해야할 일 목록 조회를 진행한다.
  const todos = await Todo.find().sort("-order").exec();

  // 2. 해야할 일 목록 조회 결과를 클라이언트에게 반환 한다.
  return res.status(200).json({ todos }); // 200 성공
});

/** 해야할 일 순서 변경 API**/

// /:todoId' : 수정해야 할 데이터를 알기 위해 지정.
router.patch("/todos/:todoId", async (req, res) => {
  // 변경할 '해야할 일'의 ID 값을 가져옵니다.
  const { todoId } = req.params;
  // '해야할 일'을 몇번째 순서로 설정할 지 order 값을 가져옵니다.
  const { order } = req.body;

  // 현재 나의 order가 무엇인자 알아야 한다.
  //.findById() : ()안의 ID로 데이터 찾기
  // 변경하려는 '해야할 일'을 가져옵니다. 만약, 해당 ID값을 가진 '해야할 일'이 없다면 에러를 발생시킵니다.
  const currentTodo = await Todo.findById(todoId).exec();
  if (!currentTodo) {
    return res
      .status(404) // 400은 클라이언트 잘못
      .json({ errorMessage: "존재하지 않는 해야할 일 입니다." });
  }

  // 해야할 일 순서 변경
  if (order) {
    // 변경하려는 order 값을 가지고 있는 '해야할 일'을 찾습니다.
    // findOne : 하나만 찾기(DB 컬럼 조건에 유일성을 지정해서 'order' 값이 일치하는 데이터는 하나밖에 없긴 함.)
    const targetTodo = await Todo.findOne({ order }).exec(); // {order}는  {order:order}를 의미함. 객체의 값이라 {}사용
    if (targetTodo) {
      // targetTod 값이 있다면
      // 만약, 이미 해당 order 값을 가진 '해야할 일'이 있다면, 해당 '해야할 일'의 order 값을 변경하고 저장합니다.
      targetTodo.order = currentTodo.order; // order값 바꾸기.. 4->1이면 2,3번은 그대로 있고 4, 1번만 바뀜
      await targetTodo.save();
    }
    // 변경하려는 '해야할 일'의 order 값을 변경합니니다.
    currentTodo.order = order;
  }

  // 변경된 '해야할 일'을 저장합니다.
  await currentTodo.save();

  return res.status(200).json({});
});

export default router;


test : 6번(장조림) 과 3번 order 번호 바꾸기

// localhost:3000/api/todos/_ID값
PATCH localhost:3000/api/todos/6740546947412f98efd6d5f3

// body json
// { "order" : 바꿀  order번호}

{
	"order" : 3
}

 

중요도 변경
장조림의 order값이 6->3

 

 Response값이 왜 아무것도 전달하지 않는가

현재 프론트엔드에서는 PATCH /api/todos:/todoId API를 호출한 후, 바로 GET /api/todos API를 요청하도록 구현했기에 변경된 Response를 반환하지 않더라도 괜찮습니다.
할 일 순서 변경 API에서는 클라이언트가 전달한 ID에 해당하는 ‘해야할 일’이 존재하는지 확인하고, order에 해당하는 Todo 데이터가 존재한다면, 2개의 순서를 변경하게 됩니다.​

현재는 2개의 데이터를 변경하기 위해서 1개의 할 일을 수정한 이후 나머지 다른 할 일 순서를 변경하는 방식으로 비즈니스 로직이 구현되어있습니다.


3) 할 일 완료/해제 API 만들기

 API는 done이라는 값을 전달받아 할 일 완료/해제 기능을 구현하게됩니다.
할 일 완료/해제 API에서 가장 중요한 것은 API에서 Todo 항목의 doneAt이라는 필드에 완료된 시점의 시간을 기입해야 한다는 것입니다. 
만약, done값이 true로 전달되면, 해당 할 일이 완료된 것으로 간주하고 doneAt필드에 현재 시간을 기록하게됩니다. 이를 통해 언제 작업이 완료되었는지 추적할 수 있습니다.

done 값이 false로 전달되면, doneAt 필드또한 null로 수정되어야합니다.


 할 일 완료/해제 API   PATCH /api/todos/:todoId API를 수정

// /routes/todos.router.js

router.patch('/todos/:todoId', async (req, res) => {
  const { todoId } = req.params;

  const { order, done } = req.body; // done 추가(완료여부 조회)
...
  if (done !== undefined) {
    // 변경하려는 '해야할 일'의 doneAt 값을 변경합니다.
    currentTodo.doneAt = done ? new Date() : null;
  }

 

더보기
// /routes/todos.router.js

import express from "express";
import Todo from "../schemas/todo.schema.js";

// 라우터 생성
const router = express.Router();

/** 할일등록 API **/
router.post("/todos", async (req, res, next) => {
  // 1. 클라이언트에게 전달받은 value 데이터를 변수에 저장합니다.
  const { value } = req.body;

  // 1-5. 데이터 유효성 검사 기능 추가
  if (!value) {
    return res
      .status(400) // 400 : 클라이언트 잘못으로 오류 발생
      .json({ errorMessage: "해야할 일 데이터가 존재하지 않습니다." });
  }

  // 2. 해당하는 마지막 order 데이터를 조회 (MongoDB에서 'order' 값이 가장 높은 '해야할 일')
  const todoMaxOrder = await Todo.findOne().sort("-order").exec();

  // 3. 만약 존재한다면 현재 할 일에 +1, order 데이터가 존재하지 않는다면 1로 할당합니다.('order' 값이 가장 높은 도큐멘트의 1을 추가하거나 없다면, 1을 할당합니다.)
  const order = todoMaxOrder ? todoMaxOrder.order + 1 : 1;

  // 4. 해야할 일 등록
  // todo를 실제 인스턴스형식으로 만듦
  const todo = new Todo({ value, order });
  // 실제로 DB에 저장
  await todo.save();

  // 5. 해야할 일을 클라이언트에게 반환
  return res.status(201).json({ todo });
});

/** 해야할 일 목록 조회 API**/
// API등록은 router에서 합니다.
router.get("/todos", async (req, res, next) => {
  // 1. 해야할 일 목록 조회를 진행한다.
  const todos = await Todo.find().sort("-order").exec();

  // 2. 해야할 일 목록 조회 결과를 클라이언트에게 반환 한다.
  return res.status(200).json({ todos }); // 200 성공
});

/** 해야할 일 순서 변경, 완료/해제 API **/
// /:todoId' : 수정해야 할 데이터를 알기 위해 지정.
router.patch("/todos/:todoId", async (req, res) => {
  const { todoId } = req.params;

  // 완료여부확인을 위해 done 추가
  const { order, done } = req.body;

  const currentTodo = await Todo.findById(todoId).exec();
  if (!currentTodo) {
    return res
      .status(404) // 400은 클라이언트 잘못
      .json({ errorMessage: "존재하지 않는 해야할 일 입니다." });
  }

  // 해야할 일 순서 변경
  if (order) {
    const targetTodo = await Todo.findOne({ order }).exec(); // {order}는  {order:order}를 의미함. 객체의 값이라 {}사용
    if (targetTodo) {
      targetTodo.order = currentTodo.order; // order값 바꾸기.. 4->1이면 2,3번은 그대로 있고 4, 1번만 바뀜
      await targetTodo.save();
    }
    // 변경하려는 '해야할 일'의 order 값을 변경합니니다.
    currentTodo.order = order;
  }

  // 할일을 완료한 경우
  if (done !== undefined) {
    // false는 done에 값이 들어가지 않습니다.
    // 변경하려는 '해야할 일'의 doneAt 값을 변경합니다.
    // 위에서 todoId 조회를 위해 사용한 currentTodo필드에 doneAt라는 컬럼에 할당한다
    // 삼항문으로 done의 값이 있는 경우 그 값을 할당 합니다.
    currentTodo.doneAt = done ? new Date() : null;
  }

  // 변경된 '해야할 일'을 저장합니다.
  await currentTodo.save();

  return res.status(200).json({});
});

export default router;

test

숫정한 코드를 저장하고 app.js를 실행해 서버를 열어주세요

_id로 할일을 특정하고 done값을 true로 바꾸세요

PATCH   localhost:3000/api/todos/674038bc91f2313409188a1d


MongoDB에 할 일이 완료된 시간 즉, doneAt 필드를 조회한다면, 아래와 같은 doneAT를  확인할 수 있니다.


저장되어 있는 시간이 UTC(협정 세계시)를 기준으로 설정되어 있는데 이는 백엔드 서버가 전 세계의 다양한 시간대의 사용자와 통신할 때 시간에 대한 혼란을 방지하기 위함입니다.
일반적으로 컴퓨터의 시간은 유닉스 시간(Unix time)이라 불리는 1970년 1월 1일 00:00:00 UTC부터의 시간을 뜻하게됩니다. 보통 백엔드 서버에서 “유닉스 시간”을 사용하여 시간을 저장하며, 이를 통해 서로 다른 시간대의 사용자와의 통신에서 발생할 수 있는 문제를 방지하게 됩니다.
따라서, 우리가 구현하는 시스템에서도 UTC를 기준으로 시간을 관리하는것이 중요합니다,

4) 할 일 삭제 API 만들기

 API는 삭제할 할 일의 ID를 클라이언트에게 전달받아, MongoDB에서 해당 데이터를 삭제하도록 구현하게됩니다.​
 클라이언트에게 전달받은 ID를 바탕으로 MongoDB에서 해당 Todo 데이터를 삭제합니다. 만약, 해당 ID의 할 일이 존재하지 않는다면 에러 메시지를 반환합니다.

 

할 일 삭제 API     routes/todos.router.js

// routes/todos.router.js

/** 할 일 삭제 **/
router.delete("/todos/:todoId", async (req, res) => {
  // 삭제할 '해야할 일'의 ID 값을 가져옵니다.
  // todoId : 경로 매개변수인 req.params에서 가져오기
  const { todoId } = req.params;

  // 삭제하려는 '해야할 일'을 가져옵니다.(조회)
  const todo = await Todo.findById(todoId).exec();
  //만약, 해당 ID값을 가진 '해야할 일'이 없다면 에러를 발생시킵니다.
  if (!todo) {
    return res
      .status(404) // 클라이언트 잘못. id 입력 오류
      .json({ errorMessage: "존재하지 않는 todo 데이터입니다." });
  }

  // 조회된 '해야할 일'을 삭제합니다.
  // MongDB의 데이터 id는 '_id'에 있습니다.
  // 코드에서 중복없이 발급되는 데이터 id를 컬럼todoId에 할당하고 있습니다.
  await Todo.deleteOne({ _id: todoId }).exec();

  return res.status(200).json({});
});

 

 

order 2의 할일을 삭제하겠습니다.

order 2번의 데이터가 삭제됬을 확인할 수 있습니다.

 

5) 할 일 내용 변경 API 만들기

이번 API는 이전에 작업한 PATCH /api/todos/:todoId API의 코드를 수정하여 구현하게됩니다.

[Request] PATCH /api/todos/:todoId

{
  "value": "수정된 할 일 입니다."
}


[Response] PATCH /api/todos/:todoId ( 빈 객체 전달)

{ }

/routes/todos.router.js 

router.patch("/todos/:todoId", async (req, res) => {
  const { todoId } = req.params;
  const { order, done, value } = req.body; // value 추가
  ...
  if (value) {
    currentTodo.value = value; // 변경하려는 '해야할 일'의 내용을 변경합니다.
  }
더보기
// /routes/todos.router.js


/** 순서 변경, 할 일 완료/해제, 할 일 내용 변경 **/
router.patch('/todos/:todoId', async (req, res) => {
  // 변경할 '해야할 일'의 ID 값을 가져옵니다.
  const { todoId } = req.params;
  // 클라이언트가 전달한 순서, 완료 여부, 내용 데이터를 가져옵니다.
  const { order, done, value } = req.body;

  // 변경하려는 '해야할 일'을 가져옵니다. 만약, 해당 ID값을 가진 '해야할 일'이 없다면 에러를 발생시킵니다.
  const currentTodo = await Todo.findById(todoId).exec();
  if (!currentTodo) {
    return res
      .status(404)
      .json({ errorMessage: '존재하지 않는 todo 데이터입니다.' });
  }

  if (order) {
    // 변경하려는 order 값을 가지고 있는 '해야할 일'을 찾습니다.
    const targetTodo = await Todo.findOne({ order }).exec();
    if (targetTodo) {
      // 만약, 이미 해당 order 값을 가진 '해야할 일'이 있다면, 해당 '해야할 일'의 order 값을 변경하고 저장합니다.
      targetTodo.order = currentTodo.order;
      await targetTodo.save();
    }
    // 변경하려는 '해야할 일'의 order 값을 변경합니니다.
    currentTodo.order = order;
  }
  if (done !== undefined) {
    // 변경하려는 '해야할 일'의 doneAt 값을 변경합니다.
    currentTodo.doneAt = done ? new Date() : null;
  }
  if (value) {
    // 변경하려는 '해야할 일'의 내용을 변경합니다.
    currentTodo.value = value;
  }

  // 변경된 '해야할 일'을 저장합니다.
  await currentTodo.save();

  return res.status(200).json({});
});



test