웹 백엔드

Node.js - 동적 폼 전송

토리쟁이 2024. 1. 29. 20:10

 

이번 포스팅에서는 node.js에서의 동적 form 전송 대해 공부해 보려고 한다.


 

이전 포스팅들에서는 input이나 button의 type="submit"을  이용해 폼을 전송해왔다.

이러한 방식으로 데이터를 전송했을 땐, 페이지의 이동이 발생했다.

 

하지만, 로그인을 실패한 경우와 같이 굳이 페이지의 이동이 필요가 없는 상황에서는 페이지의 이동없이 서버를 통해 데이터를 주고 받는 것이 적절하다. 이러한 방식을 비동기 방식이라고 하는데, HTTP 통신에서의 동기/비동기 방식에 대해 알아보자.

 

 

 

동기 HTTP 통신

  • 한 번에 하나만 처리 → 페이지를 아예 이동해 서버가 데이터를 처리하는 방식

 

 

 

비동기 HTTP 통신

  • 서버에 데이터를 보내고 응답을 기다리는 동안 다른 처리가 가능
  • form의 데이터를 서버와 dynamic하게 송수신하는 방식
  • dynamic: 웹 문서가 정적으로 멈춰있는 것이 아니라, 일부 내용이 실시간으로 변경되는 것
  • 방법: ajax, axios, fetch

 

 

비동기 HTTP 통신 방법에는 ajax, axios, fetch 이렇게 3가지가 있다.

각 방법에 대해 알아보자.

 

 

 

 

 

 

 

 

 

 

1. Ajax(Asynchronous JavaScript And XML)

  • 자바스크립트를 이용해 클라이언트와 서버 간에 데이터를 주고 받는 비동기 HTTP 통신
  • 장점
    • Jquery를 통해 쉽게 구현 가능
    • Error, Success, Complete의 상태를 통해 실행 흐름 조절 가능
    • 변경할 부분만 갱신 → 불필요한 데이터 통신 발생X
    • 전체 리렌더링 필요X → 깜빡임 현상 발생X
    • 비동기 통신 → 서버 요청 후 블로킹 발생X
  • 단점
    • Jquery를 사용해야만 간편하고 호환성이 보장됨(XML사용은 복잡함)
      • XML
      • eXtensible Markup Language
      • HTML과 비슷한 마크업 언어
      • HTML과 달리 미리 정의된 태그 없음
      • 대신, 사용자가 필요에 따라 태그를 직접 정의해 사용 가능
    • Promise 기반이 아님

 

 

실습 코드를 살펴보자.

뷰 코드이다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>동적폼 전송</title>
    <style>
      body {
        background-color: aliceblue;
      }
      .result {
        font-size: 1.5rem;
        font-weight: 700;
      }
    </style>
    <!--jQuery CDN ajax 사용-->
    <script
      src="https://code.jquery.com/jquery-3.7.1.min.js"
      integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
      crossorigin="anonymous"
    ></script>
  </head>
  <body>
    <h1>동적폼 연습하기</h1>
    <!--이름과 성별을 입력받는 폼(제출은 안할거라 method 지정 필요x)-->
    <form name="register">
      <input
        type="text"
        name="name"
        required
        placeholder="이름을 입력해주세요"
      />
      <br />
      성별
      <input type="radio" name="gender" id="male" value="남자" required />
      <label for="male">남성</label>
      <input type="radio" name="gender" id="female" value="여자" required />
      <label for="female">여성</label>
      <br />
      <br />

      <button type="button" onclick="ajaxGet()">Ajax GET 제출</button>
      <button type="button" onclick="ajaxPost()">Ajax Post 제출</button>
      <br />
      <button type="button" onclick="axiosGet()">Axios GET 제출</button>
      <button type="button" onclick="axiosPost()">Axios Post 제출</button>
      <br />
      <button type="button" onclick="fetchGet()">Fetch GET 제출</button>
      <button type="button" onclick="fetchPost()">Fetch Post 제출</button>
      <br />
      <a href="/practice1">실습문제1</a>
      <a href="/practice2">실습문제2</a>

      <!--동적폼 전송 결과를 보여줄 곳-->
      <div class="result"></div>
  </body>
