nodejs로 서버를 구현할 때 일반적으로 express 패키지를 사용하여 편하게 구현할 수 있다.
하지만 이번에는 express의 패키지를 사용하지않고 http모듈로만 원초적으로 서버를 만들어보고 원리를 파해쳐보고자 한다.
우선은 아주 정말 원시적으로 response body에 html을 반환하는 서버를 구현해보자.
// server.js
const http = require('http');
// const http2 = require('http2'); // http2는 오직 https와만 적용된다. 따라서 SSL이 필요한데 지금은 연습이니 http로 연습하자
// console.log(http.STATUS_CODES); // http의 상태코드
// console.log(http.METHODS); // http의 메소드
// 서버 생성
const server = http.createServer((req, res) => {
console.log('incoming...');
console.log(req.headers); // 요청의 헤더
console.log(req.httpVersion); // 요청의 http 버전
console.log(req.method); // 요청의 메서드
console.log(req.url); // 요청의 url
const url = req.url;
res.setHeader('Content-Type', 'text/html'); // response body의 content-type 명시
if (url === '/') {
res.write('<html>'); // response body 작성
res.write('<head><title>Node Server</title></head>');
res.write('<body><h1>Welcome!</h1><p>this is node server</p></body>');
res.write('</html>');
} else if (url === '/about') {
res.write('<html>');
res.write('<head><title>Node Server</title></head>');
res.write('<body><h1>About</h1><p>this is about page</p></body>');
res.write('</html>');
} else {
res.write('<html>');
res.write('<head><title>Node Server</title></head>');
res.write('<body><h1>404</h1><p>Not Found!</p></body>');
res.write('</html>');
}
res.end(); // response 완료
});
// 서버가 어떤 포트에서 리스닝 할 것인지 설정
server.listen(8080);
html의 내용을 한줄 한줄 res
에 작성하여 반환하는 식이다.
$ node sever.js
# 혹은
$ nodemon server.js # 변경사항을 실시간으로 적요하고 싶다면 nodemon으로 실행
브라우저를 열고 아래 url을 입력하자.
http://localhost:8080
위에서 구현한 방식은 너무나 불편하다. 이제는 HTML 파일을 찾아서 response에 담아서 반환해보도록 개선을 해보자.
├── server.js
└── html
├── about.html
├── index.html
└── not-found.html
// server.js
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
const url = req.url;
res.setHeader('Content-Type', 'text/html');
if (url === '/') {
const read = fs.createReadStream('./html/index.html'); // html 파일을 stream으로 읽어온다.
read.pipe(res); // 읽어온 파일을 response에 연결한다.
} else if (url === '/about') {
fs.createReadStream('./html/about.html').pipe(res); // 메서드 체인으로 한번에 작성이 가능하다.
} else {
fs.createReadStream('./html/not-found.html').pipe(res);
}
});
server.listen(8080);
훨씬 코드가 간결해지고 편해졌다. 😃😃
이때 주의점은 res.end()
작성은 삭제를 해주어야 한다.
pipe는 비동기적인 함수이므로 호출만 해놓고 (작업이 끝나길 기다리지 않고) 다음 코드 라인으로 넘어간다.
그래서 piping이 되고 있는 중간에 res.end를 호출하게 되면 파이핑이 멈추게되어 response에 제대로 값이 작성되지 않은채 종료가 되는 꼴이다.
pipe가 끝나면 자동으로 end() 처리가 되므로, 수동적으로 호출해줄 필요는 없다.
템플릿 엔진을 이용하면 HTML 문서의 뼈대인 템플릿을 만들고 템플릿의 내용물만 동적으로 바꾸어서 클라이언트에게 보낼수 있다.
서버에서 동적인 데이터에 맞추어 HTML을 가공하여 보내는 방식인데 지금 구현해보는것이 아주 원시적인 서버사이드 렌더링이라고 할 수 있다.
고도화된 서버사이드 렌더링을 하려면 react + next.js 조합으로 구현하면 된다.
템플릿 엔진으로는 다양한것들이 있지만 가장 유명한 ejs를 사용해보자.
ejs의 템플릿 문법은
<%= 변수 %>
<% 자바스크립트 문법 %>
이 2가지만 알면 된다.
우선 npm을 초기화하고 ejs를 설치하자.
$ npm init -y
$ npm i ejs
├── server.js
└── template
├── index.ejs
├── todos.ejs
└── not-found.ejs
// index.ejs
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Node Server</title>
</head>
<body>
<h1>Welcome!!! <%= name %></h1>
<p>this is node sever</p>
</body>
</html>
// todos.ejs
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Node Server</title>
</head>
<body>
<h1>Todo List</h1>
<p>What are you going to do?</p>
<ul>
<% todos.forEach(todo => { %>
<li><%= todo.text %></li>
<% }) %>
</ul>
</body>
</html>
// not-found.ejs
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Node Server</title>
</head>
<body>
<h1>Todo List</h1>
<p>What are you going to do?</p>
<ul>
<% todos.forEach(todo => { %>
<li><%= todo.text %></li>
<% }) %>
</ul>
</body>
</html>
const http = require('http');
const fs = require('fs');
const ejs = require('ejs');
// 임시 데이터
const name = 'noah';
const todos = [{ text: 'eat' }, { text: 'sleep' }, { text: 'repeat' }];
const server = http.createServer((req, res) => {
const url = req.url;
res.setHeader('Content-Type', 'text/html');
if (url === '/') {
ejs
.renderFile('./template/index.ejs', { name })
.then(data => res.end(data));
} else if (url === '/todos') {
ejs
.renderFile('./template/todos.ejs', { todos })
.then(data => res.end(data));
} else {
ejs
.renderFile('./template/not-found.ejs', { name })
.then(data => res.end(data));
}
});
server.listen(8080);
ejs.renderFile('./template/index.ejs', { name }).then(data => res.end(data));
ejs의 renderFile메서드를 사용하여 템플릿 파일에 데이터를 넣고 res에 값을 넘겨줄수 있다.
첫번째 인자로 템플릿 파일으의 경로를 지정하고 템플릿 파일에 넘겨줄 데이터를 2번째 인자로 넘겨준다.
ejs는 프로미스를 구현이 되어있기 때문에 then을 통해 수행이 완료되면 res에 넘기면서 종료를 해준다.
이제 다시 서버를 실행시키고 각각의 url에 들어가면 템플릿은 고정된채로 내용물이 동적인 임시 데이터로 채워져서 보여지게 된다.
이전까지는 HTML파일을 응답해주는 서버를 만들었다. 이렇게 HTML 파일을 반환하는 서버는 오로지 클라이언트가 브라우저일 때만 의미가 있다.
이번에는 브라우저 클라이언트만이 아닌 iOS, 안드로이와 같은 다양한 클라이언트와의 통신을 위해 JSON 데이터를 반환하는 서버를 만들어보자.
const http = require('http');
const todos = [{ text: 'eat' }, { text: 'sleep' }, { text: 'repeat' }];
const server = http.createServer((req, res) => {
const url = req.url;
const method = req.method;
if (url === '/todos') {
if (method === 'GET') { // 요청이 GET 방식일 때
res.writeHead(200, { contentType: 'application/json' });
res.end(JSON.stringify(todos));
} else if (method === 'POST') { // 요청이 POST 방식일 때, 즉 새로운 데이터 추가하려 할 때
const body = [];
req.on('data', chunk => { // 요청의 데이터를 읽어올때마다 데이터를 담아둔다.
body.push(chunk);
});
req.on('end', () => { // 데이터 읽기가 완료되면
const bodyStr = Buffer.concat(body).toString(); // 버퍼에 여태까지 모아둔 데이터를 문자열로합친다.
const todo = JSON.parse(bodyStr); // 문자열을 파싱한다.
todos.push(todo); // 파싱한 데이터를 우리의 임시 메모리(?)에 담아둔다.
res.writeHead(201); // 201 응답을 담아주고
res.end(); // 종료한다.
});
}
}
});
server.listen(8080);
서버를 실행하고 브라우저에 https://localhost:8080/todos
입력하면 정상적으로 데이터가 아래와 같은 형태로 받아오는 것을 확인할 수 있다.
[
{
"text": "eat"
},
{
"text": "sleep"
},
{
"text": "repeat"
}
]
포스트맨과 같은 툴을 이용하여 https://localhost:8080/todos
에 아래와 같이 JSON 데이터를 입력하고 POST 요청을 보내보자.
{
"text": "new todo"
}
데이터와 201 코드를 받은것을 확인할 수 있다.
그리고 나서 다시 우리 GET메서드로 요청을 보내면 아래와 같이 데이터가 온전하게 생성된것을 확인할 수 있다.
[
{
"text": "eat"
},
{
"text": "sleep"
},
{
"text": "repeat"
},
{
"text": "new todo"
}
]