[Vanilla JS Project (2)] REST API로 백엔드와 통신하기

쥬롬의 코드착즙기·2023년 1월 16일
0
post-thumbnail

REST API의 GET, POST, DELETE를 통해 서버로부터 데이터를 받아와 보자.

0. REST API란?

REST API의 개념을 살펴보자.
먼저 REST는...

어떤 자원에 대해 CRUD(Create, Read, Update, Delete) 연산을 수행하기 위해 URI(Resource)로 GET, POST 등의 방식(Method)을 사용하여 요청을 보내며, 요청을 위한 자원은 특정한 형태(Representation of Resource)로 표현된다.

다음으로 API는...

API는 정의 및 프로토콜 집합을 사용하여 두 소프트웨어 구성 요소가 서로 통신할 수 있게 하는 메커니즘이다. 예를 들어 기상청의 소프트웨어 시스템에는 일일 기상 데이터가 들어 있다. 휴대폰의 날씨 앱은 API를 통해 이 시스템과 대화하여 휴대폰에 매일 최신 날씨 정보를 표시한다.

그렇다면 REST API는...

REST API는 HTTP 요청을 통해 통신하여 데이터 생성, 읽기, 업데이트 및 삭제 기능을 완료하는 API이다.

GET, POST, DELETE 등의 메소드로 통신하는 API를 말한다. REST 스타일을 준수한 것을 RESTful하다고도 표현한다.

1. GET

GET 은 클라이언트에서 서버로 어떠한 리소스로부터 정보를 요청할 때 사용하는 메소드이다. 예시로 게시판의 모든 포스트를 받아오기 위해 사용한 코드이다.

    async getList(){
        const res = await fetch(BASE_URL+"/posts");
        const data = await res.json();
        const list = data.data.posts;
        
        return list;
    }

    async getHtml(){
        const list = await this.getList();
        return (
        `<div id="List">
            <button id="ListAddBtn" data-link="/edit">새 글 작성하기</button>
            ${list.map((elm, key)=>(
                  `<div id="ListItem" data-link="/show/${elm.postId}">
                    <div id="ListItemImage" data-link="/show/${elm.postId}">
                        <image src=${elm.image} loading=${key>3?"lazy":"eager"}></image>
                    </div>
                    <div id="ListItemTitle" data-link="/show/${elm.postId}">${elm.title}</div>
                    <div id="ListItemContent" data-link="/show/${elm.postId}">${elm.content}</div>
                </div>`
            )).join('')}
        </div>`
        );
    }

2. POST

POST는 클라이언트에서 서버로 리소스를 생성하거나 업데이트하기 위해 데이터를 보낼 때 사용하는 메소드다. 예시로 포스트를 작성하고 난 뒤에 게시판에 올릴 때 사용한 코드이다.

    async addPost(){
        const title = document.getElementById("EditTitle").getElementsByTagName("input")[0].value;
        const content = document.getElementById("EditContent").getElementsByTagName("textarea")[0].value;
        const image = document.getElementById("EditImage").getElementsByTagName("img")[0].src;

        const res = await fetch(BASE_URL+"/post", {
            method: 'POST',
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
                "title": title,
                "content": content,
                "image": image,
            }),
        })
        const data = await res.json();
    }

3. DELETE

DELETE는 클라이언트에서 서버의 데이터를 삭제할 때 사용하는 메소드다. 포스트를 삭제할 때 사용한 코드이다.

    async deletePost(){
        const res = await fetch(BASE_URL+"/post/"+POSTID, {
            method: 'DELETE',
        })
        const data = await res.json();
    }

4. PATCH

PATCH는 클라이언트에서 서버의 데이터를 수정할 때 사용하는 메소드다. 포스트를 수정할 때 사용한 코드이다.

    async editPost(){
        const editTitle = document.getElementById("EditTitle").getElementsByTagName("input")[0].value;
        const editContent = document.getElementById("EditContent").getElementsByTagName("textarea")[0].value;
       
        const res = await fetch(BASE_URL+"/post/"+POSTID, {
            method: 'PATCH',
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
                "title": editTitle,
                "content": editContent,
            }),
        })
     

5. 배운 점

1. class 전역변수 사용법

React에서는 setstate로 해버리면 되는 걸 바닐라js로 하니까 좀더 생각해야 해서 어려웠다.

전역변수가 상수값

전역변수가 늘 정해져 있는 상수값인 경우는 문제가 없다. class 바깥쪽에서 const로 선언해버리면 된다. 예컨대 서버 url같은 것은 const로 했다. 아래 코드에서 UNSPLASH_URLBASE_URL은 각각 서버 API 주소이다. 서버 주소는 절대 변하지 않는 상수값이다. 그래서 class 선언부 바깥쪽에 const로 선언해주면 된다.
Edit.js