</html>

 

위 코드에 대한 실제 웹 화면은 아래 사진과 같다.

 

 

 

 

 

 

화면에서 이름과 성별을 입력 후, 입력받은 데이터를 Ajax get/post 방식으로 통신해보자.

 

<script>
      const resultBox = document.querySelector(".result");
      
      // ajax get 통신 함수
      function ajaxGet() {
        const form = document.forms["register"]; // 전체 form요소들 중에서 name이 register인 요소
        
        // 변수 data에 입력받은 데이터 넣기
        const data = {
          name: form.name.value, // name="name"인 요소의 value값에 입력 데이터가 들어있음
          gender: form.gender.value, // name="gender"인 요소의 value값에 입력 데이터가 들어있음
        };
	
    	// ajax 통신
        // /ajax 경로에 get 방식으로 위에서 담은 data를 전송할 것
        $.ajax({
          type: "get", // 메소드 지정
          url: "/ajax", // 경로 지정
          data: data, // data를 data에 담기
          success: function (res) {
            // 요청 성공시 실행할 함수
            // res는 요청이 성공했을 때 받아오는 응답임(백에서 send로 보낸 것)
            
            // 받은 응답(res) 데이터를 꺼내어 화면에 띄워줌
            resultBox.textContent = `GET ajax 요청 완료!: ${res.name}님의 성별은 ${res.gender}입니다.`;
            resultBox.style.color = "red";
          },
          error: function (error) {
            // ajax 요청 실패시 실행할 함수
            console.log("ajax 실패");
          },
        });
      }
      
      
       // ajax post 통신 함수
      function ajaxPost() {
        const form = document.forms["register"];
        
        // 변수 data에 입력받은 데이터 넣기
        const data = {
          name: form.name.value,
          gender: form.gender.value,
        };

		// /ajax 경로에 post 방식으로 위에서 담은 data를 전송할 것
        $.ajax({
          type: "post",
          url: "/ajax", // 경로 지정
          data: data, // data를 data에 담기
          success: function (res) {
            // 요청 성공시 실행할 함수
            // res는 요청이 성공했을 때 받아오는 응답임(send로 보낸 것)
            
            // 받은 응답(res) 데이터를 꺼내어 화면에 띄워줌
            resultBox.textContent = `POST ajax 요청 완료!: ${res.name}님의 성별은 ${res.gender}입니다.`;
            resultBox.style.color = "orange";
          },
        });
      }

    </script>

 

 

 

app.js(백) 코드는 다음과 같다.

const express = require("express");
const app = express();
const PORT = 8089;

app.set("view engine", "ejs");
app.set("views", "./views"); // 뷰가 있는 경로

// req.body를 사용하기 위한 body-parser 미들웨어 설정
app.use(express.urlencoded({ extended: true }));
// json 형식을 사용하기 위한 설정
app.use(express.json());

// 루트 경로(제일 처음 진입했을 때)
app.get("/", (req, res) => {
  res.render("index");
});


// ajax 라우팅
// '/ajax' 경로에 get 통신이 들어온다면
app.get("/ajax", (req, res) => {

// get 통신은 데이터가 요청(req)의 쿼리에 들어있음
  res.send(req.query); // 프론트에서 받은 데이터를 응답(response)함
});

app.post("/ajax", (req, res) => {

  // "/ajax"에 post 요청이 들어올 시
  console.log(req.body); // post 방식은 데이터가 req.body에 담겨져 옴
  res.send(req.body); // 프론트에서 받은 데이터를 응답(response)함
});

(app.js에서 윗 줄은 설정 코드이므로, 하단 ajax 관련 함수만 잘 보면 된다.)

 

위의 실습 코드를 리뷰해보자.

