블록체인 TIL-5Week-33Day

디오·2023년 4월 14일
1

오늘은 어제 진행하다 다 마무리하지 못한 백엔드와 DB연결과정에 대해 진행하였다. 어제는 DB에 대해서 간략한 내용을 듣고 진짜 DB에 백엔드를 연결하는 과정을 진행했는데 다 마무리하지 못하고 수업이 끝난 부분도 있고해서 오늘 전반적인 정리를 해볼까 한다. 물론 오늘도 전부 마무리된 것은 아니기 때문에 지금까지 진행했던 부분들에 대해서만 정리해서 올려보도록 하겠다.



➡️오늘 수업진행과정.

  • 어제 진행하던 백엔드와 DB 터미널로 연결하는 부분 진행.

  • 백엔드에 routes에 있는 user.js에 유저조회부분 만듬.

  • routes에 todo.js 만들어서 투두생성,조회,완료,삭제 만듬

  • 프론트엔트 폴더 만들고 강사님이 주신 깃헙 클론해서 설치후 App.jsx 만듬.

  • App.jsx 안에 내용 복붙하고, 로그인창 만들어주기 위해 if문에 return을 사용해 로그인해야 원하는 페이지를 볼 수 있도록 만듬.

  • 로그인 부분 코드를 컴포넌트화 시킴.

  • 컴포넌트화 시킨 Login.jsx 파일에 async를 사용해 .env와 연결하고 axios도 연결함.



✅Backend 코드리뷰.

<user.js>

const express = require("express");
const { PrismaClient } = require("@prisma/client");

const router = express.Router();

const client = new PrismaClient(); 

router.post("/", async (req, res) => {
  try {
    const { account } = req.body;

    const existUser = await client.user.findUnique({
      where: {
        account,
      },
    });
    if (existUser) {
      return res
        .status(400)
        .json({ ok: false, error: "Already exist account." });
    }

    const user = await client.user.create({
     
      data: {
        account,
      },
    });

    res.json({ ok: true, user });
  } catch (error) {
    console.error(error);
  }
});

router.get("/:account", async (req, res) => {
  try {
    const { account } = req.params;

    const user = await client.user.findUnique({
      where: {
        account,
      },
    });
   
    if (!user) {
      return res.status(400).json({
        ok: false,
        error: "Not exist user.",
      });
    }

    res.json({
      ok: true, 
      user,
    });
  } catch (error) {
    console.error(error);
  }
});

module.exports = router;
  • import를 통한 연결.

    • express 모듈을 불러와 express 변수에 할당했다.

    • Prisma ORM에 있는 PrismaClient 객체를 가져와 PrismaClient 변수에 할당했다. (PrismaClient를 사용하면 데이터베이스에 접근할 수 있다.)

    • express 모듈의 Router 함수를 사용하여 새로운 라우팅 객체를 만들었다.

    • PrismaClient 객체를 생성하는데 new PrismaClient()를 호출하여 Prisma ORM과 데이터베이스를 연결했다.



  • 유저생성.(해당코드는 HTTP POST 요청을 처리하는 라우터이며, 클라이언트로부터 전송된 데이터를 기반으로 Prisma ORM을 사용하여 데이터베이스에 새로운 사용자 계정을 만드는 역할을 한다.)

    • req 객체에서 body 프로퍼티에서 account 값을 추출한다. 그리고 req.body는 HTTP POST 요청의 본문(body)에 있는 데이터를 읽어올 때 사용된다.

    • Prisma ORM을 사용하여 account 값을 가진 사용자가 이미 데이터베이스에 존재하는지를 확인한다. client.user.findUnique() 메소드는 Prisma ORM을 사용하여 데이터베이스에서 특정 조건을 만족하는 유일한 데이터를 찾는다.

    • 이미 존재하는 사용자의 경우 에러 메시지를 반환한다. HTTP 응답 객체 res의 status() 메소드와 json() 메소드를 사용하여 클라이언트에게 적절한 에러 응답을 보내도록 한다.

    • 데이터베이스에 존재하지 않는 사용자의 경우 Prisma ORM을 사용하여 새로운 사용자를 생성한다. client.user.create() 메소드를 사용하여 데이터베이스에 새로운 데이터를 만들 수 있다.

    • 새로운 사용자 데이터를 클라이언트에게 반환한다. res 객체의 json() 메소드를 사용하여 응답 본문에 JSON 형식으로 데이터를 담아 클라이언트에게 응답한다.

    • 예외가 발생한 경우 catch 블록에서 해당 예외를 콘솔에 출력한다.



  • 유저조회.(해당코드는 Express에서 HTTP GET 요청을 처리하는 라우트 핸들러다. 이 핸들러는 /account 경로로 GET 요청이 들어올 때 실행되는데 :account는 동적 라우팅 파라미터이기 때문에 URL에서 다른 값으로 대체될 수 있다.)

    • req 객체에서 params 프로퍼티에서 account 값을 추출한다. req.params는 동적 라우팅 파라미터를 읽어올 때 사용된다.

    • Prisma ORM을 사용하여 account 값을 가진 사용자가 데이터베이스에 존재하는지 확인한다. client.user.findUnique() 메소드는 Prisma ORM을 사용하여 데이터베이스에서 특정 조건을 만족하는 유일한 데이터를 찾는다.

    • 데이터베이스에서 사용자를 찾지 못한 경우 에러 메시지를 반환한다. HTTP 응답 객체 res의 status() 메소드와 json() 메소드를 사용하여 클라이언트에게 에러 메세지를 보낸다.

    • 데이터베이스에서 사용자를 찾은 경우, 해당 사용자를 JSON 형식으로 클라이언트에게 반환한다. 그리고 res.json() 메소드를 사용하여 응답 본문에 JSON 형식으로 데이터를 담아 클라이언트에게 응답한다.

    • 예외가 발생한 경우 catch 블록에서 해당 예외를 콘솔에 출력한다.



