원시적으로 노드 서버 만들기(feet.서버사이드 렌더링, JSON 서버)

younoah·2022년 1월 16일
1

[nodeJS]

목록 보기
7/15
post-thumbnail

intro

nodejs로 서버를 구현할 때 일반적으로 express 패키지를 사용하여 편하게 구현할 수 있다.

하지만 이번에는 express의 패키지를 사용하지않고 http모듈로만 원초적으로 서버를 만들어보고 원리를 파해쳐보고자 한다.


1️⃣ 간단한 서버 구현하기

우선은 아주 정말 원시적으로 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

2️⃣ 서버 개선하기

위에서 구현한 방식은 너무나 불편하다. 이제는 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() 처리가 되므로, 수동적으로 호출해줄 필요는 없다.


3️⃣ 템플릿 엔진(EJS) 사용해보기 (feet.서버사이드 렌더링)

템플릿 엔진을 이용하면 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

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에 들어가면 템플릿은 고정된채로 내용물이 동적인 임시 데이터로 채워져서 보여지게 된다.


4️⃣ JSON을 보내는 서버 만들기

이전까지는 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);

GET 테스트

서버를 실행하고 브라우저에 https://localhost:8080/todos 입력하면 정상적으로 데이터가 아래와 같은 형태로 받아오는 것을 확인할 수 있다.

[
  {
    "text": "eat"
  },
  {
    "text": "sleep"
  },
  {
    "text": "repeat"
  }
]

POST 테스트

포스트맨과 같은 툴을 이용하여 https://localhost:8080/todos 에 아래와 같이 JSON 데이터를 입력하고 POST 요청을 보내보자.

{
    "text": "new todo"
}

데이터와 201 코드를 받은것을 확인할 수 있다.

그리고 나서 다시 우리 GET메서드로 요청을 보내면 아래와 같이 데이터가 온전하게 생성된것을 확인할 수 있다.

[
  {
    "text": "eat"
  },
  {
    "text": "sleep"
  },
  {
    "text": "repeat"
  },
  {
    "text": "new todo"
	}
]
profile
console.log(noah(🍕 , 🍺)); // true

0개의 댓글