[Django] - Fetch API

KimJiHong·2023년 10월 24일
1

Django and API

목록 보기
6/9
post-thumbnail

장고에서 REST API 또는 GraphQL 등 API 통신 기술을 사용할 때, 브라우저 단에서는 보낸 request 에 대한 response 을 받기 위해서는 Fetch API 또는 다른 유사 기술(Ajax 등) 을 사용해야 한다.

먼저 Fetch API 를 알아가기 전
Ajax 가 무엇인지, 왜 Fetch API 를 사용하는지 알아보자.

Ajax (Asynchronous JavaScript And XML)

Ajax는 단어 뜻을 통해 유추할 수 있듯 JS와 XML을 사용하여 비동기 작업을 하는 것이다.

좀 더 자세히 말해보면, JavaScriptXMLHttpRequest 객체 통해서 서버와 비동기적(때로는 동기적) 으로 API 통신하여 서버로 부터 받은 데이터를 DOM 조작을 통해 웹 브라우저가 페이지를 재랜더링(refresh) 없이 동적으로 원하는 부분의 내용에 변화를 줄 수 있는 기술이다.

즉, JS를 사용해서 브라우저가 페이지를 새로고침 하지 않고 현재 페이지의 데이터에 변화를 주는 것이다.

최근에는 Ajax 보다는 Fetch API 또는 axios 를 사용하여 비동기 통신을 구현하는 추세이다.

이 게시글에서 Fetch API 를 다루기전에 먼저 XMLHttpRequest 와 어떻게 다른지 알아보자.

XMLHttpRequest & Fetch API

그렇다면 Ajax 에서 사용되는 XMLHttpReqeust 와 fetch API의 차이점을 알아보자.

XMLHttpRequest & Fetch API

  • XMLHttpRequest (Ajax)
    - 콜백 (CallBack) 기반
    - XML, 텍스트 데이터 형식

  • Fetch API
    • Promise 기반
    • JSON, ArrayBuffer 등의 데이터 형식

CallBack?   Promise?

XMLHttpRequest 에서 사용되는 콜백(CallBack)과 fetch API에서 사용되는 Promise는 같은 비동기(Asynchronous) 처리를 위해 사용되는 방법이다.

아래의 사진을 봐보자.

출처: https://hellocode.dev/callback-vs-promises-javascript/

사진을 보면
CallBack 함수는 비동기 전송 후 작업이 완료 or 오류가 발생하면 호출되는 함수이고

Promise는 비동기 작업을 하나의 객체로 호출하여 비동기 작업에 대한 결과값(대기, 완료, 거부)을 Promise 객체에 저장된다.

CallBack 함수로 비동기 작업을 처리한다면 콜백 함수 내에서 처리를 해야하지만, Promise 객체를 통해 비동기 작업을 수행한다면 결과값이 객체 내에 저장되어 .then 함수를 통해 작업을 처리하거나 .catch 함수를 통해 오류를 처리할 수 있다.

즉, Callback 함수는 비동기 작업을 수행해야만 해당 결과에 대한 로직을 수행할 수 있고,
Promise는 .then 함수를 사용하여 원할때 객체에 저장된 결과에 대한 로직을 수행할 수 있다.


DOM (Document Object Model)

DOM은 HTML 이나 XML 형태의 웹 페이지 구조를 트리 구조로 표현해 각 요소들의 속성과 Context 내용을 JavaScript로 제어할 수 있도록 도와준다.

말로만 들으면 헷갈리니, 직접 간단한 DOM 제어 코드를 작성해보자

<body>
  <div class="test">실험용임</div>
  <script>
    // DOM
    var testClass = document.querySelector('.test');
    testClass.textContent = "바뀐거임";
  </script>
</body>

위 코드를 보면
JavaScript 에서 class 이름이 test인 요소를 testClass 라는 변수로 정의하고
textContent 즉, 해당 요소의 텍스트 내용을 "실험용임" 에서 "바뀐거임" 으로 바꿔준다.

Ajax 와 Fetch API 는 이러한 DOM 조작을 통해 서버로 부터 데이터를 받아 기존 요소들에 동적인 변화를 줄 수 있는 것이다.


Using Fetch API in Django

최근에는 콜백 함수의 문제점으로 비춰지는 콜백 지옥(CallBackHell) 문제도 있고, CORS(Cross-Origin Resource Sharing) 정책 처리등의 문제로 XMLHttpRequest 대신 Fetch apiaxios 를 사용하여 비동기 시스템을 구현하는 경향이 있다.

이번 게시글에서는 fetch API를 사용해 장고 서버의 REST API 와 비동기적으로 동작하는 시스템을 구현 해보자!

장고에서는 REST API를 사용하기 위해, Django REST Framework를 사용하였다.

HTML code

최근에 졸업작품으로 장고를 사용하여 아두이노 도어락 서버를 구현중인데,
로그인 페이지에서 페이지 전환 없이 사용자 인증 로직을 구현하기 위해서 Fetch API를 사용했다.

먼저 미리 만들어둔 HTML 소스에서 로그인 폼 부분만 가져왔다.

<!-- users/login.html -->

<!DOCTYPE html>