<todo.jsx>

const express = require("express");
const { PrismaClient } = require("@prisma/client");
const { route } = require("./user");

const router = express.Router();

const client = new PrismaClient();

// 투두 생성
router.post("/", async (req, res) => {
  try {
    const { todo, userId } = req.body;

    if (!todo) {
    
      return res.status(400).json({ ok: false, error: "Not exist todo." });
    }
    if (!userId) {
    
      return res.status(400).json({ ok: false, error: "Not exist userId." });
    }

    const user = await client.user.findUnique({
      where: {
        id: parseInt(userId),
      },
    });

    if (!user) {
      return res.status(400).json({ ok: false, error: "Not exist user." });
    }

    const newTodo = await client.todo.create({
      data: {
        todo, 
        isDone: false,
        userId: user.id, 
      },
    });

    res.json({ ok: true, todo: newTodo });
  } catch (error) {
    console.error(error);
  }
});

// 투두 조회
router.get("/:userId", async (req, res) => {
  try {
    const { userId } = req.params;

    const user = await client.user.findUnique({
      where: {
        id: parseInt(userId),
      },
    });

    if (!user) {
      return res.status(400).json({ ok: false, error: "Not exist user." });
    }

    const todos = await client.todo.findMany({
      where: {
        userId: parseInt(userId),
      },
    });

    res.json({ ok: true, todos });
  } catch (error) {
    console.error(error);
  }
});

// 투두 완료
router.put("/:id/done", async (req, res) => {
  try {
    const { id } = req.params;
    const { userId } = req.body;

    const existTodo = await client.todo.findUnique({
      where: {
        id: parseInt(id),
      },
    });

    if (!existTodo) {
      return res.status(400).json({ ok: false, error: "Not exist todo." });
    }
    if (existTodo.userId !== parseInt(userId)) {
      return res.status(400).json({ ok: false, error: "U R not todo owner." });
    }

    const updatedTodo = await client.todo.update({
      where: {
        id: parseInt(id),
      },
      data: {
        isDone: !existTodo.isDone,
      },
    });

    res.json({ ok: true, todo: updatedTodo });
  } catch (error) {
    console.error(error);
  }
});

