내 방의 책장을 정리하다가 오~래 전에 사놓은 [node.js 200제]라는 책을 발견했다.학부시절 이 책을 기반으로 한 실습강의를 듣고, 웹개발자가 되기로 결정했던 날이 생각나 문득 책을 열어보았다. 교수님의 코드를 받아 적기만 하던 그 때의 나보다 지금의 나는 얼마나 성장해 있을지 나 스스로 느껴보고 싶기도 했다.
총 200개의 예제 중 160번예제까지는 모두 아는 것들이었다. 아하. 나는 모던자바스크립트 Depp Dive를 잘 공부해 놓았기에 자바스크립트 기초 문법은 다 알고, 잘 사용할 수 있는 내용들인데. 다행이다. 아, request response에 대해 알고 있구나, 다행이다. 아, 프로젝트 셋업에서 package.json이 하는 역할을 잘 알고, 적절하게 의존성 모듈을 설치할 줄 아는구나 다행이다. HTML 태그 종류와, 속성들을 잘 알고 있구나 휴...다행이다. 그래도 성장은 했구나.
160번 이후에는 express 모듈과 미들웨어를 활용한 라우팅 관련 실습예제들이었다. HTML Form에서 데이터를 보내면, 백엔드에서는 이 데이터를 어떻게 다루고, 응답은 어떻게 해 주는지 얼핏 알지만, 더 정확하게 알고싶어졌다. 이제 회사에서 백엔드 개발도 해야하기에 필요한 지식이었다.
그래서 161번부터 실습예제를 빠르게 따라해보고 있는 중이다.
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의 urlencoded의 extended옵션이 어떤 역할을 하는지 무엇인지 궁금해졌다. 대체 뭐길래 저게 있고 없고가 form에서 post요청으로 보낸 body의 데이터를 읽어서 채우고 말고를 결정하는 것인가? 책에는 나와있지 않아 직접 찾아보았다.


urlencoded 바디만 파싱해서 만든 새로운 body 객체를 reqeust.body에 반영시키는 역할을 하는 미들웨어 기능이었다.
그 중 extended 옵션은 querystring라이브러리와 qs라이브러리 둘 중 어느 것을 사용하여 URL-encoded 데이터를 파싱할 지 고르는 옵션이었다. 결국 이 둘의 차이를 알고 있어야 사용할 수 있는 옵션이었던 것이다.
나는 여기서 세 가지 모르는 점이 생겼다.
하지만 그 중에서 첫 번째 의문점만 이 글에서는 알아보았다.
URL-encoded body 는 클라이언트가 서버로 key value쌍의 형식의 데이터를 URL-encoded 포맷으로 인코딩하여 보낸 데이터를 뜻한다.
여기서 URL encoding이란, 문자를 인터넷상에서 전송될 수 있는 형식으로 변환하는 것을 의미한다.
이렇게 인코딩된 문자들은 결국 클라이언트측에서 설정한 대로 작성되기에, 우리는 데이터의 출처인 클라이언트 코드를 봐야한다.
내 코드의 예시에서는 <form> 태그가 데이터의 출처니, form 에서 어떻게 입력값이 변환되어 서버로 전달되는지가 내 지식의 공백이겠다.
Mozilla POST 에서는 HTML form태그에서 수행되는 POST방식의 요청의 경우에, content-type을 enctype속성에 명시할 수 있다고 한다.
이 때 세 가지 content-type을 줄 수가 있는데,
그 중에서, 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 이 있다는 것을 처음 알아서 가장 유익했고, 서버는 다양한 형식으로 서버에 전송되는 데이터를 처리할 수 있어야 하기에, 대비가 되어있어야 한다는 점이 재미있었다.
오늘 참 유익했다~