[TIL/React] 2024/08/31
✅ 나만의 로제타 스톤 구현하기MongoDB: NoSQL 데이터베이스로, JSON 형태의 데이터를 저장하고 관리한다.Express.js: Node.js 기반의 웹 애플리케이션 프레임워크로, 서버 및 API를 쉽게 구축할 수 있도록 지원한다.React.js: 사용자 인터
✅ 나만의 로제타 스톤 구현하기
0. 원하는 기술 스택 👨💻
(금일 목표: 내가 구현하길 원하는 기술 스택 조합을 아우르는, ```가장 간단한 형태의 로제타 스톤```을 만들어보자!)
## 1. Backend 👨💻
1-1. 초기화 및 패키지 설치 ✍️
// 프로젝트 초기화 관련 command line
mkdir todolist-app
cd todolist-app
mkdir backend
cd backend
yarn init -y
// 패키지 설치 관련 command line
yarn add express mongoose dotenv cors
yarn add -D nodemon
1-2. server.js 설정 ✍️
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
require("dotenv").config();
// Express 애플리케이션을 생성한다.
const app = express();
const PORT = process.env.PORT || 5000;
// CORS를 설정하여 모든 출처의 요청을 허용한다.
app.use(cors());
// JSON 형식의 요청 본문을 파싱한다.
app.use(express.json());
// MongoDB 데이터베이스에 연결한다.
mongoose
.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true, // 새로운 서버 탐지 및 모니터링 엔진 사용
})
.then(() => console.log("MongoDB connected")) // 연결 성공 시 메시지 출력
.catch((err) => console.log(err)); // 연결 실패 시 에러 출력
// '/api/todos' 경로로 들어오는 요청을 처리할 라우트를 등록한다.
// todos 라우트 파일을 가져와서 처리
app.use("/api/todos", require("./routes/todos"));
// 서버가 지정된 포트에서 요청을 수신한다.
// 서버가 성공적으로 시작되었음을 출력
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
1-3. MongoDB 모델 생성 ✍️
const mongoose = require("mongoose");
// Todo 스키마 정의
const TodoSchema = new mongoose.Schema({
title: {
type: String, // 제목은 문자열 타입
required: true, // 제목은 필수 입력 항목
},
completed: {
type: Boolean, // 완료 상태는 불리언 타입
default: false, // 기본값은 false (할 일이 완료되지 않은 상태로 시작한다는 뜻)
},
});
// 'Todo' 모델을 정의하고, TodoSchema를 사용하여 Mongoose 모델을 생성
module.exports = mongoose.model("Todo", TodoSchema);
1-4. Controller 생성 ✍️
const Todo = require("../models/Todo");
// 모든 Todo 항목을 가져오는 함수
exports.getTodos = async (req, res) => {
try {
const todos = await Todo.find(); // 모든 Todo 항목을 MongoDB에서 찾음
res.json(todos); // 결과를 JSON 형식으로 클라이언트에 응답
} catch (err) {
res.status(500).json({ message: "Error fetching todos" });
}
};
// 새로운 Todo 항목을 생성하는 함수
exports.createTodo = async (req, res) => {
try {
const newTodo = new Todo({
title: req.body.title, // 요청 본문에서 제목을 가져옴
completed: req.body.completed, // 요청 본문에서 완료 상태를 가져옴
});
const savedTodo = await newTodo.save(); // 새 Todo 항목을 데이터베이스에 저장
res.json(savedTodo); // 저장된 Todo 항목을 JSON 형식으로 클라이언트에 응답
} catch (err) {
res.status(500).json({ message: "Error creating todo" });
}
};
// 특정 Todo 항목을 업데이트하는 함수
exports.updateTodo = async (req, res) => {
try {
const updatedTodo = await Todo.findByIdAndUpdate(req.params.id, req.body, {
new: true, // 업데이트된 결과를 반환
});
res.json(updatedTodo); // 업데이트된 Todo 항목을 JSON 형식으로 클라이언트에 응답
} catch (err) {
res.status(500).json({ message: "Error updating todo" });
}
};
// 특정 Todo 항목을 삭제하는 함수
exports.deleteTodo = async (req, res) => {
try {
await Todo.findByIdAndDelete(req.params.id); // 요청 경로의 ID를 가진 Todo 항목을 삭제
res.json({ message: "Todo deleted" }); // 삭제 성공 메시지를 JSON 형식으로 클라이언트에 응답
} catch (err) {
res.status(500).json({ message: "Error deleting todo" });
}
};
1-5. Router 설정 ✍️
const express = require("express");
const {
getTodos,
createTodo,
updateTodo,
deleteTodo,
} = require("../controllers/todoController");
// Express 라우터를 생성
const router = express.Router();
// GET 요청이 '/' 경로로 들어올 때, 모든 Todo 항목을 가져오는 핸들러를 호출
router.get("/", getTodos);
// POST 요청이 '/' 경로로 들어올 때, 새 Todo 항목을 생성하는 핸들러를 호출
router.post("/", createTodo);
// PUT 요청이 '/:id' 경로로 들어올 때, 특정 Todo 항목을 업데이트하는 핸들러를 호출
router.put("/:id", updateTodo);
// DELETE 요청이 '/:id' 경로로 들어올 때, 특정 Todo 항목을 삭제하는 핸들러를 호출
router.delete("/:id", deleteTodo);
// 설정한 라우터를 모듈로 내보냄
module.exports = router;1-6. env 및 nodemon 설정 ✍️
// .env 설정 양식
MONGO_URI=your_mongodb_uri_here
// nodemon 관련 설정을 package.json에 추가
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
2. Frontend 👨💻
### 2-1. 디렉토리 및 패키지 설정 ✍️
// vite project 생성 mkdir frontend cd frontend yarn create vite .
// 패키지 설정 yarn install yarn add @mui/material jotai react-query axios
### 2-2. main.jsx 설정 ✍️
import ReactDOM from "react-dom/client"; import App from "./App"; // React-Query에서 QueryClient와 QueryClientProvider를 가져옴 import { QueryClient, QueryClientProvider } from "react-query";
// 새로운 QueryClient 인스턴스를 생성 // QueryClient는 React-Query의 데이터 캐싱 및 상태 관리를 처리 const queryClient = new QueryClient();
// React 애플리케이션을 DOM에 렌더링 ReactDOM.createRoot(document.getElementById("root")).render( // QueryClientProvider를 사용하여 전체 애플리케이션에 QueryClient를 제공
### 2-3. App.jsx 설정 ✍️
// TodoList Component 렌더링 import TodoList from "./components/TodoList"; import { Container } from "@mui/material";
const App = () => { return (
export default App;
### 2-4. atom 설정(jotai) ✍️
// src/atoms.js import { atom } from "jotai";
const todoListAtom = atom([]); const newTodoAtom = atom(""); const editTodoAtom = atom(null); const editTodoTitleAtom = atom("");
export { todoListAtom, newTodoAtom, editTodoAtom, editTodoTitleAtom };
### 2-5. CRUD를 위한 hook 설정(React-Query, Axios) ✍️
// src/hooks/useTodos.js import { useQuery, useMutation, useQueryClient } from "react-query"; import axios from "axios"; import { useAtom } from "jotai"; import { todoListAtom } from "../atoms/todoAtom";
// Todo 항목을 가져오는 비동기 함수 const fetchTodos = async () => { // API에서 Todo 항목을 가져옴 const { data } = await axios.get("http://localhost:5000/api/todos"); // 가져온 데이터를 반환 return data; };
// 새로운 Todo 항목을 생성하는 비동기 함수 const createTodo = async (newTodo) => { // API에 새로운 Todo 항목을 POST 요청으로 보냄 const { data } = await axios.post("http://localhost:5000/api/todos", newTodo); // 생성된 Todo 항목을 반환 return data; };
// Todo 항목을 업데이트하는 비동기 함수 const updateTodo = async (updatedTodo) => { // API에 PUT 요청으로 Todo 항목을 업데이트 const { data } = await axios.put( http://localhost:5000/api/todos/${updatedTodo._id}, updatedTodo ); // 업데이트된 Todo 항목을 반환 return data; };
// Todo 항목을 삭제하는 비동기 함수 const deleteTodo = async (id) => { // API에 DELETE 요청으로 Todo 항목을 삭제 await axios.delete(http://localhost:5000/api/todos/${id}); };
// Todo 항목을 가져오기 위한 커스텀 훅 export const useTodos = () => { const [, setTodos] = useAtom(todoListAtom); // 전역 상태의 todos를 가져옴 return useQuery("todos", fetchTodos, { // 서버에서 데이터를 가져온 후 전역 상태를 업데이트 onSuccess: (data) => setTodos(data), refetchOnWindowFocus: false, }); };
// 새로운 Todo 항목을 생성하기 위한 커스텀 훅 export const useCreateTodo = () => { const queryClient = useQueryClient(); return useMutation(createTodo, { // Todo 항목이 성공적으로 생성되면 'todos' 쿼리를 무효화하여 데이터를 새로고침 onSuccess: () => { queryClient.invalidateQueries("todos"); queryClient.refetchQueries("todos"); }, }); };
// Todo 항목을 업데이트하기 위한 커스텀 훅 export const useUpdateTodo = () => { const queryClient = useQueryClient(); return useMutation(updateTodo, { // Todo 항목이 성공적으로 업데이트되면 'todos' 쿼리를 무효화하여 데이터를 새로고침 onSuccess: () => { queryClient.invalidateQueries("todos"); queryClient.refetchQueries("todos"); }, }); };
// Todo 항목을 삭제하기 위한 커스텀 훅 export const useDeleteTodo = () => { const queryClient = useQueryClient(); return useMutation(deleteTodo, { // Todo 항목이 성공적으로 삭제되면 'todos' 쿼리를 무효화하여 데이터를 새로고침 onSuccess: () => { queryClient.invalidateQueries("todos"); queryClient.refetchQueries("todos"); }, }); };
### 2-6. 예제 component 설정 ✍️
// src/components/TodoList.jsx import { useAtom } from "jotai"; import { todoListAtom, newTodoAtom, editTodoAtom, editTodoTitleAtom, } from "../atoms/todoAtom"; import { Box, Button, TextField, Checkbox, List, ListItem, ListItemText, IconButton, Container, } from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; import CheckIcon from "@mui/icons-material/Check"; import CloseIcon from "@mui/icons-material/Close"; import { useTodos, useCreateTodo, useUpdateTodo, useDeleteTodo, } from "../hooks/useTodos";
const TodoList = () => { const [todos] = useAtom(todoListAtom); // 전역 상태에서 todos 가져오기 const [newTodo, setNewTodo] = useAtom(newTodoAtom); // 전역 상태에서 새 Todo 제목 가져오기 const [editTodoId, setEditTodoId] = useAtom(editTodoAtom); // 전역 상태에서 편집 중인 Todo의 ID 가져오기 const [editTodoTitle, setEditTodoTitle] = useAtom(editTodoTitleAtom); // 전역 상태에서 편집 중인 Todo 제목 가져오기
const { isLoading } = useTodos(); // 데이터와 로딩 상태 가져오기 const createTodo = useCreateTodo(); const updateTodo = useUpdateTodo(); const deleteTodo = useDeleteTodo();
if (isLoading) return
const handleAddTodo = () => { if (newTodo.trim()) { createTodo.mutate({ title: newTodo }); // 새 Todo 항목을 서버에 전송 setNewTodo(""); // 입력 필드 초기화 } };
const handleEditTodo = (todo) => { setEditTodoId(todo._id); // 편집 중인 Todo의 ID 설정 setEditTodoTitle(todo.title); // 편집 중인 Todo 제목 설정 };
const handleUpdateTodo = (todo) => { updateTodo.mutate({ ...todo, title: editTodoTitle }); // Todo 제목 업데이트 setEditTodoId(null); // 편집 모드 종료 setEditTodoTitle(""); // 제목 입력 필드 초기화 };
const handleCancelEdit = () => { setEditTodoId(null); // 편집 중인 Todo의 ID 초기화 setEditTodoTitle(""); // 제목 입력 필드 초기화 };
return ( 먹킷리스트
{/ 새 Todo 항목 추가 섹션 /}
{/ Todo 항목 목록 섹션 /} {todos.map((todo) => (
{/ Todo 항목 제목: 편집 모드일 때와 아닐 때 /} {editTodoId === todo._id ? (
{/ 편집 모드일 때와 아닐 때의 버튼들 /} {editTodoId === todo._id ? ( <>
export default TodoList;
## 3. 완성본 이미지 👨💻


## 4. 분석 방향과 앞으로의 고민 👨💻
### 4-1. 분석 ✍️
2-4. atom 설정(jotai) 2-5. CRUD를 위한 hook 설정(React-Query, Axios) 2-6. 예제 component 설정
1) 4와 5 2) 5와 6 3) 4와 6 5) 4와 5와 6
1-2. server.js 설정
1-3. MongoDB 모델 생성
1-4. Controller 생성
1-5. Router 설정
1) 2와 3
2) 2와 4
3) 2와 5
5) 3과 4
6) 3과 5
7) 4와 5
8) 2와 3과 4
9) 2와 3과 5
10) 2와 4와 5
11) 2와 3과 4와 5
4-2. 고민 ✍️
More to read
AI&ML 기초
Reference: https://bettermesol.github.io/ml/2019/09/16/ai-ml-dl/AI: 기계가 사람처럼 생각하고 판단하게 만드는 가장 넓은 범주의 기술입니다.ML: 데이터를 학습하여 스스로 규칙을 찾아내는 AI의 한 분야로,
'AI Agent Economy'Novitas : AI Agent가 지갑을 가지는 세상
얼마 전, 미래에셋증권 리서치 리포트(올해는 이더리움이다: 에이전트 시대의 Near Automata)를 접하게 되었습니다. AI Agent를 인간과 함께할 경제 주체로 바라보는 시각에 적잖이 충격을 받았더랬죠.한 가지 짚고 넘어갈 부분이 있습니다. 우리가 흔히 'AI'
'ERC-8004'Novitas: AI 에이전트 경제 주체
Web 4.0을 한 문장으로 정의하면 Sovereign Transact입니다.AI가 인간의 허락 없이 지갑을 소유하고, 결제를 수행하며, 인프라를 통제하는 주권적 경제 주체가 되는 세계입니다. Web 3.0이 블록체인 기반의 탈중앙화를 실현했다면, Web 4.0은 그