TIL) 익스프레스 서버 구현

정우시·2022년 8월 17일
1

2. 코드스테이츠

목록 보기
33/52

나만의 아고라 스테이츠 서버 만들기

오늘은 나만의 아고라 스테이츠 서버 만들기라는 과제를 통해 익스프레스로 서버를 구현하였습니다.

Bare Minimum Requirement


  • my-agora-states-server/app.js
    • 모든 Origin, 경로에 대해 CORS 요청을 허용하게 미들웨어를 적용합니다.
    • POST 요청 등에 포함된 body(payload)를 구조화하기 위한 미들웨어를 적용합니다.
    • 서버 상태 확인을 위해 GET / 에서 상태 코드 200으로 응답합니다.
    • discussionsRouter 를 이용하여 /discussions 경로로 라우팅합니다.

app.js

const express = require('express');
const app = express();

const cors = require('cors');
const morgan = require('morgan');

// morgan 미들웨어가 세팅되어 있습니다.
// HTTP 요청 logger를 편리하게 사용할 수 있는 미들웨어 입니다.
app.use(morgan('tiny'));

// TODO: cors를 적용합니다.
app.use(cors());

// TODO: Express 내장 미들웨어인 express.json()을 적용합니다.
app.use(express.json());

const port = 4000;
const discussionsRouter = require('./router/discussions');

// TODO: app.use()를 활용하여 /discussions 경로로 라우팅합니다. 
app.use('/discussions', discussionsRouter);

app.get('/', (req, res) => {
  // 서버 상태 확인을 위해 상태 코드 200과 함께 응답을 보냅니다.
  res.status(200).send('fe-sprint-my-agora-states-server');
});

const server = app.listen(port, () => {
  console.log(`[RUN] My Agora States Server... | http://localhost:${port}`);
});

module.exports.app = app;
module.exports.server = server;
1. 먼저 npm i를 통해 node_modules를 설치해준다.

2. app.use(cors());를 사용하여 cors를 적용한다.

3. app.use(express.json());를 사용하여 express.json()을 적용한다.
3-1. 클라이언트에서 받은 http 요청 메시지 형식에서 body데이터를 해석하기 위해서 express.json()을 사용한다.

4. 라우팅도 미들웨어처럼 app.use()를 사용하면 된다.
4-1. app.use의 /discussions라는 라우터에 들어가면 discussionsRouter에 가겠다는 의미이다.

5. get으로 '/'를 통해 서버가 연결된 것을 확인 할 수 있다.
5-1. 포스트맨과 터미널을 통해 확인 가능하다.
5-2. 터미널에서는 메서드 URL 그리고 사전에 입력한 morgan을 통해 몇 초가 걸렸는지 알 수 있다.

  • my-agora-states-server/router/discussions.js

    • GET /discussions

      • 모든 discussion 목록을 조회하는 라우터를 작성합니다.
    • GET /discussions/:id

      • discussion 하나를 조회하는 라우터를 작성합니다.

discussions.js

// TODO: discussions 라우터를 완성합니다.
const { discussionsController } = require('../controller');
const { findAll, findById } = discussionsController;
const express = require('express');
const router = express.Router();

// TODO: 모든 discussions 목록을 조회하는 라우터를 작성합니다.
router.get('/', findAll);

// TODO: :id에 맞는 discussion을 조회하는 라우터를 작성합니다.
router.get('/:id', findById);

module.exports = router;
1. get으로 모든 discussions 목록을 조회한다.
1-1. router.get('/')는 http://localhost:4000/을 의미하는 것이 아니라 http://localhost:4000/discussions을 뜻한다.
1-2. 왜냐하면 app.js에서 '/discussions'로 설정을 했기 때문이다. 따라서 http://localhost:4000/discussions에 작대기 하나 포함해서 http://localhost:4000/discussions/를 의미한다.

2. router.get('/')의 옆에는 controller에 있는 findAll을 사용해서 router.get('/', findAll)이 된다.

3. id에 맞는 discussion을 조회하는 라우터의 경우 라우터에 get으로 id를 적어주고 옆에는 findById를 적어준다.

  • my-agora-states-server/controller/index.js
    • discussionsController.findAll (모든 discussion 목록 조회)
    • discussionsController.findById (discussion 하나를 조회)

index.js

const { agoraStatesDiscussions } = require("../repository/discussions");
const discussionsData = agoraStatesDiscussions;

