서버 비동기 통신 Axios 프로젝트 도입

김건우·2023년 1월 7일
1

비동기 Axios

목록 보기
1/3
post-thumbnail

JPA와 Axios를 이용한 좋아요 기능 구현

RestApi를 통한 좋아요를 Jpa를 사용해서 구현 하였다. 우선 구글링을 통해서 RestApi까지는 많이 나오지만 프론트 서버와의 비동기 통신에 대한 정보는 Ajax가 상당히 많이 나왔다. 반면 Axios 통신 방법은 거의 없다고 보아도 무방했다(좋아요 기능 구현에서..) 그래서 Spring에서의 엔티티 생성과 서비스 컨트롤러 부터 차근차근 설명할 것이다.

LiKE Entity


@Builder
@Getter
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "\"likes\"")
@SQLDelete(sql = "UPDATE \"likes\" SET deleted = true WHERE like_id = ?")
@Where(clause = "deleted = false")
public class LikeEntity extends BaseEntity{

    @Id
    @GeneratedValue
    @Column(name = "like_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne
    @JoinColumn(name = "post_id")
    private Post post;

    private Integer count;

    /**SoftDeleteColumn**/
    @Column(name = "deleted")
    private boolean deleted = Boolean.FALSE;

    //연관관계 편의 메서드
    //1. user
    //2. post의 값을 like 엔티티에 넣어준다.
    public static LikeEntity of(User user, Post post) {
        LikeEntity like = new LikeEntity();
        like.setUser(user);
        like.setPost(post);
        return like;
    }
}

위의 엔티티를 설명하자면 Soft Delete를 채택하여 엔티티위에 delete() 기능이 나갈 때 update구문을 설정해주어서 실제 DB에서는 지워지지 않는 방법을 채택하였다. 그리고 @where 에노테이션을 이용하여 조회시에 deleted 컬럼이 false인것만 조회되게 하였다.

Like controller


    @ResponseBody
    @PostMapping("/api/v1/posts/mvc/likes")
    public Response<LikeResponse> likeMvc(@RequestBody LikeRequest request, @SessionAttribute(name = "loginMember", required = false) User loginMember, HttpServletResponse response) throws Exception {
      
        //로그인 하지 않은 사용자 접근 시 404에러 
        if (loginMember == null) {
            throw new UserException(ErrorCode.USERNAME_NOT_FOUND);
        }
        LikeResponse likeResponse = postService.likes(request.getPostId(), loginMember.getUsername());
        return Response.success(likeResponse);
    }

위의 컨트롤러는 우선 httpBody를 통해 들어오는 정보를 @RequestBody를 통해 요청을 받는다.LikeRequest의 컬럼에는 @sessionAttribute 를 통해 해당 로그인 유/무를 확인하게 만들었다.

  • 컨트롤러에 들어오는 @RequestBody DTO
@AllArgsConstructor @NoArgsConstructor
@Data
public class LikeRequest {
    private Long postId;
}

Like Service

 
    public LikeResponse likes(Long postId,String userName) {
        //해당 글 찾음
        Post post = postRepository.findById(postId)
                .orElseThrow(() -> new PostException(ErrorCode.POST_NOT_FOUND, "해당 글은 존재하지 않습니다"));
        //해당 유저 찾음
        User user = userRepository.findOptionalByUserName(userName)
                .orElseThrow(() -> new UserException(ErrorCode.USERNAME_NOT_FOUND, String.format("%s님은 존재하지 않습니다.", userName)));

        //like 눌렀는지 확인 비지니스 로직🔽
        //ifPresent() 메소드 = 값을 가지고 있는지 확인 후 예외처리 / 값이 존재한다면 예외처리 진행
        likeEntityRepository.findByUserAndPost(user,post)
                .ifPresent(entity -> {
                    log.info("에러 터져야함");
                    throw new LikeException(ErrorCode.ALREADY_LIKED, ErrorCode.ALREADY_LIKED.getMessage());
                });
        ////like 눌렀는지 확인 비지니스 로직 끝

        LikeEntity like = LikeEntity.of(user, post);
        LikeEntity savedLike = likeEntityRepository.save(like);
        
        /**해당 글 좋아요 갯수도 구하기**/
        /**getLikeCount() 메서드는 postId를 통해 LIKE갯수를 구하는 메서드**/
        Integer count = getLikeCount(postId);

        LikeResponse likeResponse = LikeResponse.of(savedLike,count);
      
        return likeResponse;
    }

위의 서비스단의 비지니스 로직을 설명하자면 우선 다음과 같은 검증 로직이 있다.

  1. post가 존재하는지 , 없으면 404에러
  2. user가 존재하는 user인지 , 없으면 404에러
  3. 해당 user가 like를 이미 눌렀는지 확인, 두번 눌렀다면 403에러 ifPresent() 사용