import AbstractView from "./AbstractView.js";

const UNSPLASH_URL = "https://api.unsplash.com/photos/random?client_id=eqYyvjr-ziiTM28CnPklAKC2OGO9z-vYnzOycwr5VaE"
const BASE_URL = "http://43.201.103.199";

export default class Edit extends AbstractView{
	//...
    async getImage(){
        const res = await fetch(UNSPLASH_URL);
      	// ...
    }
      async addPost(){
        const res = await fetch(BASE_URL+"/post", {
        //...
//후략

전역변수가 상수값이 아니라면?

전역변수가 바뀔 수 있는 값이라면? let으로 해 두고 함수 실행으로 바꾸면 된다. 그 함수가 리액트 setState의 역할을 하도록 하면 된다.

예를 들자면 아래와 같은 게시판이 있다.

여기서 서로 다른 게시물을 누르면 서로 다른 url을 가지면서 각자 게시물에 맞는 데이터를 보여준다.

예를 들어 localhost:4000/show/123에 접속하면 아래 페이지가,

localhost:4000/show/789에 접속하면 아래 페이지가 뜬다고 가정해보자.

각 게시물의 번호는 url의 두 번째 슬래시 뒤에 있다. 이를 받아와서 전역변수로 저장하면 된다. 저장하는 작업은 url 라우팅 후 페이지 객체가 만들어질 때 실행해야 한다. 즉, 해당 페이지 class의 생성자에 넣어주면 된다.

show.js

import AbstractView from "./AbstractView.js";

//...
let POSTID = null;

export default class Show extends AbstractView{
    
    constructor(){
      	//...
        this.getPostId();
    }

    getPostId(){
        POSTID = location.pathname.split("/")[2]; //123 or 789 ...
    }

해놓고 보니 너무 간단한데 꽤나 헤매다가 알아냈다. class 안에 멤버변수로는 선언 자체가 안 되고. 각 함수마다 postid를 선언해주자니 같은 코드가 반복되는 게 비효율적이고. 뭔가 자기 생각의 틀에 갇히면 깨고 나오기 어려운 것 같다......

2. onclick? addEventListener("click")?

클릭 이벤트를 통해 라우팅을 구현하고 CRUD 요청을 보냈다. 클릭 이벤트를 처리할 때 onclick과 addEventListner("click")을 둘 다 쓸 수 있다. onclick은 onclick is not defined 에러가 자꾸 떠서 열받아서 addEventListner("click")으로 전부 처리했다. 하다 보니 이래도 되나? 하는 생각이 들어 차이점을 찾아보았다.

  1. 브라우저 호환성
    onclick은 모든 브라우저와 버전에서 호환된다.
    반면 addEventListner는 IE 6,7,8에서는 호환되지 않는다.
    호환성에 신경써야 하는 상황이라면 onclick을 써야 한다.

  2. 여러 이벤트 관리
    onclick은 여러 이벤트가 관리되지 못하고 마지막으로 작성된 이벤트가 이전의 모든 이벤트를 덮어쓴다.
    반면 addEventListner는 모든 이벤트가 정상적으로 작동할 수 있다.
    여러 이벤트가 모두 동작해야 하는 상황에서는 addEventListner를 쓴다.

  1. 버블링, 캡처링 설정
    onclick은 버블링, 캡처링 설정을 할 수 없다.
    반면 addEventListner는 세 번째 파라미터를 통해, 중첩된 event가 발생했을 때 버블링으로 할지(true) 캡처링으로 할지(false) 설정할 수 있다.
    (버블링은 bottom-up, 캡처링은 top-down 방식이라고 생각하면 된다.)

종합적으로 보면, addEventListner가 기능적으로 더 나은 선택으로 보인다. 그러나 브라우저 호환성에 주의해야 한다면 onclick을 사용하면 된다.

+) 추가
아니... post를 할 때마다 중복된 포스트라는 오류가 떴다. 그것도 한번 할 때마다 한번씩 더... 이게뭐지? 하고 생각해 보다가 위에서 정리한 내용에서 문제점을 발견할 수 있었다.
addEventListener는 말 그대로 이벤트 리스너를 💥추가💥한다. 그러니까 함수 실행 안에 addEventListener를 넣으면... 함수가 실행될 때마다 이벤트 리스너가 추가된다...!!! ㄷㄷㄷ 이런 사고를 겪고 싶지 않다면 addEventListener는 함수 밖에다 선언해주자.

profile
코드를 짭니다...

0개의 댓글