오늘은 나만의 아고라 스테이츠 서버 만들기라는 과제를 통해 익스프레스로 서버를 구현하였습니다.
my-agora-states-server/app.js
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
GET /discussions/:id
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-server가 켜져 있는지 확인합니다.
로컬 환경에서 실행한 나만의 아고라 스테이츠 서버에서 discussions 데이터를 조회합니다.
data.js
파일을 사용하지 않고, discussions 데이터를 받아옵니다.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가 된다.