    ifPresent() 메소드 = 값을 가지고 있는지 확인 후 예외처리 / 값이 존재한다면 예외처리 진행

item service단의 코드는 위의 3가지 검증로직을 통과 후에 LikeResponse의 응답 객체를 통해 반환을 한다.

LikeResponse 응답객체와 변환 메서드

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LikeResponse {
    private Long likeId;
    private Long postId;
    private String  userName;
    private String message;
    private Integer count;// 좋아요 갯수


    public static LikeResponse of(LikeEntity likeEntity) {

        return LikeResponse.builder()
                .likeId(likeEntity.getId())
                .postId(likeEntity.getPost().getId())
                .userName(likeEntity.getUser().getUsername())
                .message("좋아요를 눌렀습니다")
                .build();
    }
   /**해당 포스트(postId)의 좋아요 count가 추가된 변환 메서드**/
    public static LikeResponse of(LikeEntity likeEntity,Integer count) {
        return LikeResponse.builder()
                .likeId(likeEntity.getId())
                .postId(likeEntity.getPost().getId())
                .userName(likeEntity.getUser().getUsername())
                .count(count)
                .message("좋아요를 눌렀습니다")
                .build();
    }

}

여기 까지의 코드를 본다면 @PostMapping("/api/v1/posts/mvc/likes")의 요청이 들어 왔을 시 다음과 같은 응답 값을 기대할 수 있다.🔽
(참고로 컨트롤러에서 응답은 Response 제네릭 타입을 이용하여 감싸주었다.)

  • JSON 성공 응답값 ✅
    {
      "resultCode": "SUCCESS",
      "result": "좋아요 성공"
                             }
  • 이미 좋아요를 눌렀을 때의 응답값 ❌
 {
  "resultCode": "ERROR",
  "result": {
    "errorCode": "ALREADY_LIKED",
    "message": "이미 좋아요를 눌렀습니다."
  }
}

서비스 로직의 검증 과정을 통해 만약 해당 회원이 postId에 해당하는 포스트에 좋아요를 또 눌렀다면
ErrorCode.ALREADY_LIKED 404에러가 터지게 구현하였다. 위의 ALREADY_LIKED 에러 내용은 프론트단에서 알맞은 응답을 처리하기 위한 값이다.
이제는 백엔드에서의 기능은 다 끝났다. 다음부터는 Axios를 이용한 비동기 처리를 구현할 차례이다.

Axios를 이용한 비동기 처리

Axios란?

Axios 라이브러리
Axios는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리 아다. 쉽게 말해서 백엔드랑 프론트엔드랑 통신을 쉽게하기 위해 Ajax와 더불어 사용한다. 이미 자바스크립트에는 fetch api가 있지만, 프레임워크에서 ajax를 구현할땐 axios를 쓰는 편이라고 보면 된다.

axios 라이브러리 설치하기

  • jsDeliver / unpkg CDN 둘중 택
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

위의 2개 중 하나를 골라서 HTML에 붙여주면 된다.

Axios 문법 구성

axios({
  url: 'https://test/api/cafe/list/today', // 통신할 웹문서
  method: 'get', // 통신할 방식
  data: { // 인자로 보낼 데이터
    foo: 'diary'
  }
});
  • method : 요청방식. (get이 디폴트)
  • url : 서버 주소
  • headers : 요청 헤더
  • data : 요청 방식이 'PUT', 'POST', 'PATCH' 해당하는 경우 body에 보내는 데이터
  • params : URL 파라미터 ( ?key=value 로 요청하는 url get방식을 객체로 표현한 것)
  • responseType : 서버가 응답해주는 데이터의 타입 지정 (arraybuffer, documetn, json, text, stream, blob)

Like 좋아요 (JavaScript)

우선 본격적으로 Axios 통신을 과정을 설명하기전에 백엔드 서버로 요청을 보내기 위한 HTML 코드는 다음과 같다.🔽

 <div>
    <form  style="margin-left: 195px;" onsubmit="main.plusLike(this); return false;" >
         <input type="hidden" placeholder="작성자" name="postId" th:value="${post.postId}">
              <button type="submit" class="btn-hover color-8 btn btn-outline-secondary btn-lg" id="like-btn"
                      style="font-size: 20px">
              <div class="fill-one" th:text="'👍  ' + ${likeCount}"></div>
              </button>
     </form>
 </div>

${post.postId}는 컨트롤러에서 요청을 받을 해당 포스트의 id이고 ${likeCount}는 해당 포스트의 좋아요 count이다.

    <form  style="margin-left: 195px;" onsubmit="main.plusLike(this); return false;" >

를 보면 알 수 있듯이 form 태그안에 button을 클릭하면 plusLike() 메서드가 실행되는 것을 볼 수 있다. plusLike(this); 에서의 this는 form태그 객체를 의미한다.

