[node.js 200제] body-parser 미들웨어와 form 의 기본 MIME-type

Seungrok Yoon (Lethe)·2024년 3월 25일
0

body-parser 미들웨어

내 방의 책장을 정리하다가 오~래 전에 사놓은 [node.js 200제]라는 책을 발견했다.학부시절 이 책을 기반으로 한 실습강의를 듣고, 웹개발자가 되기로 결정했던 날이 생각나 문득 책을 열어보았다. 교수님의 코드를 받아 적기만 하던 그 때의 나보다 지금의 나는 얼마나 성장해 있을지 나 스스로 느껴보고 싶기도 했다.

총 200개의 예제 중 160번예제까지는 모두 아는 것들이었다. 아하. 나는 모던자바스크립트 Depp Dive를 잘 공부해 놓았기에 자바스크립트 기초 문법은 다 알고, 잘 사용할 수 있는 내용들인데. 다행이다. 아, request response에 대해 알고 있구나, 다행이다. 아, 프로젝트 셋업에서 package.json이 하는 역할을 잘 알고, 적절하게 의존성 모듈을 설치할 줄 아는구나 다행이다. HTML 태그 종류와, 속성들을 잘 알고 있구나 휴...다행이다. 그래도 성장은 했구나.

160번 이후에는 express 모듈과 미들웨어를 활용한 라우팅 관련 실습예제들이었다. HTML Form에서 데이터를 보내면, 백엔드에서는 이 데이터를 어떻게 다루고, 응답은 어떻게 해 주는지 얼핏 알지만, 더 정확하게 알고싶어졌다. 이제 회사에서 백엔드 개발도 해야하기에 필요한 지식이었다.

그래서 161번부터 실습예제를 빠르게 따라해보고 있는 중이다.

body-parser 미들웨어

express 모듈 기준에서 미들웨어는 <request 수신>- <미들웨어> - <response 송신> 그 사이에서 다양한 작업을 처리해주는 소프트웨어(로직)으로 이해했다.

redux-toolkit에서도 스토어 셋업 시 미들웨어 미들웨어 개념이 나와서 공부했었던 기억이 있기에, 미들웨어 그 자체가 무엇인지에 대해서 이해하는데 많은 시간이 들지는 않았다.

express모듈에는 다양한 미들웨어를 추가하여 사용할 수 있는데, 그 중 body-parser라는 미들웨어 모듈을 사용하는 예제가 있었다.

전체 코드는 다음과 같다.

<login.html>


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Login page</title>
  </head>
  <body>
    <h1>Login</h1>
    <hr />
    <form method="post" action="/login">
      <table>
        <tr>
          <td>
            <label for="userId">UserId</label>
            <input type="text" name="userId" />
          </td>
          <td>
            <label for="password">PW</label>
            <input type="password" name="password" />
          </td>
        </tr>
        <tr></tr>
      </table>
      <input type="submit" value="confirm" name="" />
    </form>
  </body>
</html>

<app.js>

const path = require("path");
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const port = 5500;

app.use(express.static(path.join(__dirname, "./html")));

/**
 * @description bodyParser.urlencoded 를 설정하지 않으면 앱이 동작을 하지 않는다.
 */
app.use(bodyParser.urlencoded());
app.use(bodyParser.json());

/**
 * 미들웨어를 여러개를 순차적으로 엮을 수 있다.
 */
app.use((request, response, next) => {
  console.log(request.body);
  console.log("first middleware");
  request.user1 = "승록";
  next();
});

app.use((request, response, next) => {
  console.log("second middleware");
  request.user2 = "승재";
  next();
});

app.use((request, response, next) => {
  console.log("third middleware");
  next();
});

app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "./html/index.html"));
});

app.get("/login", (req, res) => {
  res.status(200).set("Content-Type", "text/html");
  res.sendFile(path.join(__dirname, "./html/login.html"));
});

app.post("/login", (req, res) => {
  const { userId, password } = req.body;
  if (!userId || !password) {
    return res.status(400).json({ error: "Missing id, pw" });
  }
  res.status(200).set("Content-Type", "application/json");
  const jsonData = {
    date: new Date(),
    userId,
    password,
  };
  res.json(jsonData);
});