// 투두 삭제
router.delete("/:id", async (req, res) => {
  try {
    const { id } = req.params;
    const { userId } = req.body;

    const existTodo = await client.todo.findUnique({
      where: {
        id: parseInt(id),
      },
    });

    if (!existTodo) {
      return res.status(400).json({ ok: false, error: "Not exist todo." });
    }
    if (existTodo.userId !== parseInt(userId)) {
      return res.status(400).json({ ok: false, error: "U R not todo owner." });
    }

    const deletedTodo = await client.todo.delete({
      where: {
        id: parseInt(id),
      },
    });

    res.json({ ok: true, todo: deletedTodo });
  } catch (error) {
    console.error(error);
  }
});
module.exports = router;
  • import를 통한 연결.

    • 대부분의 연결정보는 상단에 있는 user.jsx와 같고, 다른 부분은 const { route } = require("./user"); 인데 user 라우팅을 route 변수에 할당했다.



  • 투두 생성. (새로운 todo를 생성하는 라우터)

    • 클라이언트는 HTTP POST 요청을 보내고, 요청 본문에 todo 내용과 연관된 userId를 포함시킨다. 서버는 요청을 받으면 Prisma ORM을 사용하여 새로운 todo를 생성하고, 이를 데이터베이스에 저장한다.

    • 코드를 자세히 살펴보면, 라우터는 HTTP POST 요청을 받는다. 요청 본문에서 todo 내용과 userId를 추출하고, todo가 없거나 userId가 없는 경우 에러를 반환하도록 했다.

    • 이후 Prisma ORM을 사용하여 해당 userId에 해당하는 사용자가 존재하는지 확인하고, 사용자가 존재하지 않는 경우 에러를 반환한다.

    • 마지막으로, Prisma ORM을 사용하여 todo를 생성하고, 생성된 todo를 클라이언트에게 반환하도록 했다.



  • 투두 조회. (클라이언트에서 GET 요청이 들어오면, 파라미터로 전달된 userId 값을 이용해 유저의 모든 Todo목록을 조회)

    • 클라이언트에서 전달된 userId 값을 파라미터에서 추출한다.

    • Prisma ORM을 이용하여, userId 값에 해당하는 유저를 DB에서 조회하고, 유저가 존재하지 않으면 "Not exist user." 메시지와 함께 400 에러를 반환한다.

    • Prisma ORM을 이용하여, userId 값에 해당하는 Todo목록을 DB에서 조회한다.

    • 조회된 Todo목록을 클라이언트에게 반환한다.



  • 투두 완료. (todo의 완료 여부를 변경하는 기능을 구현한 라우터)

    • PUT 메서드를 사용하여 "/:id/done" 경로에 요청이 들어왔을 때 이 라우터 함수를 실행하도록 한다.

    • 함수 내부에서는 요청 매개변수에서 todo의 id와 userId를 가져오고, Prisma ORM을 사용하여 해당 id와 userId에 해당하는 todo를 검색한다. 검색한 todo가 존재하지 않을 경우 400 에러를 반환하도록 한다.

    • 이후 검색한 todo와 요청한 사용자의 todo가 맞는지 검사하고, 사용자가 todo의 소유자가 아닌 경우 400 에러를 반환한다.

    • 마지막으로, 검색한 todo의 isDone 속성을 반전시켜서 업데이트하고, 업데이트된 todo를 반환한다.



  • 투두 삭제. (router.delete 메소드를 사용하여 /todos/:id 경로에 대한 DELETE 요청을 처리)

    • 해당 요청은 클라이언트로부터 id와 userId라는 두 가지 파라미터를 받는데 id는 삭제하려는 todo고유 식별자이고, userId는 요청을 보내는 사용자의 고유 식별자다.(헷갈리기 쉬움!! 잘 구별하기!)

    • id를 사용하여 데이터베이스에서 해당 id를 가진 할 일을 검색해야하는데 client.todo.findUnique 메소드를 사용하여 진행한다.

    • 검색된 Todo가 존재하지 않는 경우, 404 Not Found 오류와 함께 "Not exist todo." 메시지를 반환한다.

    • 검색된 Todo가 존재하지만, 요청을 보내는 사용자가 해당 Todo의 소유자가 아닌 경우, 403 Forbidden 오류와 함께 "U R not todo owner." 메시지를 반환한다.

    • 요청이 유효한 경우, client.todo.delete 메소드를 사용하여 데이터베이스에서 해당 Todo를 삭제한다.



✅Frontend 코드리뷰.

프론트엔드는 기존에 만들어져있던 코드가 일부 있기때문에 그 부분을 제외한 추가된 부분에 대해서만 코드리뷰를 남기도록 하겠다.

<App.jsx>

 if (!user) {
    return <LogIn />;
  }
  • if문도 return을 쓸수있다. 이렇게 코드를 작성하면 로그인을 해야 아래 코드로 넘어갈 수 있는 길을 만드는것과 같다.

    • 아래 이전시간에 만들었던 Todolist 코드가 적혀있는데 위에 작성된 코드를 기존에 만들었던 Todolist 코드위에 작성하면 로그인 문제를 해결해야만 해당 페이지를 볼 수 있도록 설정할 수 있다.



<LogIn.jsx>

import { useState } from "react";
import axios from "axios";

