Section2에서 배운 내용을 바탕으로 나만의 아고라 스테이츠 서버를 만듭니다.
코드스테이츠 fe-sprint-my-agora-states-server Repository에서 자신의 Repository로 fork후 과제 진행합니다.
[✅] my-agora-states-server/app.js
[✅] my-agora-states-server/router/discussions.js
[✅] my-agora-states-server/controller/index.js
[✅] my-agora-states-server 과제 제출 (Pull request)
[✅] my-agora-states-server 시작
[✅] my-agora-states와 연동하기
[✅] my-agora-states-in-react
목차
1. 초기 세팅
2. 과정을 기록하기
3. Error note
// 💬 주어진 코드 (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를 적용합니다.
// TODO: Express 내장 미들웨어인 express.json()을 적용합니다.
const port = 4000;
const discussionsRouter = require('./router/discussions');
// TODO: app.use()를 활용하여 /discussions 경로로 라우팅합니다.
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;
// 💬 주어진 코드 (index.js)
const { agoraStatesDiscussions } = require("../repository/discussions");
const discussionsData = agoraStatesDiscussions;
const discussionsController = {
findAll: (req, res) => {
// TODO: 모든 discussions 목록을 응답합니다.
res.send('TODO:')
},
findById: (req, res) => {
// TODO: 요청으로 들어온 id와 일치하는 discussion을 응답합니다.
res.send('TODO:')
}
};
module.exports = {
discussionsController,
};
// 💬 주어진 코드 (discussions.js)
// TODO: discussions 라우터를 완성합니다.
const { discussionsController } = require('../controller');
const { findAll, findById } = discussionsController;
const express = require('express');
const router = express.Router();
// TODO: 모든 discussions 목록을 조회하는 라우터를 작성합니다.
// TODO: :id에 맞는 discussion을 조회하는 라우터를 작성합니다.
module.exports = router;
오늘 과제는 원래 아고라스테이츠 서버 구현이었다.
서버 구현은 아주 무난하게 끝났고, 이걸 기존 클라이언트 서버와 합치면 어떨까 하는 생각이 들었다.
한 파일 안에서 서버 2개를 열고, 동시에 돌리면 뭔가 멋있을 것(?) 같았다.
단순한 생각에서 시작했지만, 과정은 정말 쉽지 않았고 한 10시간은 투자한 것 같다.
결국 Post 기능을 포기하게 됐고, 사서 고생한 결과물이 만족스럽게 나왔다.
클라이언트 코드 파일에 서버 파일을 merge 하는 데에 성공했고,
정상적으로 데이터를 가져와서 discussion 들을 나열하는 것까지 성공했다.
1. 서버 구현하기
// ✅ 구현한 코드 (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");
});
app.use((req, res, next) => {
res.status(404).send("Not Found!");
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send({
message: "Internal Server Error",
stacktrace: err.toString(),
});
});
const server = app.listen(port, () => {
console.log(`[RUN] My Agora States Server... | http://localhost:${port}`);
});
module.exports.app = app;
module.exports.server = server;
// ✅ 구현한 코드 (index.js)
const { agoraStatesDiscussions } = require("../repository/discussions");
const discussionsData = agoraStatesDiscussions;
const discussionsController = {
findAll: (req, res) => {
// TODO: 모든 discussions 목록을 응답합니다.
const { author } = req.query;
if (author) {
res.json(discussionsData.filter((item) => item.author === author));
} else {
res.json(discussionsData);
}
},
findById: (req, res) => {
// TODO: 요청으로 들어온 id와 일치하는 discussion을 응답합니다.
const { id } = req.params;
if (id) {
const discussion = discussionsData.find((item) => item.id === Number(id));
if (discussion) {
res.json(discussion);
} else {
res.status(404).json({ error: "Discussion not found." });
}
}
},
};
module.exports = {
discussionsController,
};
// ✅ 구현한 코드 (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;
2. Server & Client ♻️
기존 클라이언트 파일에 서버 파일을 합치는 과정은 다음과 같았다.
// ♻️ React 식으로 리팩토링한 코드 (Client - App.js)
import React, { useState, useEffect } from "react";
import "./App.css";
import axios from "axios";
const DiscussionItem = ({ avatarUrl, title, author, createdAt, answer }) => {
return (
<li className="discussion__container">
<div className="discussion__avatar--wrapper">
<img
className="discussion__avatar--image"
src={avatarUrl}
alt="avatar"
/>
</div>
<div className="discussion__content">
<h2 className="discussion__title">
<a href={title}>
{title.length > 53 ? `${title.slice(0, 53)}...` : title}
</a>
</h2>
<div
className="discussion__information"
dangerouslySetInnerHTML={{
__html: `${author} / ${new Date(createdAt).toLocaleTimeString()}`,
}}
></div>
</div>
<div className="discussion__answered">
<p>{answer ? "✅" : "❌"}</p>
</div>
</li>
);
};
const App = () => {
const [discussions, setDiscussions] = useState([]);
useEffect(() => {
axios
.get("http://localhost:4000/discussions")
.then((response) => {
setDiscussions(response.data);
})
.catch((error) => {
console.error("Error fetching discussions:", error);
});
}, []);
return (
<main>
<div className="form__info">
<h1>My Agora States</h1>
<section className="form__container">
<form action="" method="get" className="form">
<div className="form__input--wrapper">
<div className="form__input--name">
<label htmlFor="name">Enter your name: </label>
<input type="text" name="name" id="name" required />
</div>
<div className="form__input--title">
<label htmlFor="title">Enter your title: </label>
<input type="text" name="title" id="title" required />
</div>
<div className="form__textbox">
<label htmlFor="story">Your question: </label>
<textarea
id="story"
name="story"
placeholder="질문을 작성하세요"
required
></textarea>
</div>
</div>
<div className="form__submit">
<input type="submit" value="submit" />
</div>
</form>
</section>
</div>
<div className="form__active">
<section className="discussion__wrapper">
<ul className="discussions__container">
{discussions.map((discussion) => (
<DiscussionItem
key={discussion.id}
avatarUrl={discussion.avatarUrl}
title={discussion.title}
author={discussion.author}
createdAt={discussion.createdAt}
answer={discussion.answer}
/>
))}
</ul>
</section>
</div>
</main>
);
};
export default App;
3. Build
💡 find와 filter의 차이
💡 프로토콜 주의 !
💡 dangerouslySetInnerHTML
function MyComponent() {
const htmlString = '<div>Hello, <strong>World!</strong></div>';
return <div dangerouslySetInnerHTML={{ __html: htmlString }} />;
}
💡 axios
HTTP 요청 보내기: Axios는 GET, POST, PUT, DELETE와 같은 다양한 HTTP 요청 메서드를 지원한다.
요청 및 응답 인터셉트: Axios는 요청과 응답을 인터셉트하고 수정하는 기능을 제공한다.
이를 통해 요청 전에 인증 토큰을 추가하거나 응답을 가공하는 등의 작업을 수행할 수 있다.
요청과 응답 데이터 변환: Axios는 요청과 응답 데이터를 자동으로 변환하는 기능을 제공한다.
JSON, XML, FormData 등 다양한 데이터 형식을 자동으로 처리할 수 있다.
요청 취소: Axios는 요청을 취소하는 기능을 제공한다.
이를 통해 사용자가 요청을 취소하거나 중단할 수 있다.
에러 처리: Axios는 HTTP 요청 중 발생하는 에러를 캐치하고 처리할 수 있는 기능을 제공한다.
네트워크 오류, 타임아웃, 응답 상태 코드 등의 에러를 처리할 수 있다.
discussion 눌렀을 때, URL 제대로 수정
discussion 눌렀을 때, 해당 컴포넌트 제외하고 모두 숨김처리
POST, DELETE 기능 추가
CSS 리팩토링