
인프런 ==> 따라하며-배우는-노드-리액트-기본
강의 자료 : 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
동영상 34개













댓글 ( 4)
댓글 남기기