const discussionsController = {
  findAll: (req, res) => {
    // TODO: 요청으로 들어온 id와 일치하는 discussion을 응답합니다.
    const {id} = req.params;
    // console.log(id);
    // console.log(typeof id);
    const found = discussionsData.find(data => data.id === Number(id));
    // console.log(found);

    if(found) {
      res.status(200).send(found);
    } else {
      res.status(404).send('Not Found');
    }
    
  }

};

module.exports = {
  discussionsController,
};
1. 상태 코드 200을 보내기 위해 status를 이용한다.
1-1. send에는 discussionsData를 보내준다.

2. findById에서 우리가 요청이 들어오는 id를 받고 싶을 때 req.params라는 path parameter를 사용한다.
2-1. req.params를 받아오고 그거를 구조 분해 할당으로 받아온다.
2-2. npx nodemon my-agora-states-server/app
.js를 통해 nodemon으로 확인이 바로 가능하다.
2-3. console.log(id);를 통해 const {id} = req.params를 받아오는지 확인한다.
2-4. 포스트맨을 통해 http://localhost:4000/discussions/를 잘 받아오는 것을 확인할 수 있다.
2-5. 또한 http://localhost:4000/discussions/45를 했을 경우 console.log로 45가 찍히는 것을 확인할 수 있다.
2-6. params로 아이디(아까 45)가 올 때 아이디는 문자열이다. 이따 문자열로 바꿔야한다.
2-7. 우리가 id를 찾고 싶을 때 쓸 수 있는 메서드가 여러가지 있지만 (filter 등등) 여기서는 find를 사용한다.
2-8. discussionsData에서 find에서 data가 data의 id가 받아오는 id와 같으면 그 번호의 정보만 리턴한다.
2-9. const found = discussionsData.find(data => data.id === Number(id));로 표현하고 console.log(found);에서 확인 가능하다.

3. 404 코드를 보내주기 위해 if문으로 고친다.
3-1. 만약에 찾는 데이터가 있으면 status는 200, send는 found를 보내준다. 없으면 status는 404로 Not Found를 send한다.

  • my-agora-states와 연동하기
    • my-agora-states-server가 켜져 있는지 확인합니다.

    • 로컬 환경에서 실행한 나만의 아고라 스테이츠 서버에서 discussions 데이터를 조회합니다.

      • 더 이상 data.js 파일을 사용하지 않고, discussions 데이터를 받아옵니다.
      • Fetch API를 이용합니다.

index.html

<!DOCTYPE html>
<html lang="en">

<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>My Agora States</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.3.8/purify.js"
    integrity="sha512-QaF+0tDlqVmwZaQSc0kImgYmw+Cd66TxA5D9X70I5V9BNSqk6yBTbyqw2VEUsVYV5OTbxw8HD9d45on1wvYv7g=="
    crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>

<body>
  <main>
    <h1>My Agora States</h1>
    <section class="form__container">
      <form action="" method="get" class="form">
        <div class="form__input--wrapper">
          <div class="form__input--name">
            <label for="name">Enter your name: </label>
            <input type="text" name="name" id="name" required>
          </div>
          <div class="form__input--title">
            <label for="name">Enter your title: </label>
            <input type="text" name="name" id="name" required>
          </div>
          <div class="form__textbox">
            <label for="story">Your question: </label>
            <textarea id="story" name="story" placeholder="질문을 작성하세요" required></textarea>
          </div>
        </div>
        <div class="form__submit">
          <input type="submit" value="submit">
        </div>
      </form>
    </section>
    <section class="discussion__wrapper">
      <ul class="discussions__container">
        <li class="discussion__container">
          <div class="discussion__avatar--wrapper">
            <img class="discussion__avatar--image"
              src="https://avatars.githubusercontent.com/u/12145019?s=64&u=5c97f25ee02d87898457e23c0e61b884241838e3&v=4"
              alt="avatar of kimploo">
          </div>
          <div class="discussion__content">
            <h2 class="discussion__title"><a href="https://github.com/codestates-seb/agora-states-fe/discussions/6">[notice] 좋은 질문하는 법</a></h2>
            <div class="discussion__information">kimploo / 2022-04-22T14:08:33Z</div>
          </div>
          <div class="discussion__answered"><p></p></div>
        </li>
      </ul>
    </section>
  </main>
</body>
<!-- <script src="data.js"></script> -->
<script src="script.js"></script>

</html>
1. 맨 밑에 <script src="data.js"></script>를 주석처리하고 fetch로 데이터를 받아온다.

script.js

// index.html을 열어서 agoraStatesDiscussions 배열 요소를 확인하세요.
let agoraStatesDiscussions;

