포트폴리오의 서버를 살려보자

프로그래머스 데브코스 부트캠프에서 과제로 받았던 노션클론서비스의 서버를 프로그래머스 측에서 자체제공 해주셨다.
그러나 지원기간이 끝나 서버가 죽어 포트폴리오에 첨부된 노션클론서비스를 이용하지 못하게 됐다.
어려워 보이는 로직은 없는데...기초적인 백엔드 지식을 습득하고싶었기도 하고, 이참에 만들어볼까 싶다.


웹서버?

이전에 SSR을 공부하며 간단하게 정리했던 적이 있다.
따라서 웹서버에 대해 자세히 서술하지는 않는다.

핵심은 GET Request가 서버로 들어왔을때, 요청된 URL경로에따라 적절한 페이지를 보여줄 것인지 결정하는 로직이다.

이를 어떻게 표현하면좋을까? 먼저 의사코드가 가미된 JS로 나타내보겠다.

const 서버 = 서버열기(서버의_특정포트);

const route = {
	"/" : main.html파일,
  	"/login":" login.html파일,
  	"/*": error.html파일,
  	...
};

서버.클라이언트의_요청((요청) => {
	렌더링(route[요청.경로]);
});

아주 간단하게 이런느낌 아닐까? 중요한건 요청, URL매핑, 맞는 HTML응답이니까.

사실 js를 사용해 웹서버를 만들기 이전에는, 웹서버 전용 소프트웨어들이 존재했다.

  • NginX
  • Apache
  • 기타등...

이러한 웹서버 소프트웨어들은 정적 페이지를 운반하기 위해 존재했다. 정적 웹서버 소프트웨어 답게 문법도 간단하다.

아래는 Nginx의 문법이다.

server {
    listen 80;

    # 정적 파일 요청 처리
    location /static/ {
        root /var/www/html;
    }

    # 동적 요청 프록시 전달
    location / {
        proxy_pass http://localhost:3000;
    }
}

현대의 웹은 동적인 페이지가 많지만, 정적인 페이지도 당연히 있다. 그래서 아직까지도 정적 페이지는 리버스프록시로 둔 NginX를 통해 처리하고, 동적 페이지는 따로 처리하는 방식을 추구하기도 한다.


출처 : https://blog.containerize.com/ko/how-to-setup-and-configure-nginx-as-reverse-proxy/

이렇게 하면 정적 페이지 접근시 빠르게 클라이언트에게 화면을 보여주며 웹서버의 부담도 줄일 수 있게된다.

그러나 지금 제작할 웹서버는 간단하여 리버스프록시등의 개념이 필요 없기에 동적 페이지를 위한 웹서버를 구축해보겠다.


Node.js로 웹서버 구축하기

자바도 좋지만, 백-프론트를 한 언어로 빠르게 구축할 수 있다는 장점이 있는 Node.js를 선택했다.
제작하려는 웹서버도 규모가 아주 간단하기에 안성맞춤 아닐까? 심지어 Node.js로 웹서버를 구성하려면 별다른 설치가 필요없다. 그냥 Node.js를 설치하고 내장된 http모듈을 갖다 쓰면 된다.

//app.js
const http = require("http");
const fs = require("fs");
const path = require("path");
const PORT = 3000;

const server = http.createServer((req, res) => {
  if (req.url === "/") {
    const filePath = path.join(__dirname, "index.html");

    fs.readFile(filePath, "utf8", (err, data) => {
      if (err) {
        res.writeHead(500, { "Content-Type": "text/plain" });
        res.end("Internal Server Error");
        return;
      }
      res.writeHead(200, { "Content-Type": "text/html" });
      res.end(data);
    });
  } else {
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("404 Not Found");
  }
});

server.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});


//--------------index.html-----------------//
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="/style.css" />
    <title>Notion Clone</title>
</head>

<body id="root">
    <h1>ㅎㅇ</h1>
</body>
</html>

실제로 접속하면 아래와 같은 결과를 볼 수 있다.


참고로 fs모듈은 파일시스템, path모듈은 말 그대로 폴더와 파일의 경로를 사용할때 쓰는 모듈이다.

프론트엔드를 붙여보자

현재 프론트쪽은 바닐라Js를 이용하여 SPA가 구현되어있다. 이 코드는 Vercel에 업로드되어있는데, Vercel이 하는 웹서버의 역할을 Node.js가 담당할 차례다.

public폴더를 만들어서 클라이언트 코드를 우겨넣었다.

이후 index.html파일에서 스크립트를 로드해준다.

...
<body id="root">
    <script src="/public/main.js" type="module"></script>
</body>

과연 성공할까?

화면이 새하얗다.

html파일 응답은 잘 오는 것을 확인할 수 있다.

그러나 css파일과 js파일이 제대로 넘어오지 않았다.


왜 그런걸까? 답은 간단하다.
서버코드에서 index.html파일을 경로매핑을 통해 제공해주었던 것 처럼, 다른 정적파일(js, css)도 마찬가지로 경로를 제공하여 붙여주어야 한다.

다시 수정해보자.

js와 css도 정적 파일이므로, 경로에 접근시 반환해주어야한다.
이때 응답하는 파일의 타입도 잘 선언해주어야한다.

const http = require("http");
const fs = require("fs");
const path = require("path");
const PORT = 3000;

const server = http.createServer((req, res) => {
  let filePath = path.join(__dirname, req.url === "/" ? "index.html" : req.url);
  const extname = path.extname(filePath); //extname은 파일 확장자를 의미한다. index.html은 .html을 반환함.

  // MIME 타입 설정. 각 파일마다 타입이 다르므로 유의한다.
  const mimeTypes = {
    ".html": "text/html",
    ".css": "text/css",
    ".js": "application/javascript",
  };

  const contentType = mimeTypes[extname] || "text/plain";

  // 파일 읽기
  fs.readFile(filePath, (err, data) => {
    if (!err) {
      //오류가 아닐시, 파일 반환
      res.writeHead(200, { "Content-Type": contentType });
      res.end(data);
      return;
    }

    if (err.code === "ENOENT") {
      //파일이나 디렉토리를 찾을 수 없을때 ENOENT오류가 발생한다.
      res.writeHead(404, { "Content-Type": "text/plain" });
      res.end("404 Not Found");
      return;
    }
    
    // 기타 서버 오류
    res.writeHead(500, { "Content-Type": "text/plain" });
    res.end("Internal Server Error");
  });
});

server.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

접속해보면 화면이 잘 렌더링 된 모습을 볼 수 있다.

서버는 띄웠다! 이제는 클라이언트에서 사용중인 각 api-client에 맞게 api를 만들어야한다. 현재 사용중인 api는 대부분 데이터베이스를 사용중이다. 따라서 먼저 db를 설계해보자.


profile
모르는 것을 모른다고 하기

0개의 댓글

Powered by GraphCDN, the GraphQL CDN