<head>
    <title>KimJiHong</title>
</head>
<body>
  <!-- 로그인 부분만 가져왔습니당 -->
  <div class="form-container sign-in-container">
    <form action="" method="post" class="toggle">
    {% csrf_token %}
      <h1>Sign in</h1>
      <span id="error-msg-in">insert your username & password</span>
      <input type="text" placeholder="Username" name="username" value="{% if username %}{{ username }}{% endif %}"/>
      <input type="password" placeholder="Password" name="password"/>
      <a href="javascript:void(0);" onclick="showForgotForm()">Forgot your password?</a>
      <button name="account" value="SignIn">Sign In</button>
    </form>
  </div>
</body>

Browser - POST to server

이제 Fetch API 를 구현하기 위한 JavaScript 와 장고 REST 프레임워크와 Authentication 처리를 위한 urls.py 와 views.py 를 구현해보자.

// JavaScript in login.html

const signInForm = document.querySelectorAll('.sign-in-container form');
const passwordInput = document.querySelectorAll('input[name="password"]');
const errorMessagein = document.getElementById('error-msg-in');

signInForm[0].addEventListener('submit', function (e) {
  // Form은 기본적으로 summit 시에 href를 통해서 페이지를
  // 이동하기 때문에 Ajax로 비동기적인 웹 브라우저를 만들기 위해
  // 페이지를 이동하지 못하게 막기 위해서 사용!
  e.preventDefault();
  
  const formData = new FormData(this);
  fetch('/api/v1/auth/login', {
    method: 'POST',
    body: formData,
  })
  
  	// ... 결과 처리 로직 ...
  
});

check!!!

다만, 여기서 중요한 부분은

HTML의 로그인 폼인 signInForm 을 눌렀을 때

페이지 이동없이 비동기적으로 로그인에 대한 결과를 처리하기 위해선

JS의 이벤트리스너 함수중 preventDefault() 를 통해서 Form 제출시 페이지를 이동하지 못하게 막아줘야한다.

만약 페이지 이동을 막아주지 않게 된다면, Form 제출시 페이지가 이동되는 문제가 생기게 된다.

그 다음, FormData 객체를 사용해서 폼 데이터의 정보를 가져오고
폼 데이터를 fetch 를 통해서 REST API 의 '/api/v1/auth/login' End-point 에 POST 요청을 보낸다.

Django - receive form Browser form (urls.py & views.py)

장고 REST 프레임워크를 사용해 '/api/v1/auth/login' End-Point 의 POST 메소드에 대한 처리 로직과

인증 시스템 구현을 위해서, URL(URI) 매핑과 View 를 구현하였다.

# users/urls.py

from django.urls import path
from .views import LoginAPIView

urlpatterns = [
    # ...
    path('api/v1/auth/login', LoginAPIView.as_view(), name='api-login'),
    # ...
]
# users/views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny
from django.contrib.auth import authenticate, login

class LoginAPIView(APIView):
    permission_classes = [AllowAny]
    
    # ... 다른 HTTP Method 로직 ...
    # ... 여기서는 POST 만 작성했습니다! ...

    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')

        # 장고 내장 authenticate 함수를 통해서 사용자 인증
        user = authenticate(request, username=username, password=password)

        if user is not None:
            # 로그인 성공
            login(request, user)
            return Response({'success': True}, status=status.HTTP_200_OK)
        else:
            # 로그인 실패
            return Response({'success': False, 'error': 'login failed'}, status=status.HTTP_401_UNAUTHORIZED)

브라우저에서 POST 메소드를 통해 전달받은 ID 와 PW 를 authenticate 함수로 해당 사용자의 유효성을 검사하고

인증 결과를 HTTP status 및 success 로 응답하도록 구현했다.

Browser - receive result

// JavaScript in login.html

signInForm[0].addEventListener('submit', function (e) {
  
  // ...
  
  fetch('/api/v1/auth/login', {
    method: 'POST',
    body: formData,
  })
  	// response 받은 결과값을 then 함수로 접근
    .then(response => response.json())
    .then(data => {
    // 서버에서 받은 결과(data)를 처리
    if (data.success) {
      // 성공 처리
      window.location.href = '/chat/connect/';
    } else {
      // 로그인 실패 처리
      errorMessagein.textContent = 'login failed';
      errorMessagein.style.color = 'red';
      passwordInput[1].value = "";
    }
  })
  	// 오류 발생시 catch 함수로 접근
    .catch(error => {
    // 오류 처리
    console.error(error);
  });
});

브라우저에서는 .then.catch 함수를 통해서 서버로 부터 받은 데이터에 대한 로직을 수행 하였다.

then 함수로 장고에서 보내온 success 의 값이 true 일 경우 '/chat/connect/' 로 이동
success 의 값이 false 일 경우에는 DOM 제어를 사용하여 'login failed' 라는 오류 문구를 사용자에게 보여주게 했다.

또한, catch 함수로 오류 발생 시 콘솔창에 error 값을 띄우게 구성함으로써 로직을 완성하였다.

Test Videos




callback promise 사진 출처 : https://hellocode.dev/callback-vs-promises-javascript/
profile
https://h0ng.dev

0개의 댓글