Search
Duplicate
✔️

todo list clone

1. 설계

가. 기능 명세

Client 입력값을 Server가 받아서 DB에 저장
DB 값을 Server를 통해 Client로 출력

2. Server

가. 기본 설정

package.json 작성(npm init)
필요한 스크립트, 종속성 추가 및 설치(dependacies, npm install)
"scripts": { "start": "node index.js", "nodemon": "nodemon index.js", "frontend": "npm run start --prefix client", "dev": "concurrently \"npm run nodemon\" \"npm run start --prefix client\"" }, "dependencies": { "body-parser": "^1.18.3", "cors": "^2.8.5", "debug": "^4.1.1", "express": "^4.17.1", "mongoose": "^5.10.0", "saslprep": "^1.0.3", "supports-color": "^7.1.0" }, "devDependencies": { "concurrently": "^4.1.0", "nodemon": "^1.19.1" }
JavaScript
복사

나. 템플릿 작성

index.js 작성
// 필요한 모듈 사용 const express = require("express"); const bodyParser = require("body-parser"); // express server 생성 const app = express(); // json 형태 요청에 대해 파싱 가능 app.use(bodyParser.json()); app.get("/", function (req, res) { console.log("hello"); }); // 5000포트 사용하여 app 시작 app.listen(5000, () => { console.log("app start at 5000 port"); });
JavaScript
복사

다. DB 구성

DB 연결(mongoDB, mongoose)
const mongoose = require("mongoose"); const connect = mongoose .connect( "URL from Mongo DB", { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false, } ) .then(() => console.log("MongoDB Connected...")) .catch((err) => console.log(err));
JavaScript
복사
DB Schema 및 model 설정
const mongoose = require("mongoose"); const Schema = mongoose.Schema; const TodosSchema = mongoose.Schema( { todoId: { type: Schema.Types.ObjectId, }, todoTitle: { type: String, }, }, { timestamps: true } ); const Todos = mongoose.model("Todos", TodosSchema); module.exports = { Todos };
JavaScript
복사

라. API 작성

DB 내 todos 출력 API
app.get("/api/printTodos", function (req, res) { Todos.find({}, function (err, result) { if (err) return res.status(400).send(err); res.status(200).send(result); }); });
JavaScript
복사
Client 입력값 DB 저장 API
app.post("/api/inputTodos", function (req, res) { const todo = new Todos(req.body); todo.save((err, result) => { if (err) return res.json({ success: false, err }); return res.status(200).json({ success: true }); }); });
JavaScript
복사
DB 내 모든 todos 삭제
// todos 모두 삭제 app.get("/api/removeTodos", function (req, res) { Todos.remove({}, function (err, result) { if (err) return res.status(400).send(err); res.status(200).send(result); }); });
JavaScript
복사

3. Client

가. 기본 설정

React 설치(npx create-react-app .)
→ 해당 디렉토리에 react-app 설치
종속성 추가
→ axios

나. 템플릿 작성

UI 생성(JSX 부분 작성)
function App() { return ( <div className="App"> <header className="App-header"> <div className="container"> <form className="inputBox"> <input type="text" placeholder="입력란" /> <button type="submit">입력</button> </form> </div> </header> </div> ); }
JavaScript
복사

다. API 처리

Todo-list 출력
const [todos, setTodos] = useState([]); useEffect(() => { axios.get("/api/printTodos").then((response) => { setTodos(response.data); }); }, []); // JSX 부분 <div className="container"> {todos && todos.map((todo, index) => <li key={index}>{todo.todoTitle}</li>)} <form className="inputBox"> <input type="text" placeholder="입력란" /> <button type="submit">입력</button> </form> </div>
JavaScript
복사
todo 입력
const [inputValue, setinputValue] = useState(""); const changeHandler = (event) => { setinputValue(event.currentTarget.value); }; const submitHandler = (event) => { event.preventDefault(); axios .post("/api/inputTodos", { todoTitle: inputValue }) .then((response) => { if (response.data.success) { setTodos([...todos, response.data]); setinputValue(""); } else { alert("falied to update todos"); } }); }; // JSX 부분 <form className="inputBox" onSubmit={submitHandler}> <input type="text" placeholder="입력란" value={inputValue} onChange={changeHandler} /> <button type="submit">입력</button> </form>
JavaScript
복사
todo-list 삭제
const removeHandler = (event) => { event.preventDefault(); axios.get("/api/removeTodos").then((response) => { setTodos([]); setinputValue(""); }); }; // JSX 부분 <form className="removeButton" onSubmit={removeHandler}> <button type="submit">모두 삭제</button> </form>
JavaScript
복사

라. Proxy 설정

npm install http-proxy-middleware —save
다음의 경로에 아래와 같이 파일 생성 client/src/setupProxy.js
// setupProxy.js const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = function (app) { app.use( '/api', createProxyMiddleware({ // 서버 포트 target: 'http://localhost:5000', changeOrigin: true, }) ); };
JavaScript
복사

4. 개발 환경

가. Dockerfile 작성

client Dockerfile.dev 작성
FROM node:alpine WORKDIR /app COPY package.json ./ RUN npm install COPY ./ ./ CMD [ "npm", "run", "start" ]
Docker
복사
server Dockerfile.dev 작성
FROM node:alpine WORKDIR /app COPY ./package.json ./ RUN npm install COPY . . CMD ["npm", "run", "dev"]
Docker
복사
Ngnix Dockefile 작성
→ proxy 기능을 위해 Ngnix가 필요하며 default.conf에 세부사항 명시

나. docker-compose 작성

