인프런 ==> 따라하며-배우는-노드-리액트-기본
강의 자료 : https://braverokmc79.github.io/react-series/BoilerPlate%20Korean.html
소스 : https://github.com/braverokmc79/react-series
섹션 0. Node JS
강의
1.소개
2.NODE JS 와 EXPRESS JS 다운로드 하기
3.몽고 DB 연결
4.MongoDB Model & Schema
5.GIT 설치
6.SSH를 이용해 GITHUB 연결
7.BodyParser & PostMan & 회원 가입 기능
8.Nodemon 설치
9.비밀 설정 정보 관리
10.Bcrypt로 비밀번호 암호화 하기
11.로그인 기능 with Bcrypt (1)
12.토큰 생성 with jsonwebtoken
13.Auth 기능 만들기
14.로그아웃 기능
섹션 1. React JS
20.강의
15.리액트란 ?
16.Create-React-App
17.npm npx
18.구조 설명
19.CRA to Our Boilerplate
20.React Router Dom
21.데이터 Flow & Axios
22.CORS 이슈, Proxy 설정
23.Proxy Server ?
24.Concurrently
25.Antd CSS Framwork 26.Redux 기초
27.Redux UP !!!!!
28.React Hooks
29.로그인 페이지(1)
30.로그인 페이지(2)
31.회원 가입 페이지
32.로그아웃
33.인증 체크 (1)
34.인증 체크 (2) 강의 마무리.
2. Diagram 자료
2 강의
Diagram HTML 자료
Diagram XML 자료
1.소개
2.NODE JS 와 EXPRESS JS 다운로드 하기
expressjs 공식 문서 : https://expressjs.com/
설치 중
이미 Node.js 를 설치했다고 가정 하고 애플리케이션을 저장할 디렉토리를 만들고 이 디렉토리를 작업 디렉토리로 만듭니다.
$ mkdir myapp $ cd myapp
npm init명령을 사용하여 응용 프로그램에 대한 파일 package.json을 만듭니다. package.json작동 방식 에 대한 자세한 내용 은 npm의 package.json 처리 세부 사항을 참조하세요 .
$ npm init
이 명령은 응용 프로그램의 이름 및 버전과 같은 여러 항목을 묻는 메시지를 표시합니다. 지금은 RETURN을 눌러 다음 예외를 제외하고 대부분의 기본값을 수락할 수 있습니다.
entry point: (index.js)
를 입력 app.js하거나 기본 파일 이름을 원하는 대로 입력합니다. 원하는 경우 index.jsRETURN을 눌러 제안된 기본 파일 이름을 수락하십시오.
이제 myapp디렉토리에 Express를 설치하고 종속성 목록에 저장하십시오. 예를 들어:
$ npm install express
Express를 임시로 설치하고 종속성 목록에 추가하지 않으려면:
$ npm install express --no-save
#biler-plate 디렉토리 설치
$ mkdir biler-plate
$ cd biler-plate
$ npm init
#express 설치
$ yarn add express
index.js 파일 생성 후 작성
const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => { res.send('Hello World! 안녕하세요.') }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
package.json
{ "name": "biler-plate", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "macaronics", "license": "ISC", "dependencies": { "express": "^4.18.1" } }
실행 :
$ node index.js
또는
$ npm start
3.몽고 DB 연결
몽고 DB 윈도우 설치 및 실행
2)[MongoDB] 몽고디비 GUI 개발/관리도구 Studio 3T 설치 (Robo 3T)
몽고 DB 클라우드 가입
몽고 DB 툴로 연결 할 경우
로컬일 경우 : mongodb://localhost:27017
클라우드일 경우 : mongodb+srv://macaronics:<password>@mongo-macaronics.y37mjuf.mongodb.net/test
몽고DB 연결 참조 : https://bokki.org/nodejs/nodejs-mongoose로-mongodb-연결하기/
nodejs 몽고 DB 연결 라이브러리 설치
$ npm install mongoose
mongodbURI.js
const mongodbURI = "mongodb+srv://macaronics:<password>@mongo-macaronics.y37mjuf.mongodb.net/test"; module.exports = mongodbURI;
index.js
const express = require('express') const app = express() const port = 5000 const mongodbURI = require('./mongodbURI') const mongoose = require("mongoose"); // MongoDB 연결 1 // '단' 하나의 데이터베이스 연결 시 유효. mongoose.connect( mongodbURI, // MongoDB url // { // useNewUrlParser: true, // useUnifiedTopology: true, // useCreateIndex: true, // useFindAndModify: false, // } // options // MongoDB 5 버전부터 useNewUrlParser 옵션을 사용해주지 않으면 에러가 뜹니다. //Mongoose 6 사용으로 에러 발생시. //useNewUrlParser, useUnifiedTopology, //useFindAndModify 및 useCreateIndex는 더 이상 지원되지 않는 옵션입니다. //Mongoose 6은 항상 useNewUrlParser, useUnifiedTopology 및 useCreateIndex가 true이고 // useFindAndModify가 false인 것처럼 작동합니다.코드에서 이러한 옵션을 제거하십시오. ).then(() => console.log("MongoDB Connected...")).catch(err => console.error("에러 :", err)); app.get('/', (req, res) => { res.send('Hello World! 안녕하세요.') }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
실행 :
npm start
4.MongoDB Model & Schema
몽고 DB Scema 예
const Schema = mongoose.Schema; const productSchema = mongoose.Schema({ writer: { type: Schema.Types.ObjectId, ref: "User" }, title: { type: String, maxlength: 50 }, descrip: { type: String } }, { timestamps: true }) const Product = mongoose.model('Product', productSchema); module.exports = { Product };
models/User.js 에 User 스키마 생성
const mongoose = require('mongoose'); const userSchema = mongoose.Schema({ name: { type: String, maxlength: 50 }, email: { type: String, trim: true, unique: 1 }, password: { type: String, minlength: 5 }, lastname: { type: String, maxlength: 50 }, role: { type: Number, default: 0 }, image: String, token: { type: String }, tokenExp: { type: Number } }) const User = mongoose.model('User', userSchema); module.exports = { User };
테스트 : index.js
const express = require('express') const app = express() const port = 5000 const mongodbURI = require('./mongodbURI') const mongoose = require("mongoose"); const User = require("./models/User"); console.log("User : ", User); //Mongoose 6은 항상 useNewUrlParser, useUnifiedTopology 및 useCreateIndex가 true이고 useFindAndModify가 false인 것처럼 작동 // mongoose.connect(mongodbURI, // ).then(() => console.log("MongoDB Connected...")).catch(err => console.error("에러 :", err)); app.get('/', (req, res) => { res.send('Hello World! 안녕하세요.') }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
$npm start
실행 결과=>
> biler-plate@1.0.0 start
> node index.js
User : { User: Model { User } }
Example app listening on port 500
5.GIT 설치
깃 다운로드 : https://git-scm.com/downloads
상황을 들어보자.
작업이 완료되어 저장소에 push를 날렸다.
하지만 모르고 작업에 필요했지만 올리지 않아도 되는 private이라는 폴더를 올려버렸다.
그래서 폴더를 삭제하고, 다시 push를 날렸다.
하지만 Github에서는 삭제되지 않았다.
폴더를 리팩토링하고 난 후에도 마찬가지이다.
삭제 및 이동을 하고 push를 할 시 Github에 있는 폴더는 유지된 채 바뀐 폴더가 새로 생성된다.
원격 저장소에 이미 파일은 저장되어있다.
로컬에서 삭제만 한다고 해서 원격 저장소에서 삭제가 이루어지지 않는다.
이 경우 git 명령어를 통한 파일 삭제 후 push를 해줘야한다.
$ git rm <fileName>
$ git rm --cached <fileName>
위의 git 명령어를 통해 폴더나 파일을 삭제하면 된다.
--cached 는 rm 명령어의 옵션이다.
--cached의 유무에 따라 차이점을 알아보자.
정의는 아래와 같다.
git rm => 원격 저장소와 로컬 저장소에 있는 파일을 삭제한다.
git rm --cached => 원격 저장소에 있는 파일을 삭제한다. 로컬 저장소에 있는 파일은 삭제하지 않는다.
정의 그대로 --cached 옵션을 넣을 경우에는 로컬 저장소에 있는 건 삭제되지 않는다.
즉, 로컬 저장소에는 존재하지만, 원격 저장소에 올라가진 않는다.
생각해보면 어떤 경우 쓰는지 모르겠고, 필요없는 옵션이라고 볼 수 있다.
예를 들어 작업시에만 쓰는 파일이나 로그 등 작업할 땐 필요하지만 Github에 안 올려도 되는 것들이 있다고 가정하자.
그렇다는 건, 작업할 때마다 올리지 않아도 되는 것들을 항상 고려해야한다.
즉 한가지 예로, Github에는 반영되지 않기 위해 git rm 명령어를 통해 삭제 해주는 작업을 말한다.
이런 경우를 위해 --cached 옵션을 통해 이런 불필요한 과정이 필요하지 않게 할 수 있다는 것이다.
그렇기에 일반적으로 git rm 명령어를 쓸 때에는 --cached 옵션을 사용한다.
아무튼 git rm 명령어를 통해 Github 저장소에 있는 private 폴더를 삭제할 수 있게 되었다.
출처: https://mygumi.tistory.com/103 [마이구미의 HelloWorld:티스토리]
$ git rm --cached node_modules -r
$ git status
$ git commit -m "처음 저장소에 올림"
$ git status
6.SSH를 이용해 GITHUB 연결
.ssh 설치 확인
$ ls -a ~/.ssh
./ ../ id_rsa id_rsa.pub known_hosts known_hosts.old
ssh 가 없을 경우
git hub 공식문서 = > 새 SSH 키 생성 및 ssh-agent에 추가
7.BodyParser & PostMan & 회원 가입 기능
body-parser는 미들웨어이다. 즉, 요청(request)과 응답(response) 사이에서 공통적인 기능을 수행하는 소프트웨어이다.
그렇다면 역할은 무엇일까? 바로 요청의 본문을 지정한 형태로 파싱해주는 미들웨어이다.
body-parser는 HTTP 의 post, put 요청시 HTTP 의 본문(body)를 parsing 하여 나온 결과값을
req.body 에 넣어 body 프로퍼티를 사용할 수 있도록 하는 미들웨어입니다.
body-parser 는 4가지의 parser 를 지원합니다.
1. JSON body parser
2. Raw body parser
3. Text body Parser
4. URL-encoded from body parser
body-parser 설치
$ yarn add body-parser
또는
$npm install body-parser
postman 설치 : https://www.postman.com/
index.js
const express = require('express') const app = express() const port = 5000 const mongodbURI = require('./mongodbURI') const mongoose = require("mongoose"); const { User } = require("./models/User"); const bodyParser = require("body-parser"); //aplication/json app.use(bodyParser.json()); //application/x-www-form-unlencoded // body-parser deprecated undefined extended //app.use(bodyParser.urlencoded({ extends: true })); //Mongoose 6은 항상 useNewUrlParser, useUnifiedTopology 및 useCreateIndex가 true이고 useFindAndModify가 false인 것처럼 작동 mongoose.connect(mongodbURI, ).then(() => console.log("MongoDB Connected...")).catch(err => console.error("에러 :", err)); app.get('/', (req, res) => { res.send('Hello World! 안녕하세요.') }) app.post('/register', (req, res) => { //회원 가입 할때 필요한 정보들을 client 에서 가져오면 //그것들을 데이터 베이스에 넣어준다. console.log("req", req.body.name); const user = new User(req.body); //몽고 DB 에 설정된 save 사용 user.save((err, doc) => { if (err) { return res.json({ success: false, err }) } return res.status(200).json({ success: true }); }); }); app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
postman 테스트
Studio 3D for MongoDB 툴로 DB 저장 확인
cloud.mongodb.com 사이트에서 DB 저장 확인
8.Nodemon 설치
* 실시간 프로젝트 변경 감지 nodemon 설치
yarn 설치 오류시
$ npm i nodemon --save-dev
package.json
실행 스크립트 추가
"scripts": { "start": "node index.js", "backend":"nodemon index.js", "test": "echo \"Error: no test specified\" && exit 1" },
9.비밀 설정 정보 관리
package.json
다음과 같이
production 실행 환경 변수는 : NODE_ENV=production,
development 실행 환경 변수는 NODE_ENV=development 로 선언한다.
"scripts": { "start": "NODE_ENV=production node index.js", "backend":"NODE_ENV=development nodemon index.js", "test": "echo \"Error: no test specified\" && exit 1" },
$ production 환경 실행 : npm start 또는 npm run start
$ development 환경 실행 : npm run backend
dev.js
module.exports = { mongoURI: "mongodb+srv://macaronics:<password>@mongo-macaronics.y37mjuf.mongodb.net/test" }
prod.js
module.exports = { mongoURI: process.env.MONGO_URI }
key.js
if (process.env.NODE_ENV === 'production') { module.exports = require("./prod"); } else { module.exports = require("./dev"); }
index.js
const config = require("./config/key"); ~ ` mongoose.connect(config.mongoURI, ).then(() => console.log("MongoDB Connected...")).catch(err => console.error("에러 :", err));
10.Bcrypt로 비밀번호 암호화 하기
bcrypt 라이브러리 설치
$ yarn add bcrypt
bcrypt npm 문서 = > https://www.npmjs.com/package/bcrypt
async (recommended)
const bcrypt = require('bcrypt'); const saltRounds = 10; const myPlaintextPassword = 's0/\/\P4$$w0rD'; const someOtherPlaintextPassword = 'not_bacon'
bcrypt.genSalt(saltRounds, function(err, salt) { bcrypt.hash(myPlaintextPassword, salt, function(err, hash) { // Store hash in your password DB. }); });
User.js
const mongoose = require('mongoose'); const bcrypt = require('bcrypt'); const saltRounds = 10; const userSchema = mongoose.Schema({ name: { type: String, maxlength: 50 }, email: { type: String, trim: true, unique: 1 }, password: { type: String, minlength: 5 }, lastname: { type: String, maxlength: 50 }, role: { type: Number, default: 0 }, image: String, token: { type: String }, tokenExp: { type: Number } }) //몽고DB 에 저장하기 전에 실행하는 함수 userSchema.pre('save', function (next) { const user = this; //비밀번호가 변환될때면 다음을 실행한다. if (user.isModified('password')) { //비밀번호를 암호와 시킨다. bcrypt.genSalt(saltRounds, function (err, salt) { if (err) return next(err); bcrypt.hash(user.password, salt, function (err, hash) { if (err) return next(err); user.password = hash; next(); }); }); } else { next(); } }) const User = mongoose.model('User', userSchema); module.exports = { User };
DB 확인
11.로그인 기능 with Bcrypt (1)
models/User.js 에 다음을 추가
//userSchema.methods => 커스텀 함수 생성 userSchema.methods.comparePassword = function (plainPassword, cb) { //plainPassword 비밀번호가 12345 일때, this.password 는 암호화된 비밀번호 $2b$10$LK86g2vaPNMHVLkj69hO7uzodTXATNMezdKnWymKi8QoTX9pE3bey bcrypt.compare(plainPassword, this.password, function (err, isMatch) { if (err) return cb(err); else return cb(null, isMatch); }); }
전체
const mongoose = require('mongoose'); const bcrypt = require('bcrypt'); const saltRounds = 10; const userSchema = mongoose.Schema({ name: { type: String, maxlength: 50 }, email: { type: String, trim: true, unique: 1 }, password: { type: String, minlength: 5 }, lastname: { type: String, maxlength: 50 }, role: { type: Number, default: 0 }, image: String, token: { type: String }, tokenExp: { type: Number } }) //몽고DB 에 저장하기 전에 실행하는 함수 userSchema.pre('save', function (next) { const user = this; //비밀번호가 변환될때면 다음을 실행한다. if (user.isModified('password')) { //비밀번호를 암호와 시킨다. bcrypt.genSalt(saltRounds, function (err, salt) { if (err) return next(err); bcrypt.hash(user.password, salt, function (err, hash) { if (err) return next(err); user.password = hash; next(); }); }); } else { next(); } }) //userSchema.methods => 커스텀 함수 생성 userSchema.methods.comparePassword = function (plainPassword, cb) { //plainPassword 비밀번호가 12345 일때, this.password 는 암호화된 비밀번호 $2b$10$LK86g2vaPNMHVLkj69hO7uzodTXATNMezdKnWymKi8QoTX9pE3bey bcrypt.compare(plainPassword, this.password, function (err, isMatch) { if (err) return cb(err); else return cb(null, isMatch); }); } const User = mongoose.model('User', userSchema); module.exports = { User };
index.js
app.post('/register', (req, res) => { //회원 가입 할때 필요한 정보들을 client 에서 가져오면 //그것들을 데이터 베이스에 넣어준다. const user = new User(req.body); //몽고 DB 에 설정된 save 사용 user.save((err, doc) => { if (err) { return res.json({ success: false, err }) } return res.status(200).json({ success: true }); }); });
12.토큰 생성 with jsonwebtoken
jsonwebtoken 토큰 라이브러리 등록
$ yarn add jsonwebtoken
npm 공식 문서 : https://www.npmjs.com/package/jsonwebtoken
cookie-parser 쿠키 라이브러리 등록
$ yarn add cookie-parser
사용등록
const cookieParser = require("cookie-parser"); app.use(cookieParser());
models/User.js
~ userSchema.methods.generateToken = function (cb) { //몽고DB 에 저장된 형태 /* _id:ObjectId("630edadb1f06e2b0be7adeea") name:"홍길동" email:"test1@gmail.com" password: "$2b$10$LK86g2vaPNMHVLkj69hO7uzodTXATNMezdKnWymKi8QoTX9pE3bey" role: 0 __v:0 */ //jwt.sign({ foo: 'bar' }, 'shhhhh'); shhhhh 는 임이 문자 //jsonwebtoken 을 이용해서 token 을 생성하기 const user = this; const token = jwt.sign(user._id.toHexString(), "abcd!!!333"); user.token = token; user.save(function (err, user) { if (err) return cb(err) cb(null, user) }); } ~
index.js
app.post('/login', (req, res) => { //1.요청된 이메일을 데이터베이스에서 찾는다. User.findOne({ email: req.body.email }, (err, user) => { if (!user) { return res.json({ loginSuccess: false, message: "제공된 이메일에 해당하는 유저가 없습니다." }) } //2.요청된 이메일이 데이터 베이스에 있다면 비밀번호가 맞는 비밀번호 인지 확인 user.comparePassword(req.body.password, (err, isMatch) => { if (!isMatch) return res.json({ loginSuccess: false, message: "비밀번호가 틀렸습니다." }); //3.비밀 번호까지 같다면 Token을 생성 user.generateToken((err, user) => { if (err) return res.status(400).send(err); //토큰을 저장한다. 어디에? 쿠키, 로컬스토리지 res.cookie("x_auth", user.token).status(200).json({ loginSuccess: true, userId: user._id, // token: user.token }) }); }) }); });
13.Auth 기능 만들기
middleware 디렉토리 생성후 auth.js 파일 생성.
토큰을 복호화 한후 유저를 찾는 임의 함수인 findByToken 은 User.js 에서 만들어 처리해 준다.
const { User } = require("../models/User"); /** 인증 처리를 하는 곳 **/ let auth = (req, res, next) => { //1.클라이언트 쿠키에서 토큰을 가져온다. let token = req.cookies.x_auth; // //2.토큰을 복호화 한후 유저를 찾는다. User.findByToken(token, (err, user) => { //3.유저가 없으면 인증 No ! if (err) throw err; if (!user) return res.json({ isAuth: false, error: true }) //4.유저가 있으면 인증 Okey req.token = token; req.user = user; next(); }); } module.exports = auth;
클라이언트 쿠키에서 토큰 가지고 와서 미들웨어 auth 통해 인증 처리
index.js
const auth = require("./middleware/auth"); ~ ~ //role 1 어드민 role 2 특정 부서 어드민 //rele 0 -> 일반유저 , role 0 이 아니면 관리자. app.get('/api/users/auth', auth, (req, res) => { //여기 까지 미들웨어를 통과해 왔다는 얘기는 Authentication이 True 라는 말 res.status(200).json({ _id: req.user._id, isAdmin: req.user.role === 0 ? false : true, isAuth: true, email: req.user.email, name: req.user.name, lastname: req.user.lastname, role: req.user.role, image: req.user.image }); }); ~ ~
User.js
1) 라이언트에서 가져온 토큰과 SECRET_KEY 값과 조합을 해서 decoded 값을 생성한다.
여기서 decoded 는 user._id 가 된다. 만약 SECRET_KEY 불일치로 조합에 실패할 경우
decoded 값인 user._id 는 undefined 가 된다.
2)decoded 하연 생성된 유저아이디와 토큰값을 이용해서 DB에서 정보를 가져온다.
3)DB에 가져온 데이터가 없으면 에러, 있으면 유저 정보값을 콜백으로 반환시켜 미들웨어 auth 에서 처리 시킨다.
const mongoose = require('mongoose'); const bcrypt = require('bcrypt'); const saltRounds = 10; const jwt = require("jsonwebtoken"); const SECRET_KEY = "abcd!!!333"; ~ userSchema.statics.findByToken = function (token, cb) { const user = this; /*** 토큰을 decode 한다.***/ //user._id+''=token //const SECRET_KEY: "abcd!!!333" jwt.verify(token, SECRET_KEY, function (err, decoded) { console.log("클라이언트에서 가져온 토큰값 :", token); console.log("decoded 는 user._id :", decoded); //예 //클라이언트에서 가져온 토큰값: eyJhbGciOiJIUzI1NiJ9.NjMwZWRhZGIxZjA2ZTJiMGJlN2FkZWVh.venwg5 - aVAvLA72IDSa1VNkls2MYwK6zXp3wJKSrn6k //ecoded 는 user._id : 630edadb1f06e2b0be7adeea //findOne() 은 몽고DB함수 user.findOne({ "_id": decoded, "token": token }, function (err, user) { if (err) return cb(err) cb(null, user); }); }); } ~
14.로그아웃 기능
1) 미들웨어를 통해 토큰값으로 아이디값을 가져온다.
가져온 아이디 예: 630edadb1f06e2b0be7adeea
2) 몽고 DB 내장 함수인 findOneAndUpdate 통해 몽고 DB 토큰값을 지운다.
app.get("/api/users/logout", auth, (req, res) => { User.findOneAndUpdate({ _id: req.user._id }, { token: "" }, (err, user) => { if (err) return res.json({ success: false, err }); return res.status(200).send({ success: true }); } ); });
소스 : https://github.com/braverokmc79/react-series
댓글 ( 4)
댓글 남기기