데이터 입력 후 버튼을 누르면, 프론트(뷰)에서는 form에 입력된 데이터를 객체에 담아 각각 get/[post 방식으로 백(app.js)으로 전송한다. 백에서는 해당 경로의 요청에 get/post 방식의 요청이 들어오면, 각각 요청(request)의 query/body에 담긴 데이터를 다시 뷰로 응답(response)해주는 로직으로, 화면의 이동없이 클라이언트와 서버 간에 데이터를 주고 받을 수 있게 된다.

 

 

 

 

 

 

 

2. Axios

  • Promise API를 활용하는 HTTP 비동기 통신 라이브러리(promise 기반)
  • return이 Promise 객체로 옴
  • 3가지 방법 중 가장 많이 사용되는 추세
  • 장점
    • Promise 기반
    • 브라우저 호환성(크로스 브라우징)이 뛰어남
    • fetch에 존재하지 않는 기능이 많음(자동 json 데이터 형식 변환, 요청 취소 및 타임아웃 설정 등..)
  • 단점
    • 모듈 설치 or 호출이 해줘야 사용이 가능
      • npm install axios (npm i axios)

 

 

마찬가지로, 실습 화면에서 데이터 입력 후 버튼을 누르면, 서버와 클라이언트가 Axios get/post 방식으로 데이터를 주고 받는 코드를 작성해보자

 

<!--axios CDN ajax 사용-->
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
	// 결과를 뿌려줄 요소 선택
      const resultBox = document.querySelector(".result");
      
      // axios get 방식
      async function axiosGet() {
        const form = document.forms["register"];
		
        // 입력받은 데이터를 변수 data에 담아 저장
        const data = { 
          name: form.name.value,
          gender: form.gender.value,
        };

        // type button의 유효성 검사: checkValidity() -> 자바스크립트에서 지원해줌
        /*
      required 속성이 있으면 기입이 됐는지 안 됐는지 검사
      - required가 있는 항목에 대해서
      - 기입했다면 true, 안 했다면 false 반환
      - required가 없는 항목에 대해서는, 무조건 true를 반환함
      */

        if (!form.name.checkValidity()) { // 입력이 되어 있지 않으면
          resultBox.textContent = form.name.validationMessage; // 기본적으로 제공하는 문자열(이 입력란을 작성하세요)
        } else if (!form.gender[0].checkValidity()) {
          resultBox.textContent = form.gender[0].validationMessage; // 기본적으로 제공하는 문자열(다음 옵션 중 하나를 선택하세요)
        } else {
          // 모든 값을 채웠을 때 axios 요청
          /*axios({
            type: "GET",
            url: "/axios",
            params: data, // axios의 get일 때는 params에 데이터를 넣어 전달
          }).then(function (res) {
            // 구조 분해 할당
            const { name, gender } = res.data;
            // 구조 분해 할당을 통해 res.data.name, res.data.gender 대신 name, gender 로 사용
            resultBox.innerText = `GET axios 요청 완료!: ${name}님의 성별은 ${gender}입니다.`;
            resultBox.style.color = "green";
          });*/
          try {
            const response = await axios({ // 데이터 전송이 완료된 후에 실행
              method: "GET",
              url: `/axios?name=${data.name}&gender=${data.gender}`, // 데이터를 쿼리 스크링에 담아 전달
            });
            resultBox.innerText = `GET axios 요청 완료!: ${data.name}님의 성별은 ${data.gender}입니다.`;
            resultBox.style.color = "green";
          } catch (error) {
            console.log("error!", error);
          }
        }
      }
		
      // axios post 방식  
      async function axiosPost() {
        const form = document.forms["register"];
		
        // 입력받은 데이터를 변수 data에 담아 저장
        const data = {
          name: form.name.value,
          gender: form.gender.value,
        };

        try {
          const response = await axios({ // 데이터 전송이 완료된 후에 실행
            method: "post",
            url: "/axios",
            data: data,
          });
          
		// 구조 분해 할당
          const { name: 이름, gender: 성 } = response.data; // name을 이름으로, gender를 성으로-
          resultBox.innerText = `POST axios 요청 완료!: ${이름}님의 성별은 ${성}입니다.`;
          resultBox.style.color = "blue";
        } catch (err) {
          console.log(err);
        }
      }

      
      
</script>

 

뷰의 script 코드가 좀 긴데, 하나하나 살펴보자.

일단, 중간에 checkValidity() 함수가 있는데, 이 함수는 자바스크립트에서 지원해주는 유효성 검사 함수이다. 이 함수를 사용하면, 해당 요소에 데이터가 입력이 됐는지 안 됐는지에 대해 true/false를 반환한다. 만약 해당 요소에 required 속성이 있을 경우 데이터 입력 여부에 따라 boolean 값을 반환하고, required 속성이 없을 경우엔 항상 true를 반환한다.

그래서, 이 함수를 폼에 걸어주고 이름과 성별이 모두 입력되었는지 확인하는 코드를 작성하였다. 검사를 해서 입력이 안 되었을 경우엔 값을 입력해달라는 메시지를 넣었는데, 이 메시지 또한 자바스크립트에서 기본적으로 제공하는 메시지이다.

 

 

.checkValidity()

  • 자바스크립트에서 지원해주는 유효성 검사 함수
  • 데이터의 입력 여부에 따라 true/false 반환
  • validationMessage: 자바스크립트에서 지원해주는 도움말 문자열?
    • text- "이 입력란을 작성하세요" 
    • radio - "다음 옵션 중 하나를 선택하세요"

 

 

그 다음으로, 유효성 검사를 만족하면 axios 요청을 하는데 중간에 주석처리된 것은, 그냥 axios 통신을 위한 코드이고 주석 처리된 부분을 비동기 처리 문법인 async & await 문법을 사용해 다시 작성했다. 즉, axios로 데이터 전송이 완료된 후에야 response 응답을 받을 수 있다. (참고로, axios에서 데이터를 백으로 전송할 때 url 값을 `(백틱)으로 묶어서 쿼리 스트링에 데이터를 담아 전송할 수 있다.)

데이터 전송이 완료되면, 백으로부터 받은 응답(response)를 그대로 뿌려주면 되므로, 화면의 이동없이 데이터를 주고 받을 수 있다.(로직은 동일하게 받은걸 그대로 뿌려주면 됨)

 

 

app.js(백) 코드는 다음과 같다.

(처 예제에서 설정을 위한 코드를 이미 적었으니, 따로 또 적지는 않았다.)

// axios

// "/axios" 경로에 get 요청이 들어오면
app.get("/axios", function (req, res) {
  console.log(req.query); // get 요청으로 전달받은 data는 쿼리에 담아서 전달됨
  res.send(req.query); // 전달받은 데이터를 다시 클라이언트에게 response함
});

// "/axios" 경로에 post 요청이 들어오면
app.post("/axios", function (req, res) {
  console.log(req.body); // post 요청으로 전달된 data는 body에 담아서 전달됨
  res.send(req.body); // 전달받은 데이터를 다시 클라이언트에게 response함
});

로직은 동일하다. 받은거(request) 다시 응답(response) -

 

 

 

 

 

 

 

 

 

마지막으로, Fetch에 대해 살펴보자.

 

3. Fetch

  • ES6에서 지원된 비동기 통신을 위한 자바스크립트 내장 API
  • Promise 기반
  • 장점
    • Promise 기반으로 동작하여 데이터를 다루기 편리하고 콜백 지옥x
    • 내장 라이브러리이기 때문에, 별도의 import 필요X
    • response에 promise를 기반으로 하는 다양한 메서드가 존재 → 다양한 형태의 응답 처리 가능
      • respose.text() - 응답을 읽고 텍스트를 반환
      • response.json() - 응답을 JSON형태로 파싱(실제로 확인해보면, javascript 객체 형태로 반환) 
  • 단점
    • 기본 응답 결과는 response 객체이며, 별도로 json or text로 바꾸는 변환과정이 반드시 필요
    • 구형 브라우저에서 지원하지X (크로스 브라우징X)
    • 네트워크 에러 발생시, 계속 기다려야 함
    • API 요청 취소 불가
    • 비교적 Axios에 비해 부족한 기능

 

 

 

마찬가지로, 실습 화면에서 데이터 입력 후 버튼을 누르면, 서버와 클라이언트가 Fetch get/post  방식으로 데이터를 주고 받는 코드를 작성해보자.

<script>
      const resultBox = document.querySelector(".result");
      
      // fetch get 방식
      function fetchGet() {
        const form = document.forms["register"];
        
        // 입력받은 데이터를 객체 data에 담아 저장
        const data = {
          name: form.name.value,
          gender: form.gender.value,
        };
        
        // fetch(url, {요청과 관련된 데이터})
        //fetch에서 get 방식을  쓸 땐 method 방식 지정 안해도 ㅇㅋ
        fetch(`./fetch?name=${data.name}&gender=${data.gender}`, { // 쿼리스트링에 데이터를 담아 전달
          method: "get",
        })
          .then(function (res) {
            // res: http 응답 전체를 나타내는 객체
            // 본문 콘텐트를 추출하기 위해서 사용하는 메서드: json(), text()
            // return response.text(); // 다음 then의 result가 됨 (반드시 return 할 것!)
            return res.json(); // 응답(response)을 JSON 형식으로-
          })
          .then(function (result) { // result: 앞의 then의 return 값
            resultBox.innerText = `GET fetch 요청 완료!: ${result.name}님의 성별은 ${result.gender}입니다.`;
            resultBox.style.color = "coral";
          });
      }
	
     // fetch post 방식
      async function fetchPost() {
        const form = document.forms["register"];
        
        // 입력받은 데이터를 객체 data에 담아 저장
        const data = {
          name: form.name.value,
          gender: form.gender.value,
        };

        try {
          const res = await fetch(`/fetch`, { // fetch post 방식으로 데이터가 모두 전송이 된 후 실행
            method: "post",
            headers: {
              "Content-Type": "application/json", // 어떠한 형식인지 명시
            },
            // data를 post 요청으로 보낼 때는
            // object -> JSON으로 변경해서 전송
            body: JSON.stringify(data), // 값이나 객체를 JSON 문자열로 변환하여 body에 저장
          });

          const result = await res.json(); // 응답(response)을 JSON으로-
          console.log(result);
          resultBox.innerText = `POST fetch 요청 완료!: ${result.name}님의 성별은 ${result.gender}입니다.`;
          resultBox.style.color = "lime";
        } catch (err) {
          console.log(err);
        }
      }
      
</script>

 

 

위 코드에서 주의 깊게 봐야할 부분

 

fetch get 방식에서 백으로부터 응답받은 response를 json 형식으로 바꾸어 데이터를 추출했다는 점이다. 또한, fetch post 방식에서 백으로 입력받은 데이터를 전송할 때 headers에 어떤 형식의 데이터를 보내는지 명시해 주어야 하며("Content-Type":"application/json") 그 데이터를 JSON 문자열로 변환하여 백으로 보내야 한다. 그 후, 백으로부터 받은 응답(response)을 JSON 형식으로 다시 변환하여 데이터를 추출해 사용하면 된다.

 

위와 같이, fetch는 다른 방법들과 달리 데이터의 형식 변환이 필요해서 많이 사용되는 편은 아니다.

 

 

app.js(백)코드는 다음과 같다. 

(처음 예제에서 설정을 위한 코드를 이미 적었으니, 따로 또 적지는 않았다.)

 

// fetch
app.get("/fetch", function (req, res) {
  console.log(req.query);
  res.send(req.query);
});

app.post("/fetch", function (req, res) {
  console.log(req.body);
  res.send(req.body);
});

 

로직은 동일하다. 받은거(request) 다시 응답(response) -