  • Axios를 이용한 Java Script 코드
<script>
  let main = {

        init: function () {
        },

        plusLike : function (form) {
            const data = form.postId.value;
            form.postId.value= form.postId.value.trim();
            const likeCount = document.getElementById("likeCount");

            axios.post(
                "/api/v1/posts/mvc/likes",
                data,
                {
                    headers: {
                        'Content-Type': 'application/json'}
                }
            ).then((response) => {
                console.log("좋아요 성공");
                console.log(response);
                console.log(response.status);
                console.log(response.data);
                console.log(response.data.resultCode);
                console.log(response.data.result.count);
                console.log(response.data.result.userName);
                console.log(response.data.result.likeId);
                console.log(response.data.result.postId);
                //좋아요
                likeCount.innerHTML = '👍  ' + response.data.result.count;
                }
            ).catch((error) => {
                console.log(error.response.data.result);
                console.log(error.response);
                console.log(error.toJSON());
                if (error.response.data.result["errorCode"] == "ALREADY_LIKED") {
                    alert("이미 해당 글에 좋아요👍를 누르셨습니다");
                    window.location.href = '/post/getOne/'+form.postId.value;
                } else if (error.response.data.result["errorCode"] == "USERNAME_NOT_FOUND") {
                    alert("로그인 후에 이용해주세요!");
                    window.location.href = '/members/loginForm';
                }
            });
        }
    };
    main.init();
 </script>

url은 @PostMaapint("/api/v1/posts/mvc/likes") 으로 요청을 보내는 것을 알 수 있다. 그리고 const data = form.postId.value; 의 data를 요청 값으로 전달 하는 것을 알 수있는데 form태그의 name속성이 postId인값의 value를 즉, 해당 post의 id 값을 요청값으로 전송하는 것이다.

then(response) 응답에 성공 했을 때

위의 자바 스크립트 코드를 보면 chaing method인 then(response)를 통해 응답값을 받아 온 것을 알 수 있다. 응답에 성공한다면 다음과 같은 성공 응답을 받을 수 있다. then(response) 안에 메서드를 보고 응답값을 봐보자.🔽


위의 응답값은 요청은 user는 김건우 postId는 11번의 글을 좋아요를 눌렀을 때의 응답 성공 값이다. message: '좋아요를 눌렀습니다', count: 1 를 볼 수 있는데 현재 해당 글의 count 누적 값은 1개 인 것이다.

성공 시 view 단 비동기 처리
좋아요 이전✔
좋아요 이후✔ (페이지 비동기 처리)

성공 시에는 페이지가 전환되지 않고 좋아요가 1개 증가 한것을 볼 수 있다.

  likeCount.innerHTML = '👍  ' + response.data.result.count;

위와 같은 성공시 응답을 넣어주었기 때문인데 페이지에 특정부분만 응답성공의 값을 넣는 것이다.

catch((error) => {에러 발생시 로직}

만약 에러가 발생 시에는 위와 같은 성공 로직이 나오면 안되는 것이다. catch() 도 chaing method중 하나인데 위에서 좋아료를 2번 누를 시에 ALREADY_LIKED 403 에러를 발생 시킨것을 기억하면 좋을 것이다.

.catch(<(error) => {
                console.log(error.response.data.result);
                console.log(error.response);
                console.log(error.toJSON());
                if (error.response.data.result["errorCode"] == "ALREADY_LIKED") {
                    alert("이미 해당 글에 좋아요👍를 누르셨습니다");
                    window.location.href = '/post/getOne/'+form.postId.value;
                } else if (error.response.data.result["errorCode"] == "USERNAME_NOT_FOUND") {
                    alert("로그인 후에 이용해주세요!");
                    window.location.href = '/members/loginForm';
                }
            });

에러 발생시의 로직이다. 우선 console.log를 이용하여 에러가 터졌을 시 로그를 확인해보자🔽
위와 같이 403에러와 ALREADY_LIKED 가 응답값으로 나온 것을 볼 수 있다. 이때 catch 문안에서

if (error.response.data.result["errorCode"] == "ALREADY_LIKED") {
                    alert("이미 해당 글에 좋아요👍를 누르셨습니다");
                    window.location.href = '/post/getOne/'+form.postId.value;

를 본다면 이제 어느 정도 감이 잡혀야한다. 두 번 눌렀을 시에 ALREADY_LIKED 응답메세지에 해당하는 값을 확인해서 이미 좋아요를 눌렀다는 알림창이 나오는 것을 알수 있다. 🔽

또한 로그인 하지 않은 사용자가 좋아요를 누른다면 "USERNAME_NOT_FOUND"라는 메세지와 404에러가 나오게 해주었는데 그러면

else if (error.response.data.result["errorCode"] == "USERNAME_NOT_FOUND") {
                    alert("로그인 후에 이용해주세요!");
                    window.location.href = '/members/loginForm';
                }

의 코드를 보면 알 듯이 로그인 경고창이 나오고 로그인화면으로 리다이렉트를 보낸 것을 알 수 있다. 🔽

profile
Live the moment for the moment.

0개의 댓글