내 방의 책장을 정리하다가 오~래 전에 사놓은 [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 이 있다는 것을 처음 알아서 가장 유익했고, 서버는 다양한 형식으로 서버에 전송되는 데이터를 처리할 수 있어야 하기에, 대비가 되어있어야 한다는 점이 재미있었다.
오늘 참 유익했다~