const LogIn = () => {
  const [createAccount, setCreateAccount] = useState("");

  const onSubmitCreateUser = async (e) => {
    try {
      e.preventDefault();

      const response = await axios.post(

`${process.env.REACT_APP_BACKEND_URL}/user`,
        {
          account: createAccount,
        }
      );

      console.log(response);
    } catch (error) {
      console.error(error);
    }
  };
  • import를 통한 연결.

    • useState , axios를 import를 사용하여 연결해준다.



  • 로그인 페이지 만들기.

    • 함수형 컴포넌트인 LogIn 내부에 useState를 사용하여 createAccount라는 새로운 상태를 생성한다. 그리고 로그인 form에서 입력한 계정 정보를 저장한다.

    • onSubmitCreateUser 함수는 React의 이벤트로, e.preventDefault() 함수를 사용하여 기본 form 제출 동작을 중지시키고, axios를 사용하여 서버에 HTTP POST 요청을 보낸다.

    • 요청의 URL은 process.env.REACT_APP_BACKEND_URL 환경 변수를 통해 설정되고, 이 변수는 React 애플리케이션에서 .env 파일을 통해 설정된다.

    • axios.post 메서드의 두 번째 인수로 객체를 전달하며, 이 객체는 요청 바디에 들어간다. 여기서는 createAccount 변수에 저장된 값을 account 필드로 가지는 객체를 전달한다.

    • 서버로부터 응답이 도착하면 console.log 함수를 사용하여 응답 객체를 콘솔에 출력한다. 만약 에러가 발생하면 에러 객체를 콘솔에 출력한다.



<LogIn.jsx>

  return (
    <div className="min-h-screen flex flex-col justify-center items-center">
      <form className="flex mt-2 my-16" onSubmit={onSubmitCreateUser}>
        <input
          className="grow border-2 border-pink-200 rounded-lg focus:outline-pink-400 px-2 py-1 text-lg"
          type="text"
          value={createAccount}
          onChange={(e) => setCreateAccount(e.target.value)}
        />
        <input
          className="ml-4 px-2 py-1 bg-pink-400 rounded-lg text-gray-50 w-24"
          type="submit"
          value="계정 생성"
        />
      </form>
    </div>
  );
};
  • 로그인 페이지 만들기(return 내.)

    • 해당코드는 사용자가 계정을 생성할 수 있는 form을 렌더링하는 컴포넌트인 LogIn을 정의하는 코드.

    • 이 컴포넌트는 createAccount라는 state를 가지고 있으며, 사용자가 입력한 값을 저장한다.

    • form 요소에는 onSubmitCreateUser 함수가 전달되어 있다.

    • 이 함수는 form 요소가 제출될 때 실행되고, 함수 내에서는 axios.post 메서드를 사용하여 API 서버에 POST 요청을 보낸다.

    • 그리고 사용자가 입력한 createAccount 값을 전달한다. 전달이되면 서버로부터 응답을 받고 콘솔에 출력한다.

    • 컴포넌트는 계정 생성 버튼을 누르면 onSubmitCreateUser 함수가 실행되고, API 서버에 사용자 계정 정보를 보내어 계정을 생성하도록 한다.



🌜하루를 마치며..

오늘은 드디어 기다리던 금요일이다. 원래 주말을 기다리지 않았었는데 이제는 부족했던 부분을 좀 정리하고 한 주를 맞이하는게 굉장히 중요해졌기 때문에 주말이 꼭 필요하다! 오늘은 회고팀이 있었고, 강사님 상담까지 생겨서 2시간의 회고시간이 생겼다. 그래서 수업내용도 조금은? 짧았던것도 있고, 덕분에 주용님이랑 단둘이 이런저런 이야기도 했다. 이번주를 마무리하며 내가 느꼈던 부분은 똑같이 이해않가는 부분이 많고 어렵지만 그럼에도 내가 이 어려움이 익숙해졌다는게 신기하게 느껴졌다. 전에는 뭔가 힘들고 답답한 느낌이였는데 이제는 그게 뭔가 당연한 느낌이랄까.. 이게 좋은건지 아닌지 모르겠지만 그럼에도 부족한 부분을 인지하고 계속 채워나가는 노력을 잊지는 말아야 한다. 마음의 여유는 조금은 갖되 긴장을 풀지않고 꾸준함을 유지하는게 중요하다. 내가 가장 잘하는것이고 내가 성장하기 위해 가장 필요한 나의 무기이기도 하니 이 부분만큼은 항상 머릿속에 신경쓰고 유지해야한다는 생각을 가지고 있어야 한다. 주말에는 할게 너무 많아서 뭐부터 해야할까 고민이 많았는데 이화매니저님이 좋은 자료를 주셔서 그걸 공부해보려고 한다. 그럼 오늘도 고생했으니 이만 잠자리로~~

profile
개발자가 되어가는 개린이"

2개의 댓글

comment-user-thumbnail
2023년 4월 15일

한결같이 꾸준한 사나이 디오님~ 오늘은 어떤 벨로그가 나올까 기대 중 ㅋㅋㅋㅋㅋ

1개의 답글