app.get("/error", (req, res) => {
  res.status(404).send("<h1>404 ERROR</h1>");
});

app.listen(port, () => {
  console.log(`Example app listening to port${port}`);
});

문제상황

app.use(bodyParser.urlencoded({ extended: false }));

처음에는 위 구문 없이 앱을 구동했다. 그랬더니 request.body가 비어있는 객체가 나오는 것이 아닌가?

원인

body-parser의 urlencodedextended옵션이 어떤 역할을 하는지 무엇인지 궁금해졌다. 대체 뭐길래 저게 있고 없고가 form에서 post요청으로 보낸 body의 데이터를 읽어서 채우고 말고를 결정하는 것인가? 책에는 나와있지 않아 직접 찾아보았다.

body-parser 레포지토리 링크

밝혀진 진실

urlencoded 바디만 파싱해서 만든 새로운 body 객체를 reqeust.body에 반영시키는 역할을 하는 미들웨어 기능이었다.

그 중 extended 옵션은 querystring라이브러리와 qs라이브러리 둘 중 어느 것을 사용하여 URL-encoded 데이터를 파싱할 지 고르는 옵션이었다. 결국 이 둘의 차이를 알고 있어야 사용할 수 있는 옵션이었던 것이다.

나는 여기서 세 가지 모르는 점이 생겼다.

  • URL-encoded body object가 뭐야?
  • querystring 라이브러리는 뭐야?
  • qs 라이브러리는 뭐야?

하지만 그 중에서 첫 번째 의문점만 이 글에서는 알아보았다.

URL-encoded body object

URL-encoded body 는 클라이언트가 서버로 key value쌍의 형식의 데이터를 URL-encoded 포맷으로 인코딩하여 보낸 데이터를 뜻한다.

여기서 URL encoding이란, 문자를 인터넷상에서 전송될 수 있는 형식으로 변환하는 것을 의미한다.

이렇게 인코딩된 문자들은 결국 클라이언트측에서 설정한 대로 작성되기에, 우리는 데이터의 출처인 클라이언트 코드를 봐야한다.

내 코드의 예시에서는 <form> 태그가 데이터의 출처니, form 에서 어떻게 입력값이 변환되어 서버로 전달되는지가 내 지식의 공백이겠다.

HTML form 태그의 데이터 전송 방식

Mozilla POST 에서는 HTML form태그에서 수행되는 POST방식의 요청의 경우에, content-typeenctype속성에 명시할 수 있다고 한다.

이 때 세 가지 content-type을 줄 수가 있는데,

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

그 중에서, application/x-www-form-urlencoded형식을 채택한 경우에는 알파벳이 아닌 문자가 URL-encoded된다고 한다. 그리고 application/x-www-form-urlencoded는 form 태그요소의 기본 MIME type이다.

가령

<form method="post" action="/submit">
  <label for="username">Username:</label>
  <input type="text" id="username" name="username">
  <label for="password">Password:</label>
  <input type="password" id="password" name="password">
  <button type="submit">Submit</button>
</form>

이런 form이 있을 때, submit된 데이터는 아래의 형식으로 인코딩이 된다. 서버는 아래 형식으로 인코딩된 데이터를 파싱해서 원래 데이터 형식으로 복원하는 작업이 필요했던 것이다.

username=user123&password=secret123

결론

body-parser 모듈의 urlencoded() 메서드는 서버로 들어온 application/x-www-form-urlencoded 포맷의 데이터를 파싱하기 위한 작업을 하는 미들웨어함수였다.

더불어 json()메서드는 서버로 들어온 application/json 포맷의데이터를 파싱하기 위한 작업을 하는 미들웨어 함수였다.

이렇게 파싱이 된 데이터를 우리의 서버 코드는 그제서야 알아보고 후속 작업을 진행할 수 있는 것이었다.

아하~ 그랬군!

<form>태그에 default MIME type 이 있다는 것을 처음 알아서 가장 유익했고, 서버는 다양한 형식으로 서버에 전송되는 데이터를 처리할 수 있어야 하기에, 대비가 되어있어야 한다는 점이 재미있었다.

오늘 참 유익했다~

profile
안녕하세요 개발자 윤승록입니다. 내 성장을 가시적으로 기록하기 위해 블로그를 운영중입니다.

0개의 댓글