version: "3" services: client: build: dockerfile: Dockerfile.dev context: ./client volumes: - /app/node_modules - ./client:/app stdin_open: true nginx: restart: always build: dockerfile: Dockerfile context: ./nginx ports: - "3000:80" server: build: dockerfile: Dockerfile.dev context: ./server container_name: app_server volumes: - /app/node_modules - ./server:/app
YAML
복사
개발환경 테스트 실행(docker-compose up)
→ 빌드 시도했을 경우, docker-compose up —build
→ 이미 빌드된 파일이 있을 경우, docker-compose up 입력 시 기존 빌드파일 사용함. 따라서 변경사항을 반영하기 위해 —build 키워드 입력할 것

5. 운영 환경 배포

가. Dockerfile 작성

client Dockerfile
→ nginx 필요
# client/nginx/default.conf server { listen 3000; location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; } }
YAML
복사
# client/Dockerfile FROM node:alpine as builder WORKDIR /app COPY ./package.json ./ RUN npm install COPY . . RUN npm run build FROM nginx EXPOSE 3000 COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf COPY --from=builder /app/build /usr/share/nginx/html
Docker
복사
server Dockerfile
FROM node:alpine WORKDIR /app COPY ./package.json ./ RUN npm install COPY . . CMD ["npm", "run", "start"]
YAML
복사
github에 배포하려는 프로젝트 repository 생성 후 commit

나. Travis CI Dokcer Hub AWS EB Steps

1.
깃헙 push
2.
Travis CI가 깃헙 코드 자동으로 가져옴
3.
Travis CI가 해당 코드로 테스트 코드 실행
4.
테스트 성공 시 운영환경 이미지 빌드
5.
빌드된 이미지를 Docker Hub로 전송
→ Travis CI가 Github으로부터 source를 가져올 수 있도록 activate 설정
→ 빌드 작업할 수 있도록 .travis.yml 파일 작성
language: generic sudo: required # 앱을 도커 환경에서 실행할 것 명시 services: - docker # 테스트 이미지 생성 before_install: - docker build -t gentlemj/todolist-app -f ./client/Dockerfile.dev ./client # 테스트 이미지에 따라 테스트 수행 script: - docker run -e CI=true gentlemj/todolist-app npm run test after_success: # 테스트 성공 시, 프로젝트의 운영버전 이미지 빌드 설정 - docker build -t gentlemj/docker-client ./client - docker build -t gentlemj/docker-server ./server - docker build -t gentlemj/docker-nginx ./nginx # 도커허브 로그인 - echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_ID" --password-stdin # 빌드된 이미지를 도커허브에 전송 - docker push gentlemj/docker-client - docker push gentlemj/docker-server - docker push gentlemj/docker-nginx
YAML
복사
6.
Docker Hub가 AWS EB에 이미지 전송
→ EB 새 환경 설정
AWS EB 서비스에서 새 EB 환경의 이름 및 플랫폼 지정(Docker, Multi-container)
→ .travis.yml 파일 배포부분 작성
deploy: # 외부 서비스 표시 provider: elasticbeanstalk # EB 접속하여 직접 확인 region: "ap-northeast-2" app: "todolist-app" env: "TodolistApp-env" # Traivs CI에서 EB로 이미지 전송 시 S3를 거쳐가므로 # S3의 bucket을 작성 bucket_name: elasticbeanstalk-ap-northeast-2-297348835951 bucket_path: "todolist-app" # master branch에 psuh할 때만 배포 실행 on: branch: master access_key_id: $AWS_ACCESS_KEY secret_access_key: $AWS_SECRET_ACCESS_KEY
YAML
복사
→ Travis CI의 AWS 접근을 위한 API key 생성
1.
AWS dashboard에서 IAM 선택 후 IAM 사용자 추가
2.
사용자 이름 및 AWS 엑세스 유형 선택(프로그래밍 방식)
3.
기존 정책 직접 연결 카테로리 내 AWSElasticbeanstalkFullAccess 권한만 허용
4.
사용자 생성 완료 후, 엑세스 키 ID와 비밀 엑세스 키를 Travis CI 사이트의 설정 부분에 추가
5.
.travis.yml 파일에 위의 키를 활용한 Access 부분 축
access_key_id: $AWS_ACCESS_KEY secret_access_key: $AWS_SECRET_ACCESS_KEY
YAML
복사
7.
AWS EB가 배포 실행
→ Dockerrun.aws.json 작성
EB의 프로세스 처리 명세
{ "AWSEBDockerrunVersion": 2, "containerDefinitions": [ { "name": "client", // Docker hub에 올린 Docker image 이름 작성 "image": "gentlemj/docker-client", // 다른 컨테이너에서 hostname 활용해서 접근 가능 "hostname": "client", // 컨테이너 구성 실패 시 나머지 컨테이너에 영향을 주지 않고 종료 // 반면 true일 경우, 다른 컨테이너도 종료 "essential": false, // 컨테이너에 따라 메모리 할당 "memory": 128 }, { "name": "server", "image": "gentlemj/docker-server", "hostname": "server", "essential": false, "memory": 128 }, { "name": "nginx", "image": "gentlemj/docker-nginx", "hostname": "nginx", "essential": true, // 컨테이너의 네트워크 지점을 호스트의 지점과 매핑 "portMappings": [ { "hostPort": 80, "containerPort": 80 } ], "links": ["client", "server"], "memory": 128 } ] }
JSON
복사
→ 위의 모든 설정 완료 후 '.travis.yml' push하면 배포 완료
질문) proxy 설정 관련