// convertToDiscussion은 아고라 스테이츠 데이터를 DOM으로 바꿔줍니다.
const convertToDiscussion = (obj) => {
  const li = document.createElement("li"); // li 요소 생성
  li.className = "discussion__container"; // 클래스 이름 지정

  const avatarWrapper = document.createElement("div");
  avatarWrapper.className = "discussion__avatar--wrapper";
  const discussionContent = document.createElement("div");
  discussionContent.className = "discussion__content";
  const discussionAnswered = document.createElement("div");
  discussionAnswered.className = "discussion__answered";

  // TODO: 객체 하나에 담긴 정보를 DOM에 적절히 넣어주세요.
  const avatarImg = document.createElement("img");
  avatarImg.src = obj.avatarUrl;
  avatarImg.alt = "avatar of " + obj.author;
  avatarWrapper.append(avatarImg);

  const discussionTitle = document.createElement("h2");
  const titleAnchor = document.createElement("a");
  titleAnchor.href = obj.url;
  titleAnchor.textContent = obj.title;
  discussionTitle.append(titleAnchor);

  const discussionInformation = document.createElement("div");
  discussionInformation.className = "discussion__information";
  discussionInformation.textContent = `${obj.author} / ${new Date(
    obj.createdAt
  ).toLocaleTimeString()}`;
  discussionContent.append(discussionTitle, discussionInformation);

  const checked = document.createElement("p");
  checked.textContent = obj.answer ? "☑" : "☒";
  discussionAnswered.append(checked);

  li.append(avatarWrapper, discussionContent, discussionAnswered);
  return li;
};
// agoraStatesDiscussions 배열의 모든 데이터를 화면에 렌더링하는 함수입니다.
const render = (element) => {
  for (let i = 0; i < agoraStatesDiscussions.length; i += 1) {
    element.append(convertToDiscussion(agoraStatesDiscussions[i]));
  }
  return;
};

// ul 요소에 agoraStatesDiscussions 배열의 모든 데이터를 화면에 렌더링합니다.
const addDiscussion = () => {};

const form = document.querySelector("form.form");
const author = form.querySelector("div.form__input--name > input");
const title = form.querySelector("div.form__input--title > input");
const textbox = form.querySelector("div.form__textbox > textarea");
form.addEventListener("submit", (event) => {
  event.preventDefault();
  const obj = {
    id: "unique id",
    createdAt: new Date().toISOString(),
    title: title.value,
    url: "https://github.com/codestates-seb/agora-states-fe/discussions",
    author: author.value,
    answer: null,
    bodyHTML: textbox.value,
    avatarUrl:
      "https://avatars.githubusercontent.com/u/12145019?s=64&u=5c97f25ee02d87898457e23c0e61b884241838e3&v=4",
  };
  agoraStatesDiscussions.unshift(obj);
  const discussion = convertToDiscussion(obj);
  const ul = document.querySelector("ul.discussions__container");
  ul.prepend(discussion);
});

fetch('http://localhost:4000/discussions')
.then(res => res.json())
.then(json => {
  agoraStatesDiscussions = json;
  const ul = document.querySelector("ul.discussions__container");
  render(ul);
})
1. 서버 주소 http://localhost:4000/discussions을 fetch에 넣어준다.

2. fetch를 쓰면 then을 같이 사용해야 한다.
2-1. then을 사용해서 json으로 해석을 해야 한다.

3. 그 다음에 json을 맨 위에 선언한 변수 let agoraStatesDiscussions;을 json으로 넣는다.

4. ul에 querySelector를 사용해서 데이터를 뿌린다.
4-1. 뿌려주는 곳은 ul에 class가 discussions__container인 곳에 뿌린다.

5. 미리 설정된 render라는 함수를 이용한다.
5-1. render함수는 agoraStatesDiscussions를 받아왔는데 이 agoraStatesDiscussions.length만큼 for문을 돌리면서 해당 element에 append를 해주겠다는 뜻이다.
5-2. append는 추가를 해주겠다는 뜻이다.
5-3. element는 이 ul에 append를 해주겠다는 뜻이다.
5-4. convertToDiscussion은 li로 변환을 해주는 메소드이다.
5-5. render라는 함수 하나로 작업을 해준다.
5-6. ul에 agoraStatesDiscussions에 0부터 44까지 데이터들이 반복문을 돌면서 이 ul에 append가 된다.
profile
프론트엔드 공부하고 있는 정우시입니다.

0개의 댓글